Commit 9d2f51fa authored by Markus Klein's avatar Markus Klein Committed by Christian Kuhn
Browse files

[TASK] Reduce number of needed SQL queries for resorting of records

This reduces the resorting to require only a single update statement
in contrast to "number of records" statements.
It also only updates the records after the current one, so less records
are touched when resorting.

Now handling of the sorting value is following:
- if no record exists: set interval as sorting number
- if inserted before an element: put in the middle of the existing elements
- if inserted behind the last element: add interval to last sorting number
- if collision: move all subsequent records by 2 * interval, insert
  new record with sorting = collision + interval

Before, in case of the collision all the records from given pid were
resorted. The first item was getting 512 (2*sortIntervals) as sorting,
following records were getting value of
previous record + sortIntervals (768,1024,...).

Note:
I case we have multiple records with the same sorting value,
resorting will not fix that, but just move them up.

New algorithm drawback:
Sorting column values will grow quicker in time as we're always
increasing the sorting value and never tidying the whole table
(thus possibly lowering the max used value).
The current implementation still allows to insert some 80 million
records per colpos if sizeof(int) is only 32bit.
A lot more if we have 64bit.

Changes to the DataHandler->getSortNumber method are mostly comments and
formatting (added guard clause), no change in behavior except for
additional sorting by uid. The additional sorting was required to have
the same test results across database servers.

Description of updated tests:

- workspaces/.../IRRE/ForeignField/Publish/DataSet/copyPage.csv
  workspaces/.../IRRE/ForeignField/Publish/DataSet/copyPageWHotelBeforeParentContent.csv
  workspaces/.../IRRE/ForeignField/PublishAll/DataSet/copyPage.csv
  workspaces/.../IRRE/ForeignField/PublishAll/DataSet/copyPageWHotelBeforeParentContent.csv

  Now copied tx_irretutorial_1nff_price records have the same
  sorting values/order as original records (1,2,3 instead of 1,3,2).
  So workspace actions result in the same values as live operations.

- backend/.../Controller/Page/Localization/CSV/DataSet/CreatedElementOrdering.csv
  Order of the records in both languages is kept (before and after the patch),
  It might look wrong that a record 2.5 in translation is between 1 and 2
  and not between 2 and 3, but keep in mind that colpos is taken into account.
  So record 2.5 has just to be after record 1 (only the two are in colpos 0).

- install/.../Updates/RowUpdater/DataSet/recordsCanBeUpdated.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizePageWSynchronization.csv
  core/.../IRRE/ForeignField/Modify/DataSet/createNLocalizeParentContentNHotelNOfferChildren.csv
  core/.../IRRE/ForeignField/Modify/DataSet/createNLocalizeParentContentNHotelNOfferChildrenWOSortBy.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizeNCopyPageWSynchronization.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizePageWExclude.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizePageWithSynchronizationAndCustomLocalizedHotel.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizeParentContentNCreateNestedChildrenWLanguageSynchronization.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizeParentContentSynchronization.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizeParentContentChainLanguageSynchronizationSource.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizeParentContentWAllChildren.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizePageAddMonoglotHotelChildNCopyPageWSynchronization.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizePageNAddMonoglotHotelChildWSynchronization.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizePageNAddHotelChildWSynchronization.csv
  core/.../IRRE/ForeignField/Modify/DataSet/localizePageNAddHotelChildWExclude.csv
  core/.../IRRE/ForeignField/Modify/DataSet/copyParentContentToLanguageWAllChildren.csv
  workspaces/.../IRRE/ForeignField/Publish/DataSet/createNLocalizeParentContentNHotelNOfferChildren.csv
  workspaces/.../IRRE/ForeignField/PublishAll/DataSet/localizeParentContentWAllChildren.csv
  workspaces/.../IRRE/ForeignField/PublishAll/DataSet/createNLocalizeParentContentNHotelNOfferChildren.csv
  workspaces/.../IRRE/ForeignField/PublishAll/DataSet/createNLocParentNHotelNOfferChildrenNDiscardCreatedParent.csv
  workspaces/.../IRRE/ForeignField/PublishAll/DataSet/createNLocParentNHotelNOfferChildrenNDiscardLocParent.csv
  workspaces/.../IRRE/ForeignField/Modify/DataSet/copyPage.csv
  workspaces/.../IRRE/ForeignField/Modify/DataSet/localizeParentContentWAllChildren.csv
  workspaces/.../IRRE/ForeignField/Modify/DataSet/createNLocalizeParentContentNHotelNOfferChildren.csv
  workspaces/.../IRRE/ForeignField/Modify/DataSet/createNLocalizeParentContentNHotelNOfferChildrenWOSortBy.csv
  workspaces/.../IRRE/ForeignField/Modify/DataSet/createNLocParentNHotelNOfferChildrenNDiscardCreatedParent.csv
  workspaces/.../IRRE/ForeignField/Modify/DataSet/copyPageWHotelBeforeParentContent.csv
  workspaces/.../IRRE/ForeignField/Publish/DataSet/localizeParentContentWAllChildren.csv
  workspaces/.../IRRE/ForeignField/Publish/DataSet/createNLocParentNHotelNOfferChildrenNDiscardCreatedParent.csv
  workspaces/.../IRRE/ForeignField/Publish/DataSet/createNLocParentNHotelNOfferChildrenNDiscardLocParent.csv

  Order of the records is kept, less records have changed sorting values,
  as now we're just resorting records after the current one.

