[FEATURE] Add PageRepository::getPagesOverlay() 54/36054/5
authorChristian Weiske <cweiske@cweiske.de>
Fri, 16 Jan 2015 21:25:16 +0000 (22:25 +0100)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Mon, 19 Jan 2015 12:49:03 +0000 (13:49 +0100)
PageRepository::getMenu() calls getPageOverlay() for each single page
in a menu, causing a lot of queries to the SQL database.

This patch introduces getPagesOverlay() which is able to fetch multiple
page record overlays at once.
getMenu() is changed to make use of the new method.

It reduces the number of SQL requests on an uncached "features" page
from the introduction package from 721 to 648.

Change-Id: I014c08b6b63bcee99f94567240f1835077f60f0b
Resolves: #64258
Releases: master
Reviewed-on: http://review.typo3.org/36054
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
typo3/sysext/frontend/Classes/Page/PageRepository.php
typo3/sysext/frontend/Tests/Functional/Fixtures/pages.xml
typo3/sysext/frontend/Tests/Functional/Page/PageRepositoryTest.php

index cc14f3c..a09e86c 100644 (file)
@@ -313,33 +313,65 @@ class PageRepository {
         * @return array Page row which is overlayed with language_overlay record (or the overlay record alone)
         */
        public function getPageOverlay($pageInput, $lUid = -1) {
+               $rows = $this->getPagesOverlay(array($pageInput), $lUid);
+               // Always an array in return
+               return count($rows) ? $rows[0] : array();
+       }
+
+       /**
+        * Returns the relevant page overlay record fields
+        *
+        * @param array $pagesInput Array of integers or array of arrays. If each value is an integer, it's the pids of the pageOverlay records and thus the page overlay records are returned. If each value is an array, it's page-records and based on this page records the language records are found and OVERLAYED before the page records are returned.
+        * @param int $lUid Language UID if you want to set an alternative value to $this->sys_language_uid which is default. Should be >=0
+        * @throws \UnexpectedValueException
+        * @return array Page rows which are overlayed with language_overlay record.
+        *                         If the input was an array of integers, missing records are not
+        *                         included. If the input were page rows, untranslated pages
+        *                         are returned.
+        */
+       public function getPagesOverlay(array $pagesInput, $lUid = -1) {
+               if (count($pagesInput) == 0) {
+                       return array();
+               }
                // Initialize:
                if ($lUid < 0) {
                        $lUid = $this->sys_language_uid;
                }
                $row = NULL;
                if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPageOverlay'])) {
-                       foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPageOverlay'] as $classRef) {
-                               $hookObject = GeneralUtility::getUserObj($classRef);
-                               if (!$hookObject instanceof \TYPO3\CMS\Frontend\Page\PageRepositoryGetPageOverlayHookInterface) {
-                                       throw new \UnexpectedValueException('$hookObject must implement interface ' . \TYPO3\CMS\Frontend\Page\PageRepositoryGetPageOverlayHookInterface::class, 1269878881);
+                       foreach ($pagesInput as $origPage) {
+                               foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPageOverlay'] as $classRef) {
+                                       $hookObject = GeneralUtility::makeInstance($classRef);
+                                       if (!$hookObject instanceof \TYPO3\CMS\Frontend\Page\PageRepositoryGetPageOverlayHookInterface) {
+                                               throw new \UnexpectedValueException('$hookObject must implement interface ' . \TYPO3\CMS\Frontend\Page\PageRepositoryGetPageOverlayHookInterface::class, 1269878881);
+                                       }
+                                       $hookObject->getPageOverlay_preProcess($origPage, $lUid, $this);
                                }
-                               $hookObject->getPageOverlay_preProcess($pageInput, $lUid, $this);
                        }
                }
                // If language UID is different from zero, do overlay:
                if ($lUid) {
                        $fieldArr = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['pageOverlayFields'], TRUE);
-                       if (is_array($pageInput)) {
-                               // Was the whole record
-                               $page_id = $pageInput['uid'];
-                               // Make sure that only fields which exist in the incoming record are overlaid!
-                               $fieldArr = array_intersect($fieldArr, array_keys($pageInput));
-                       } else {
-                               // Was the id
-                               $page_id = $pageInput;
+                       $page_ids = array();
+
+                       $origPage = reset($pagesInput);
+                       if (is_array($origPage)) {
+                               // Make sure that only fields which exist in the first incoming record are overlaid!
+                               $fieldArr = array_intersect($fieldArr, array_keys($origPage));
+                       }
+                       foreach ($pagesInput as $origPage) {
+                               if (is_array($origPage)) {
+                                       // Was the whole record
+                                       $page_ids[] = $origPage['uid'];
+                               } else {
+                                       // Was the id
+                                       $page_ids[] = $origPage;
+                               }
                        }
                        if (count($fieldArr)) {
+                               if (!in_array('pid', $fieldArr)) {
+                                       $fieldArr[] = 'pid';
+                               }
                                // NOTE to enabledFields('pages_language_overlay'):
                                // Currently the showHiddenRecords of TSFE set will allow
                                // pages_language_overlay records to be selected as they are
@@ -347,38 +379,53 @@ class PageRepository {
                                // However you may argue that the showHiddenField flag should
                                // determine this. But that's not how it's done right now.
                                // Selecting overlay record:
-                               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(implode(',', $fieldArr), 'pages_language_overlay', 'pid=' . (int)$page_id . '
-                                                               AND sys_language_uid=' . (int)$lUid . $this->enableFields('pages_language_overlay'), '', '', '1');
-                               $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
-                               $GLOBALS['TYPO3_DB']->sql_free_result($res);
-                               $this->versionOL('pages_language_overlay', $row);
-                               if (is_array($row)) {
-                                       $row['_PAGES_OVERLAY'] = TRUE;
-                                       $row['_PAGES_OVERLAY_UID'] = $row['uid'];
-                                       $row['_PAGES_OVERLAY_LANGUAGE'] = $lUid;
-                                       // Unset vital fields that are NOT allowed to be overlaid:
-                                       unset($row['uid']);
-                                       unset($row['pid']);
+                               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
+                                       implode(',', $fieldArr),
+                                       'pages_language_overlay',
+                                       'pid IN(' . implode(',', $GLOBALS['TYPO3_DB']->cleanIntArray($page_ids)) . ')'
+                                               . ' AND sys_language_uid=' . (int)$lUid . $this->enableFields('pages_language_overlay'),
+                                       '',
+                                       ''
+                               );
+                               $overlays = array();
+                               while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
+                                       $this->versionOL('pages_language_overlay', $row);
+                                       if (is_array($row)) {
+                                               $row['_PAGES_OVERLAY'] = TRUE;
+                                               $row['_PAGES_OVERLAY_UID'] = $row['uid'];
+                                               $row['_PAGES_OVERLAY_LANGUAGE'] = $lUid;
+                                               $origUid = $row['pid'];
+                                               // Unset vital fields that are NOT allowed to be overlaid:
+                                               unset($row['uid']);
+                                               unset($row['pid']);
+                                               $overlays[$origUid] = $row;
+                                       }
                                }
+                               $GLOBALS['TYPO3_DB']->sql_free_result($res);
                        }
                }
                // Create output:
-               if (is_array($pageInput)) {
-                       if (is_array($row)) {
-                               // Overwrite the original field with the overlay
-                               foreach ($row as $fieldName => $fieldValue) {
-                                       if ($fieldName !== 'uid' && $fieldName !== 'pid') {
-                                               if ($this->shouldFieldBeOverlaid('pages_language_overlay', $fieldName, $fieldValue)) {
-                                                       $pageInput[$fieldName] = $fieldValue;
+               $pagesOutput = array();
+               foreach ($pagesInput as $key => $origPage) {
+                       if (is_array($origPage)) {
+                               $pagesOutput[$key] = $origPage;
+                               if (isset($overlays[$origPage['uid']])) {
+                                       // Overwrite the original field with the overlay
+                                       foreach ($overlays[$origPage['uid']] as $fieldName => $fieldValue) {
+                                               if ($fieldName !== 'uid' && $fieldName !== 'pid') {
+                                                       if ($this->shouldFieldBeOverlaid('pages_language_overlay', $fieldName, $fieldValue)) {
+                                                               $pagesOutput[$key][$fieldName] = $fieldValue;
+                                                       }
                                                }
                                        }
                                }
+                       } else {
+                               if (isset($overlays[$origPage])) {
+                                       $pagesOutput[$key] = $overlays[$origPage];
+                               }
                        }
-                       return $pageInput;
-               } else {
-                       // Always an array in return
-                       return is_array($row) ? $row : array();
                }
+               return $pagesOutput;
        }
 
        /**
@@ -544,14 +591,14 @@ class PageRepository {
                                        // Neither shortcut target nor mode is set. Remove the page from the menu.
                                        unset($row);
                                }
-                               // Add to output array after overlaying language:
                                if (is_array($row)) {
-                                       $output[$origUid] = $this->getPageOverlay($row);
+                                       $output[$origUid] = $row;
                                }
                        }
                }
                $GLOBALS['TYPO3_DB']->sql_free_result($res);
-               return $output;
+        // Finally load language overlays
+               return $this->getPagesOverlay($output);
        }
 
        /**
index 3184be4..b149d2b 100644 (file)
         <deleted>0</deleted>
         <perms_everybody>15</perms_everybody>
     </pages>
+
+    <pages_language_overlay>
+        <uid>901</uid>
+        <pid>1</pid>
+        <sys_language_uid>1</sys_language_uid>
+        <title>Wurzel 1</title>
+        <deleted>0</deleted>
+    </pages_language_overlay>
+    <pages_language_overlay>
+        <uid>902</uid>
+        <pid>2</pid>
+        <sys_language_uid>1</sys_language_uid>
+        <title>Attrappe 1-2</title>
+        <deleted>0</deleted>
+    </pages_language_overlay>
+    <pages_language_overlay>
+        <uid>903</uid>
+        <pid>3</pid>
+        <sys_language_uid>1</sys_language_uid>
+        <title>Attrappe 1-3</title>
+        <deleted>0</deleted>
+    </pages_language_overlay>
+    <pages_language_overlay>
+        <uid>904</uid>
+        <pid>5</pid>
+        <sys_language_uid>1</sys_language_uid>
+        <title>Attrappe 1-2-5</title>
+        <deleted>0</deleted>
+    </pages_language_overlay>
+    <pages_language_overlay>
+        <uid>905</uid>
+        <pid>6</pid>
+        <sys_language_uid>1</sys_language_uid>
+        <title>Attrappe 1-2-6</title>
+        <deleted>0</deleted>
+    </pages_language_overlay>
+    <pages_language_overlay>
+        <uid>906</uid>
+        <pid>9</pid>
+        <sys_language_uid>1</sys_language_uid>
+        <title>Attrappe 1-3-9</title>
+        <deleted>0</deleted>
+    </pages_language_overlay>
 </dataset>
 
index 1102e17..41b01ce 100644 (file)
@@ -20,20 +20,21 @@ use TYPO3\CMS\Frontend\Page\PageRepository;
  * Test case
  */
 class PageRepositoryTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
-       /**
-        * @var string[]
-        */
-       protected $coreExtensionsToLoad = array();
+
+       protected $coreExtensionsToLoad = array('frontend');
+
+       public function setUp() {
+               parent::setUp();
+               $this->importDataSet(__DIR__ . '/../Fixtures/pages.xml');
+               $this->pagerepo = new \TYPO3\CMS\Frontend\Page\PageRepository();
+               $this->pagerepo->init(false);
+       }
 
        /**
         * @test
         */
        public function getMenuSingleUidRoot() {
-               $this->importDataSet(__DIR__ . '/../Fixtures/pages.xml');
-               $pageRepository = new PageRepository();
-               $pageRepository->init(false);
-
-               $rows = $pageRepository->getMenu(1, 'uid, title');
+               $rows = $this->pagerepo->getMenu(1, 'uid, title');
                $this->assertArrayHasKey(2, $rows);
                $this->assertArrayHasKey(3, $rows);
                $this->assertArrayHasKey(4, $rows);
@@ -44,11 +45,7 @@ class PageRepositoryTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
         * @test
         */
        public function getMenuSingleUidSubpage() {
-               $this->importDataSet(__DIR__ . '/../Fixtures/pages.xml');
-               $pageRepository = new PageRepository();
-               $pageRepository->init(false);
-
-               $rows = $pageRepository->getMenu(2, 'uid, title');
+               $rows = $this->pagerepo->getMenu(2, 'uid, title');
                $this->assertArrayHasKey(5, $rows);
                $this->assertArrayHasKey(6, $rows);
                $this->assertArrayHasKey(7, $rows);
@@ -59,11 +56,7 @@ class PageRepositoryTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
         * @test
         */
        public function getMenuMulipleUid() {
-               $this->importDataSet(__DIR__ . '/../Fixtures/pages.xml');
-               $pageRepository = new PageRepository();
-               $pageRepository->init(false);
-
-               $rows = $pageRepository->getMenu(array(2, 3), 'uid, title');
+               $rows = $this->pagerepo->getMenu(array(2, 3), 'uid, title');
                $this->assertArrayHasKey(5, $rows);
                $this->assertArrayHasKey(6, $rows);
                $this->assertArrayHasKey(7, $rows);
@@ -71,4 +64,217 @@ class PageRepositoryTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
                $this->assertArrayHasKey(9, $rows);
                $this->assertCount(5, $rows);
        }
+
+       /**
+        * @test
+        */
+       public function getMenuPageOverlay() {
+               $this->pagerepo->sys_language_uid = 1;
+
+               $rows = $this->pagerepo->getMenu(array(2, 3), 'uid, title');
+               $this->assertEquals('Attrappe 1-2-5', $rows[5]['title']);
+               $this->assertEquals('Attrappe 1-2-6', $rows[6]['title']);
+               $this->assertEquals('Dummy 1-2-7', $rows[7]['title']);
+               $this->assertEquals('Dummy 1-3-8', $rows[8]['title']);
+               $this->assertEquals('Attrappe 1-3-9', $rows[9]['title']);
+               $this->assertCount(5, $rows);
+       }
+
+       /**
+        * @test
+        */
+       public function getPageOverlayById() {
+               $row = $this->pagerepo->getPageOverlay(1, 1);
+               $this->assertOverlayRow($row);
+               $this->assertEquals('Wurzel 1', $row['title']);
+               $this->assertEquals('901', $row['_PAGES_OVERLAY_UID']);
+               $this->assertEquals(1, $row['_PAGES_OVERLAY_LANGUAGE']);
+       }
+
+       /**
+        * @test
+        */
+       public function getPageOverlayByIdWithoutTranslation() {
+               $row = $this->pagerepo->getPageOverlay(4, 1);
+               $this->assertInternalType('array', $row);
+               $this->assertCount(0, $row);
+       }
+
+       /**
+        * @test
+        */
+       public function getPageOverlayByRow() {
+               $orig = $this->pagerepo->getPage(1);
+               $row = $this->pagerepo->getPageOverlay($orig, 1);
+               $this->assertOverlayRow($row);
+               $this->assertEquals(1, $row['uid']);
+               $this->assertEquals('Wurzel 1', $row['title']);
+               $this->assertEquals('901', $row['_PAGES_OVERLAY_UID']);
+               $this->assertEquals(1, $row['_PAGES_OVERLAY_LANGUAGE']);
+       }
+
+       /**
+        * @test
+        */
+       public function getPageOverlayByRowWithoutTranslation() {
+               $orig = $this->pagerepo->getPage(4);
+               $row = $this->pagerepo->getPageOverlay($orig, 1);
+               $this->assertInternalType('array', $row);
+               $this->assertEquals(4, $row['uid']);
+               $this->assertEquals('Dummy 1-4', $row['title']);//original title
+       }
+
+       /**
+        * @test
+        */
+       public function getPagesOverlayByIdSingle() {
+               $this->pagerepo->sys_language_uid = 1;
+               $rows = $this->pagerepo->getPagesOverlay(array(1));
+               $this->assertInternalType('array', $rows);
+               $this->assertCount(1, $rows);
+               $this->assertArrayHasKey(0, $rows);
+
+               $row = $rows[0];
+               $this->assertOverlayRow($row);
+               $this->assertEquals('Wurzel 1', $row['title']);
+               $this->assertEquals('901', $row['_PAGES_OVERLAY_UID']);
+               $this->assertEquals(1, $row['_PAGES_OVERLAY_LANGUAGE']);
+       }
+
+       /**
+        * @test
+        */
+       public function getPagesOverlayByIdMultiple() {
+               $this->pagerepo->sys_language_uid = 1;
+               $rows = $this->pagerepo->getPagesOverlay(array(1, 5));
+               $this->assertInternalType('array', $rows);
+               $this->assertCount(2, $rows);
+               $this->assertArrayHasKey(0, $rows);
+               $this->assertArrayHasKey(1, $rows);
+
+               $row = $rows[0];
+               $this->assertOverlayRow($row);
+               $this->assertEquals('Wurzel 1', $row['title']);
+               $this->assertEquals('901', $row['_PAGES_OVERLAY_UID']);
+               $this->assertEquals(1, $row['_PAGES_OVERLAY_LANGUAGE']);
+
+               $row = $rows[1];
+               $this->assertOverlayRow($row);
+               $this->assertEquals('Attrappe 1-2-5', $row['title']);
+               $this->assertEquals('904', $row['_PAGES_OVERLAY_UID']);
+               $this->assertEquals(1, $row['_PAGES_OVERLAY_LANGUAGE']);
+       }
+
+       /**
+        * @test
+        */
+       public function getPagesOverlayByIdMultipleSomeNotOverlaid() {
+               $this->pagerepo->sys_language_uid = 1;
+               $rows = $this->pagerepo->getPagesOverlay(array(1, 4, 5, 8));
+               $this->assertInternalType('array', $rows);
+               $this->assertCount(2, $rows);
+               $this->assertArrayHasKey(0, $rows);
+               $this->assertArrayHasKey(2, $rows);
+
+               $row = $rows[0];
+               $this->assertOverlayRow($row);
+               $this->assertEquals('Wurzel 1', $row['title']);
+
+               $row = $rows[2];
+               $this->assertOverlayRow($row);
+               $this->assertEquals('Attrappe 1-2-5', $row['title']);
+       }
+
+       /**
+        * @test
+        */
+       public function getPagesOverlayByRowSingle() {
+               $origRow = $this->pagerepo->getPage(1);
+
+               $this->pagerepo->sys_language_uid = 1;
+               $rows = $this->pagerepo->getPagesOverlay(array($origRow));
+               $this->assertInternalType('array', $rows);
+               $this->assertCount(1, $rows);
+               $this->assertArrayHasKey(0, $rows);
+
+               $row = $rows[0];
+               $this->assertOverlayRow($row);
+               $this->assertEquals('Wurzel 1', $row['title']);
+               $this->assertEquals('901', $row['_PAGES_OVERLAY_UID']);
+               $this->assertEquals(1, $row['_PAGES_OVERLAY_LANGUAGE']);
+       }
+
+       /**
+        * @test
+        */
+       public function getPagesOverlayByRowMultiple() {
+               $orig1 = $this->pagerepo->getPage(1);
+               $orig2 = $this->pagerepo->getPage(5);
+
+               $this->pagerepo->sys_language_uid = 1;
+               $rows = $this->pagerepo->getPagesOverlay(array(1 => $orig1, 5 => $orig2));
+               $this->assertInternalType('array', $rows);
+               $this->assertCount(2, $rows);
+               $this->assertArrayHasKey(1, $rows);
+               $this->assertArrayHasKey(5, $rows);
+
+               $row = $rows[1];
+               $this->assertOverlayRow($row);
+               $this->assertEquals('Wurzel 1', $row['title']);
+               $this->assertEquals('901', $row['_PAGES_OVERLAY_UID']);
+               $this->assertEquals(1, $row['_PAGES_OVERLAY_LANGUAGE']);
+
+               $row = $rows[5];
+               $this->assertOverlayRow($row);
+               $this->assertEquals('Attrappe 1-2-5', $row['title']);
+               $this->assertEquals('904', $row['_PAGES_OVERLAY_UID']);
+               $this->assertEquals(1, $row['_PAGES_OVERLAY_LANGUAGE']);
+       }
+
+       /**
+        * @test
+        */
+       public function getPagesOverlayByRowMultipleSomeNotOverlaid() {
+               $orig1 = $this->pagerepo->getPage(1);
+               $orig2 = $this->pagerepo->getPage(7);
+               $orig3 = $this->pagerepo->getPage(9);
+
+               $this->pagerepo->sys_language_uid = 1;
+               $rows = $this->pagerepo->getPagesOverlay(array($orig1, $orig2, $orig3));
+               $this->assertInternalType('array', $rows);
+               $this->assertCount(3, $rows);
+               $this->assertArrayHasKey(0, $rows);
+               $this->assertArrayHasKey(1, $rows);
+               $this->assertArrayHasKey(2, $rows);
+
+               $row = $rows[0];
+               $this->assertOverlayRow($row);
+               $this->assertEquals('Wurzel 1', $row['title']);
+
+               $row = $rows[1];
+               $this->assertNotOverlayRow($row);
+               $this->assertEquals('Dummy 1-2-7', $row['title']);
+
+               $row = $rows[2];
+               $this->assertOverlayRow($row);
+               $this->assertEquals('Attrappe 1-3-9', $row['title']);
+       }
+
+       protected function assertOverlayRow($row) {
+               $this->assertInternalType('array', $row);
+
+               $this->assertArrayHasKey('_PAGES_OVERLAY', $row);
+               $this->assertArrayHasKey('_PAGES_OVERLAY_UID', $row);
+               $this->assertArrayHasKey('_PAGES_OVERLAY_LANGUAGE', $row);
+
+               $this->assertTrue($row['_PAGES_OVERLAY']);
+       }
+
+       protected function assertNotOverlayRow($row) {
+               $this->assertInternalType('array', $row);
+
+               $this->assertFalse(isset($row['_PAGES_OVERLAY']));
+               $this->assertFalse(isset($row['_PAGES_OVERLAY_UID']));
+               $this->assertFalse(isset($row['_PAGES_OVERLAY_LANGUAGE']));
+       }
 }