- impexp/.../DatabaseAssertions/importPagesAndRelatedTtContentWithDifferentImageToExistingData.csv
  impexp/.../DatabaseAssertions/importPagesAndRelatedTtContentWithSameImageToExistingData.csv
  now existing page sorting is not changed when importing data.

- workspaces/.../IRRE/CSV/Publish/DataSet/moveParentContentToDifferentPageNChangeSorting.csv
  workspaces/.../IRRE/CSV/Modify/DataSet/moveParentContentToDifferentPageNChangeSorting.csv
  workspaces/.../IRRE/CSV/PublishAll/DataSet/moveParentContentToDifferentPageNChangeSorting.csv

  in CSV relation we don't care about the child sorting field as
  the order is determined by the value of the CSV field.

Resolves: #85300
Releases: master
Change-Id: I033acae475be8778d10dfb5d506d63804aa941e0
Reviewed-on: https://review.typo3.org/57218


Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: Markus Klein's avatarMarkus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein's avatarMarkus Klein <markus.klein@typo3.org>
Reviewed-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent df60fb73
"tt_content",,,,,,,,
,"uid","pid","deleted","sys_language_uid","l18n_parent","header","colPos","sorting"
,1,1,0,0,0,"Test content 1",0,512
,2,1,0,0,0,"Test content 2",1,1024
,3,1,0,0,0,"Test content 3",1,1280
,4,1,0,1,0,"[Translate to Dansk:] Test content 1",0,768
,5,1,0,1,0,"[Translate to Dansk:] Test content 2",1,1152
,6,1,0,1,0,"[Translate to Dansk:] Test content 3",1,1216
,7,1,0,0,0,"Test content 2.5",0,1088
,8,1,0,1,0,"[Translate to Dansk:] Test content 2.5",0,896
,1,1,0,0,0,"Test content 1",0,1
,2,1,0,0,0,"Test content 2",1,514
,3,1,0,0,0,"Test content 3",1,1027
,4,1,0,1,0,"[Translate to Dansk:] Test content 1",0,257
,5,1,0,1,0,"[Translate to Dansk:] Test content 2",1,770
,6,1,0,1,0,"[Translate to Dansk:] Test content 3",1,898
,7,1,0,0,0,"Test content 2.5",0,642
,8,1,0,1,0,"[Translate to Dansk:] Test content 2.5",0,385
......@@ -145,6 +145,15 @@ class LocalizationControllerTest extends \TYPO3\TestingFramework\Core\Functional
}
/**
* This test:
* - copies default language records 1,2,3, into language 1 ("free mode translation")
* - creates new CE in default language after record 2, called 'Test content 2.5'
* - copies into language record 9 ('Test content 2.5')
* - checks if translated/copied record "[Translate to Dansk:] Test content 2.5" has sorting value after
* "[Translate to Dansk:] Test content 1", which is the previous record in the colpos.
*
* For detail about the sorting algorithm when translating records, see DataHandler->getPreviousLocalizedRecordUid
*
* @test
*/
public function copyingNewContentFromLanguageIntoExistingLocalizationHasSameOrdering(): void
......
......@@ -483,7 +483,9 @@ class DataHandler implements LoggerAwareInterface
];
/**
* Integer: The interval between sorting numbers used with tables with a 'sorting' field defined. Min 1
* The interval between sorting numbers used with tables with a 'sorting' field defined.
*
* Min 1, should be power of 2
*
* @var int
*/
......@@ -7367,6 +7369,27 @@ class DataHandler implements LoggerAwareInterface
* Returning sorting number for tables with a "sortby" column
* Using when new records are created and existing records are moved around.
*
* The strategy is:
* - if no record exists: set interval as sorting number
* - if inserted before an element: put in the middle of the existing elements
* - if inserted behind the last element: add interval to last sorting number
* - if collision: move all subsequent records by 2 * interval, insert new record with collision + interval
*
* How to calculate the maximum possible inserts for the worst case of adding all records to the top,
* such that the sorting number stays within INT_MAX
*
* i = interval (currently 256)
* c = number of inserts until collision
* s = max sorting number to reach (INT_MAX - 32bit)
* n = number of records (~83 million)
*
* c = 2 * g
* g = log2(i) / 2 + 1
* n = g * s / i - g + 1
*
* The algorithm can be tuned by adjusting the interval value.
* Higher value means less collisions, but also less inserts are possible to stay within INT_MAX.
*
* @param string $table Table name
* @param int $uid Uid of record to find sorting number for. May be zero in case of new.
* @param int $pid Positioning PID, either >=0 (pointing to page in which case we find sorting number for first record in page) or <0 (pointing to record in which case to find next sorting number after this record)
......@@ -7375,115 +7398,120 @@ class DataHandler implements LoggerAwareInterface
public function getSortNumber($table, $uid, $pid)
{
$sortColumn = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
if ($sortColumn) {
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$queryBuilder = $connectionPool->getQueryBuilderForTable($table);
$this->addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
if (!$sortColumn) {
return null;
}
$queryBuilder
->select($sortColumn, 'pid', 'uid')
->from($table);
// Sorting number is in the top
if ($pid >= 0) {
// Fetches the first record under this pid
$row = $queryBuilder
->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
->orderBy($sortColumn, 'ASC')
->setMaxResults(1)
->execute()
->fetch();
// There was an element
if (!empty($row)) {
// The top record was the record it self, so we return its current sortnumber
if ($row['uid'] == $uid) {
return $row[$sortColumn];
}
// If the pages sortingnumber < 1 we must resort the records under this pid
if ($row[$sortColumn] < 1) {
$this->resorting($table, $pid, $sortColumn, 0);
// First sorting number after resorting
return $this->sortIntervals;
}
// Sorting number between current top element and zero
return floor($row[$sortColumn] / 2);
}
// No pages, so we choose the default value as sorting-number
// First sorting number if no elements.
return $this->sortIntervals;
}
// Sorting number is inside the list
// Fetches the record which is supposed to be the prev record
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$queryBuilder = $connectionPool->getQueryBuilderForTable($table);
$this->addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
$queryBuilder
->select($sortColumn, 'pid', 'uid')
->from($table);
// find and return the sorting value for the first record on that pid
if ($pid >= 0) {
// Fetches the first record (lowest sorting) under this pid
$row = $queryBuilder
->where($queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter(abs($pid), \PDO::PARAM_INT)
))
->execute()
->fetch();
->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
->orderBy($sortColumn, 'ASC')
->addOrderBy('uid', 'ASC')
->setMaxResults(1)
->execute()
->fetch();
// There was a record
if (!empty($row)) {
// Look, if the record UID happens to be an offline record. If so, find its live version. Offline uids will be used when a page is versionized as "branch" so this is when we must correct - otherwise a pid of "-1" and a wrong sort-row number is returned which we don't want.
if ($lookForLiveVersion = BackendUtility::getLiveVersionOfRecord($table, $row['uid'], $sortColumn . ',pid,uid')) {
$row = $lookForLiveVersion;
// The top record was the record itself, so we return its current sorting value
if ($row['uid'] == $uid) {
return $row[$sortColumn];
}
// Fetch move placeholder, since it might point to a new page in the current workspace
if ($movePlaceholder = BackendUtility::getMovePlaceholder($table, $row['uid'], 'uid,pid,' . $sortColumn)) {
$row = $movePlaceholder;
// If the record sorting value < 1 we must resort all the records under this pid
if ($row[$sortColumn] < 1) {
$this->increaseSortingOfFollowingRecords($table, (int)$pid, 0);
// Lowest sorting value after full resorting is $sortIntervals
return $this->sortIntervals;
}
// If the record should be inserted after itself, keep the current sorting information:
if ((int)$row['uid'] === (int)$uid) {
$sortNumber = $row[$sortColumn];
} else {
$queryBuilder = $connectionPool->getQueryBuilderForTable($table);
$this->addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
// Sorting number between current top element and zero
return floor($row[$sortColumn] / 2);
}
// No records, so we choose the default value as sorting-number
return $this->sortIntervals;
}
$subResults = $queryBuilder
->select($sortColumn, 'pid', 'uid')
->from($table)
->where(
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter($row['pid'], \PDO::PARAM_INT)
),
$queryBuilder->expr()->gte(
$sortColumn,
$queryBuilder->createNamedParameter($row[$sortColumn], \PDO::PARAM_INT)
)
// Find and return first possible sorting value AFTER record with given uid ($pid)
// Fetches the record which is supposed to be the prev record
$row = $queryBuilder
->where($queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter(abs($pid), \PDO::PARAM_INT)
))
->execute()
->fetch();
// There is a previous record
if (!empty($row)) {
// Look, if the record UID happens to be an offline record. If so, find its live version.
// Offline uids will be used when a page is versionized as "branch" so this is when we must correct
// - otherwise a pid of "-1" and a wrong sort-row number is returned which we don't want.
if ($lookForLiveVersion = BackendUtility::getLiveVersionOfRecord($table, $row['uid'], $sortColumn . ',pid,uid')) {
$row = $lookForLiveVersion;
}
// Fetch move placeholder, since it might point to a new page in the current workspace
if ($movePlaceholder = BackendUtility::getMovePlaceholder($table, $row['uid'], 'uid,pid,' . $sortColumn)) {
$row = $movePlaceholder;
}
// If the record should be inserted after itself, keep the current sorting information:
if ((int)$row['uid'] === (int)$uid) {
$sortNumber = $row[$sortColumn];
} else {
$queryBuilder = $connectionPool->getQueryBuilderForTable($table);
$this->addDeleteRestriction($queryBuilder->getRestrictions()->removeAll());
$subResults = $queryBuilder
->select($sortColumn, 'pid', 'uid')
->from($table)
->where(
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter($row['pid'], \PDO::PARAM_INT)
),
$queryBuilder->expr()->gte(
$sortColumn,
$queryBuilder->createNamedParameter($row[$sortColumn], \PDO::PARAM_INT)
)
->orderBy($sortColumn, 'ASC')
->setMaxResults(2)
->execute()
->fetchAll();
// Fetches the next record in order to calculate the in-between sortNumber
// There was a record afterwards
if (count($subResults) === 2) {
// There was a record afterwards, fetch that
$subrow = array_pop($subResults);
// The sortNumber is found in between these values
$sortNumber = $row[$sortColumn] + floor(($subrow[$sortColumn] - $row[$sortColumn]) / 2);
// The sortNumber happened NOT to be between the two surrounding numbers, so we'll have to resort the list
if ($sortNumber <= $row[$sortColumn] || $sortNumber >= $subrow[$sortColumn]) {
// By this special param, resorting reserves and returns the sortnumber after the uid
$sortNumber = $this->resorting($table, $row['pid'], $sortColumn, $row['uid']);
}
} else {
// If after the last record in the list, we just add the sortInterval to the last sortvalue
)
->orderBy($sortColumn, 'ASC')
->addOrderBy('uid', 'DESC')
->setMaxResults(2)
->execute()
->fetchAll();
// Fetches the next record in order to calculate the in-between sortNumber
// There was a record afterwards
if (count($subResults) === 2) {
// There was a record afterwards, fetch that
$subrow = array_pop($subResults);
// The sortNumber is found in between these values
$sortNumber = $row[$sortColumn] + floor(($subrow[$sortColumn] - $row[$sortColumn]) / 2);
// The sortNumber happened NOT to be between the two surrounding numbers, so we'll have to resort the list
if ($sortNumber <= $row[$sortColumn] || $sortNumber >= $subrow[$sortColumn]) {
$this->increaseSortingOfFollowingRecords($table, (int)$row['pid'], (int)$row[$sortColumn]);
$sortNumber = $row[$sortColumn] + $this->sortIntervals;
}
} else {
// If after the last record in the list, we just add the sortInterval to the last sortvalue
$sortNumber = $row[$sortColumn] + $this->sortIntervals;
}
return ['pid' => $row['pid'], 'sortNumber' => $sortNumber];
}
if ($this->enableLogging) {
$propArr = $this->getRecordProperties($table, $uid);
// OK, don't insert $propArr['event_pid'] here...
$this->log($table, $uid, 4, 0, 1, 'Attempt to move record \'%s\' (%s) to after a non-existing record (uid=%s)', 1, [$propArr['header'], $table . ':' . $uid, abs($pid)], $propArr['pid']);
}
// There MUST be a page or else this cannot work
return false;
return ['pid' => $row['pid'], 'sortNumber' => $sortNumber];
}
return null;
if ($this->enableLogging) {
$propArr = $this->getRecordProperties($table, $uid);
// OK, don't insert $propArr['event_pid'] here...
$this->log($table, $uid, 4, 0, 1, 'Attempt to move record \'%s\' (%s) to after a non-existing record (uid=%s)', 1, [$propArr['header'], $table . ':' . $uid, abs($pid)], $propArr['pid']);
}
// There MUST be a previous record or else this cannot work
return false;
}
/**
......@@ -7497,9 +7525,12 @@ class DataHandler implements LoggerAwareInterface
* @return int|null If $return_SortNumber_After_This_Uid is set, will contain usable sorting number after that record if found (otherwise 0)
* @access private
* @see getSortNumber()
* @deprecated since core v9, will be removed with core v10
*/
public function resorting($table, $pid, $sortColumn, $return_SortNumber_After_This_Uid)
{
trigger_error('DataHandler->resorting() will be removed in TYPO3 v10.0, use the increaseSortingOfFollowingRecords() function instead.', E_USER_DEPRECATED);
$sortBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
if ($sortBy && $sortBy === $sortColumn) {
$returnVal = 0;
......@@ -7561,10 +7592,48 @@ class DataHandler implements LoggerAwareInterface
return null;
}
/**
* Increases sorting field value of all records with sorting higher than $sortingNumber
*
* Used internally by getSortNumber() to "make space" in sorting values when inserting new record
*
* @param string $table Table name
* @param int $pid Page Uid in which to resort records
* @param int $sortingValue All sorting numbers larger than this number will be shifted
* @see getSortNumber()
*/
protected function increaseSortingOfFollowingRecords(string $table, int $pid, int $sortingValue): void
{
$sortBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
if ($sortBy) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$queryBuilder
->update($table)
->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
->andWhere($queryBuilder->expr()->gt($sortBy, $sortingValue))
->set($sortBy, $queryBuilder->quoteIdentifier($sortBy) . ' + ' . $this->sortIntervals . ' + ' . $this->sortIntervals, false);
$deleteColumn = $GLOBALS['TCA'][$table]['ctrl']['delete'] ?? '';
if ($deleteColumn) {
$queryBuilder->andWhere($queryBuilder->expr()->eq($deleteColumn, 0));
}
$queryBuilder->execute();
}
}
/**
* Returning uid of previous localized record, if any, for tables with a "sortby" column
* Used when new localized records are created so that localized records are sorted in the same order as the default language records
*
* For a given record (A) uid (record we're translating) it finds first default language record (from the same colpos)
* with sorting smaller than given record (B).
* Then it fetches a translated version of record B and returns it's uid.
*
* If there is no record B, or it has no translation in given language, the record A uid is returned.
* The localized record will be placed the after record which uid is returned.
*
* @param string $table Table name
* @param int $uid Uid of default language record
* @param int $pid Pid of default language record
......@@ -7605,6 +7674,7 @@ class DataHandler implements LoggerAwareInterface
)
)
->orderBy($sortColumn, 'DESC')
->addOrderBy('uid', 'DESC')
->setMaxResults(1);
if ($table === 'tt_content') {
$queryBuilder
......
.. include:: ../../Includes.txt
==================================================
Deprecation: #85300 - DataHandler resorting method
==================================================
See :issue:`85300`
Description
===========
The public :php:`DataHandler->resorting` method has been marked as deprecated. It will be removed in v10.0.
Impact
======
Installations using this method will log deprecation message in the log.
Affected Installations
======================
All installations xclassing DataHandler, or having code calling mentioned method.
Migration
=========
Use newly introduced `increaseSortingOfFollowingRecords` method.
.. index:: Backend, FullyScanned, ext:core
......@@ -96,7 +96,7 @@ class Inline1nCest
$I->wait(1);
$I->see('lipsum', '#recordlist-tx_styleguide_inline_1n_child > div:nth-child(1) > table:nth-child(1) > tbody:nth-child(2) > tr:nth-child(1) > td:nth-child(2) > a');
$I->see('Fo Bar', '#recordlist-tx_styleguide_inline_1n_child > div:nth-child(1) > table:nth-child(1) > tbody:nth-child(2) > tr:nth-child(5) > td:nth-child(2) > a');
$I->see('Fo Bar', '#recordlist-tx_styleguide_inline_1n_child > div:nth-child(1) > table:nth-child(1) > tbody:nth-child(2) > tr:nth-child(6) > td:nth-child(2) > a');
}
/**
......@@ -116,8 +116,8 @@ class Inline1nCest
$I->wait(1);
$I->wantTo('Check new sorting');
$I->see('Fo Bar', '#recordlist-tx_styleguide_inline_1n_child > div:nth-child(1) > table:nth-child(1) > tbody:nth-child(2) > tr:nth-child(2) > td:nth-child(2) > a');
$I->see('lipsum', '#recordlist-tx_styleguide_inline_1n_child > div:nth-child(1) > table:nth-child(1) > tbody:nth-child(2) > tr:nth-child(3) > td:nth-child(2) > a');
$I->see('Fo Bar', '#recordlist-tx_styleguide_inline_1n_child > div:nth-child(1) > table:nth-child(1) > tbody:nth-child(2) > tr:nth-child(3) > td:nth-child(2) > a');
$I->see('lipsum', '#recordlist-tx_styleguide_inline_1n_child > div:nth-child(1) > table:nth-child(1) > tbody:nth-child(2) > tr:nth-child(4) > td:nth-child(2) > a');
}
/**
......
......@@ -5,25 +5,25 @@
,299,89,768,0,1,0,298,0,0,0,0,0,"[Translate to Dansk:] Regular Element #2",1,,,
"tx_irretutorial_1nff_hotel",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","offers"
,2,89,512,0,0,0,0,0,0,0,0,0,"Hotel #0",89,"pages",,0
,3,89,768,0,0,0,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2
,4,89,1536,0,0,0,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1
,5,89,1024,0,0,0,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,1
,2,89,1,0,0,0,0,0,0,0,0,0,"Hotel #0",89,"pages",,0
,3,89,1,0,0,0,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2
,4,89,514,0,0,0,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1
,5,89,1,0,0,0,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,1
,6,89,1,0,1,0,5,0,0,0,0,0,"[Translate to Dansk:] Hotel #1",299,"tt_content",,1
"tx_irretutorial_1nff_offer",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","prices"
,5,89,512,0,0,0,0,0,0,0,0,0,"Offer #1.1",3,"tx_irretutorial_1nff_hotel",,3
,6,89,1536,0,0,0,0,0,0,0,0,0,"Offer #1.2",3,"tx_irretutorial_1nff_hotel",,2
,7,89,768,0,0,0,0,0,0,0,0,0,"Offer #2.1",4,"tx_irretutorial_1nff_hotel",,1
,8,89,1024,0,0,0,0,0,0,0,0,0,"Offer #1.1",5,"tx_irretutorial_1nff_hotel",,1
,5,89,1,0,0,0,0,0,0,0,0,0,"Offer #1.1",3,"tx_irretutorial_1nff_hotel",,3
,6,89,514,0,0,0,0,0,0,0,0,0,"Offer #1.2",3,"tx_irretutorial_1nff_hotel",,2
,7,89,1,0,0,0,0,0,0,0,0,0,"Offer #2.1",4,"tx_irretutorial_1nff_hotel",,1
,8,89,1,0,0,0,0,0,0,0,0,0,"Offer #1.1",5,"tx_irretutorial_1nff_hotel",,1
,9,89,1,0,1,0,8,0,0,0,0,0,"[Translate to Dansk:] Offer #1.1",6,"tx_irretutorial_1nff_hotel",,1
"tx_irretutorial_1nff_price",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier",
,7,89,512,0,0,0,0,0,0,0,0,0,"Price #1.1.1",5,"tx_irretutorial_1nff_offer",,
,8,89,1792,0,0,0,0,0,0,0,0,0,"Price #1.1.2",5,"tx_irretutorial_1nff_offer",,
,9,89,2304,0,0,0,0,0,0,0,0,0,"Price #1.1.3",5,"tx_irretutorial_1nff_offer",,
,10,89,768,0,0,0,0,0,0,0,0,0,"Price #1.2.1",6,"tx_irretutorial_1nff_offer",,
,11,89,2048,0,0,0,0,0,0,0,0,0,"Price #1.2.2",6,"tx_irretutorial_1nff_offer",,
,12,89,1024,0,0,0,0,0,0,0,0,0,"Price #2.1.1",7,"tx_irretutorial_1nff_offer",,
,13,89,1280,0,0,0,0,0,0,0,0,0,"Price #1.1.1",8,"tx_irretutorial_1nff_offer",,
,7,89,1,0,0,0,0,0,0,0,0,0,"Price #1.1.1",5,"tx_irretutorial_1nff_offer",,
,8,89,514,0,0,0,0,0,0,0,0,0,"Price #1.1.2",5,"tx_irretutorial_1nff_offer",,
,9,89,515,0,0,0,0,0,0,0,0,0,"Price #1.1.3",5,"tx_irretutorial_1nff_offer",,
,10,89,1,0,0,0,0,0,0,0,0,0,"Price #1.2.1",6,"tx_irretutorial_1nff_offer",,
,11,89,514,0,0,0,0,0,0,0,0,0,"Price #1.2.2",6,"tx_irretutorial_1nff_offer",,
,12,89,1,0,0,0,0,0,0,0,0,0,"Price #2.1.1",7,"tx_irretutorial_1nff_offer",,
,13,89,1,0,0,0,0,0,0,0,0,0,"Price #1.1.1",8,"tx_irretutorial_1nff_offer",,
,14,89,1,0,1,0,13,0,0,0,0,0,"[Translate to Dansk:] Price #1.1.1",9,"tx_irretutorial_1nff_offer",,
......@@ -6,19 +6,19 @@
,300,89,192,0,1,299,299,0,0,0,0,0,"[Translate to Dansk:] Testing #1",1,,,
"tx_irretutorial_1nff_hotel",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","offers"
,2,89,512,0,0,0,0,0,0,0,0,0,"Hotel #0",89,"pages",,0
,3,89,768,0,0,0,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2
,4,89,1792,0,0,0,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1
,5,89,1024,0,0,0,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,1
,6,89,1280,0,0,0,0,0,0,0,0,0,"Hotel #1",299,"tt_content",,1
,2,89,1,0,0,0,0,0,0,0,0,0,"Hotel #0",89,"pages",,0
,3,89,1,0,0,0,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2
,4,89,514,0,0,0,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1
,5,89,1,0,0,0,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,1
,6,89,1,0,0,0,0,0,0,0,0,0,"Hotel #1",299,"tt_content",,1
,7,89,1,0,1,6,6,0,0,0,0,0,"[Translate to Dansk:] Hotel #1",300,"tt_content",,1
"tx_irretutorial_1nff_offer",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","prices"
,5,89,512,0,0,0,0,0,0,0,0,0,"Offer #1.1",3,"tx_irretutorial_1nff_hotel",,3
,6,89,1792,0,0,0,0,0,0,0,0,0,"Offer #1.2",3,"tx_irretutorial_1nff_hotel",,2
,7,89,768,0,0,0,0,0,0,0,0,0,"Offer #2.1",4,"tx_irretutorial_1nff_hotel",,1
,8,89,1024,0,0,0,0,0,0,0,0,0,"Offer #1.1",5,"tx_irretutorial_1nff_hotel",,1
,9,89,1280,0,0,0,0,0,0,0,0,0,"Offer #1",6,"tx_irretutorial_1nff_hotel",,0
,5,89,1,0,0,0,0,0,0,0,0,0,"Offer #1.1",3,"tx_irretutorial_1nff_hotel",,3
,6,89,514,0,0,0,0,0,0,0,0,0,"Offer #1.2",3,"tx_irretutorial_1nff_hotel",,2
,7,89,1,0,0,0,0,0,0,0,0,0,"Offer #2.1",4,"tx_irretutorial_1nff_hotel",,1
,8,89,1,0,0,0,0,0,0,0,0,0,"Offer #1.1",5,"tx_irretutorial_1nff_hotel",,1
,9,89,1,0,0,0,0,0,0,0,0,0,"Offer #1",6,"tx_irretutorial_1nff_hotel",,0
,10,89,1,0,1,9,9,0,0,0,0,0,"[Translate to Dansk:] Offer #1",7,"tx_irretutorial_1nff_hotel",,0
"tx_irretutorial_1nff_price",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier",
......
......@@ -14,11 +14,11 @@
,7,89,0,0,1,6,6,0,0,0,0,0,"[Translate to Dansk:] Hotel #1",300,"tt_content",,1
"tx_irretutorial_1nff_offer",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","prices"
,5,89,512,0,0,0,0,0,0,0,0,0,"Offer #1.1",3,"tx_irretutorial_1nff_hotel",,3
,6,89,1792,0,0,0,0,0,0,0,0,0,"Offer #1.2",3,"tx_irretutorial_1nff_hotel",,2
,7,89,768,0,0,0,0,0,0,0,0,0,"Offer #2.1",4,"tx_irretutorial_1nff_hotel",,1
,8,89,1024,0,0,0,0,0,0,0,0,0,"Offer #1.1",5,"tx_irretutorial_1nff_hotel",,1
,9,89,1280,0,0,0,0,0,0,0,0,0,"Offer #1",6,"tx_irretutorial_1nff_hotel",,0
,5,89,1,0,0,0,0,0,0,0,0,0,"Offer #1.1",3,"tx_irretutorial_1nff_hotel",,3
,6,89,514,0,0,0,0,0,0,0,0,0,"Offer #1.2",3,"tx_irretutorial_1nff_hotel",,2
,7,89,1,0,0,0,0,0,0,0,0,0,"Offer #2.1",4,"tx_irretutorial_1nff_hotel",,1
,8,89,1,0,0,0,0,0,0,0,0,0,"Offer #1.1",5,"tx_irretutorial_1nff_hotel",,1
,9,89,1,0,0,0,0,0,0,0,0,0,"Offer #1",6,"tx_irretutorial_1nff_hotel",,0
,10,89,1,0,1,9,9,0,0,0,0,0,"[Translate to Dansk:] Offer #1",7,"tx_irretutorial_1nff_hotel",,0
"tx_irretutorial_1nff_price",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier",
......
......@@ -15,10 +15,10 @@
,300,92,128,0,0,0,297,0,0,0,0,0,"Regular Element #1",2,,,
"tx_irretutorial_1nff_hotel",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","offers"
,2,89,512,0,0,0,0,0,0,0,0,0,"Hotel #0",89,"pages",,0
,3,89,1024,0,0,0,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2
,4,89,1536,0,0,0,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1
,5,89,1280,0,0,0,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,1
,2,89,1,0,0,0,0,0,0,0,0,0,"Hotel #0",89,"pages",,0
,3,89,1,0,0,0,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2
,4,89,514,0,0,0,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1
,5,89,1,0,0,0,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,1
,6,89,1,0,1,2,2,0,0,0,0,0,"[Translate to Dansk:] Hotel #0",91,"pages",,0
,7,92,1,0,0,0,2,0,0,0,0,0,"Hotel #0",92,"pages",,0
,8,92,1,0,1,7,6,0,0,0,0,0,"[Translate to Dansk:] Hotel #0",93,"pages",,0
......
......@@ -16,9 +16,9 @@
"tx_irretutorial_1nff_hotel",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","offers"
,2,89,1,0,0,0,0,0,0,0,0,0,"Hotel #0",89,"pages",,0
,3,89,1280,0,0,0,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2
,4,89,1792,0,0,0,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1
,5,89,1536,0,0,0,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,1
,3,89,513,0,0,0,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2
,4,89,1026,0,0,0,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1
,5,89,513,0,0,0,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,1
,6,89,1,0,0,0,2,0,0,0,0,0,"Hotel #0",91,"pages",,0
,7,89,2,0,0,0,0,0,0,0,0,0,"Hotel #007",89,"pages",,0
,8,89,2,0,0,0,0,0,0,0,0,0,"Hotel #007",91,"pages",,0
......
......@@ -12,9 +12,9 @@
"tx_irretutorial_1nff_hotel",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","offers"
,2,89,1,0,0,0,0,0,0,0,0,0,"Hotel #0",89,"pages",,0
,3,89,1024,0,0,0,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2
,4,89,1536,0,0,0,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1
,5,89,1280,0,0,0,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,1
,3,89,513,0,0,0,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2
,4,89,1538,0,0,0,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1
,5,89,513,0,0,0,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,1
,6,89,1,1,0,0,2,0,0,0,0,0,"Hotel #0",91,"pages",,0
,7,89,1,0,0,0,2,0,0,0,0,0,"Hotel #0",91,"pages",,0
,8,89,2,0,0,0,0,0,0,0,0,0,"Hotel #007",89,"pages",,0
......
......@@ -12,9 +12,9 @@
"tx_irretutorial_1nff_hotel",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","offers"