[FEATURE] Categories-based tt_content menu 05/23905/12
authorFrancois Suter <francois@typo3.org>
Thu, 5 Sep 2013 13:06:12 +0000 (15:06 +0200)
committerMarkus Klein <klein.t3@mfc-linz.at>
Tue, 19 Nov 2013 14:37:06 +0000 (15:37 +0100)
Provide a new menu/sitemap type for displaying list
of categorized content elements. The default rendering
simply provides links to the parent page with
anchor pointing to the specific content element.
Refactor the RECORDS object.

Resolves: #52137
Documentation: #52920
Releases: 6.2
Change-Id: I59c9eeb63148eaf12d8810fd7e64628e3f2d61aa
Reviewed-on: https://review.typo3.org/23905
Reviewed-by: Markus Klein
Tested-by: Markus Klein
12 files changed:
typo3/sysext/cms/locallang_ttc.xlf
typo3/sysext/core/Classes/Category/CategoryRegistry.php
typo3/sysext/core/Classes/Database/RelationHandler.php
typo3/sysext/css_styled_content/static/setup.txt
typo3/sysext/css_styled_content/static/v4.5/setup.txt
typo3/sysext/css_styled_content/static/v4.6/setup.txt
typo3/sysext/css_styled_content/static/v4.7/setup.txt
typo3/sysext/css_styled_content/static/v6.0/setup.txt
typo3/sysext/css_styled_content/static/v6.1/setup.txt
typo3/sysext/frontend/Classes/Category/Collection/CategoryCollection.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/ContentObject/RecordsContentObject.php
typo3/sysext/frontend/Configuration/TCA/tt_content.php

index 1af2f98..4ef1ec0 100644 (file)
                        <trans-unit id="menu_type.I.9" xml:space="preserve">
                                <source>Pages for selected categories</source>
                        </trans-unit>
+                       <trans-unit id="menu_type.I.categorized_content" xml:space="preserve">
+                               <source>Content elements for selected categories</source>
+                       </trans-unit>
                        <trans-unit id="list_type" xml:space="preserve">
                                <source>Plugin:</source>
                        </trans-unit>
index 3ca3f34..1cd1407 100644 (file)
@@ -145,6 +145,8 @@ class CategoryRegistry implements \TYPO3\CMS\Core\SingletonInterface {
                // Define the table being looked up from the type of menu
                if ($configuration['row']['menu_type'] == 9) {
                        $table = 'pages';
+               } elseif ($configuration['row']['menu_type'] == 'categorized_content') {
+                       $table = 'tt_content';
                }
                // Return early if no table is defined
                if (empty($table)) {
index e37dabd..c5c0e1b 100644 (file)
@@ -163,6 +163,12 @@ class RelationHandler {
        protected $updateReferenceIndex = TRUE;
 
        /**
+        * Results of
+        * @var array
+        */
+       public $results = array();
+
+       /**
         * Initialization of the class.
         *
         * @param string $itemlist List of group/select items
@@ -837,7 +843,7 @@ class RelationHandler {
         * Reads all records from internal tableArray into the internal ->results array where keys are table names and for each table, records are stored with uids as their keys.
         * If $this->fetchAllFields is false you can save a little memory since only uid,pid and a few other fields are selected.
         *
-        * @return      void
+        * @return      array
         * @todo Define visibility
         */
        public function getFromDB() {
index 99e756b..50e9fe9 100644 (file)
@@ -2108,6 +2108,22 @@ tt_content.menu {
                                outerWrap = <ul class="csc-menu csc-menu-9">|</ul>
                        }
                }
+
+               # Menu of categorized content elements
+               categorized_content = RECORDS
+               categorized_content {
+                       categories.field = selected_categories
+                       categories.relation.field = category_field
+                       tables = tt_content
+                       conf.tt_content = TEXT
+                       conf.tt_content {
+                               field = header
+                               typolink.parameter = {field:pid}#{field:uid}
+                               typolink.parameter.insertData = 1
+                               wrap = <li>|</li>
+                       }
+                       wrap = <ul>|</ul>
+               }
        }
 
        20.stdWrap {
index be4f9bc..e788ca0 100644 (file)
@@ -1541,6 +1541,22 @@ tt_content.menu {
                                outerWrap = <ul class="csc-menu csc-menu-9">|</ul>
                        }
                }
+
+               # Menu of categorized content elements
+               categorized_content = RECORDS
+               categorized_content {
+                       categories.field = selected_categories
+                       categories.relation.field = category_field
+                       tables = tt_content
+                       conf.tt_content = TEXT
+                       conf.tt_content {
+                               field = header
+                               typolink.parameter = {field:pid}#{field:uid}
+                               typolink.parameter.insertData = 1
+                               wrap = <li>|</li>
+                       }
+                       wrap = <ul>|</ul>
+               }
        }
 
        20.stdWrap {
index c510bdb..0107fa7 100644 (file)
@@ -1491,6 +1491,22 @@ tt_content.menu {
                                outerWrap = <ul class="csc-menu csc-menu-9">|</ul>
                        }
                }
+
+               # Menu of categorized content elements
+               categorized_content = RECORDS
+               categorized_content {
+                       categories.field = selected_categories
+                       categories.relation.field = category_field
+                       tables = tt_content
+                       conf.tt_content = TEXT
+                       conf.tt_content {
+                               field = header
+                               typolink.parameter = {field:pid}#{field:uid}
+                               typolink.parameter.insertData = 1
+                               wrap = <li>|</li>
+                       }
+                       wrap = <ul>|</ul>
+               }
        }
 
        20.stdWrap {
index 2475b8e..4f7353d 100644 (file)
@@ -1970,6 +1970,22 @@ tt_content.menu {
                                outerWrap = <ul class="csc-menu csc-menu-9">|</ul>
                        }
                }
+
+               # Menu of categorized content elements
+               categorized_content = RECORDS
+               categorized_content {
+                       categories.field = selected_categories
+                       categories.relation.field = category_field
+                       tables = tt_content
+                       conf.tt_content = TEXT
+                       conf.tt_content {
+                               field = header
+                               typolink.parameter = {field:pid}#{field:uid}
+                               typolink.parameter.insertData = 1
+                               wrap = <li>|</li>
+                       }
+                       wrap = <ul>|</ul>
+               }
        }
 
        20.stdWrap {
index f475716..9bd5f1f 100644 (file)
@@ -1954,6 +1954,22 @@ tt_content.menu {
                                outerWrap = <ul class="csc-menu csc-menu-9">|</ul>
                        }
                }
+
+               # Menu of categorized content elements
+               categorized_content = RECORDS
+               categorized_content {
+                       categories.field = selected_categories
+                       categories.relation.field = category_field
+                       tables = tt_content
+                       conf.tt_content = TEXT
+                       conf.tt_content {
+                               field = header
+                               typolink.parameter = {field:pid}#{field:uid}
+                               typolink.parameter.insertData = 1
+                               wrap = <li>|</li>
+                       }
+                       wrap = <ul>|</ul>
+               }
        }
 
        20.stdWrap {
index 963d549..ac8b69a 100644 (file)
@@ -1955,6 +1955,22 @@ tt_content.menu {
                                outerWrap = <ul class="csc-menu csc-menu-9">|</ul>
                        }
                }
+
+               # Menu of categorized content elements
+               categorized_content = RECORDS
+               categorized_content {
+                       categories.field = selected_categories
+                       categories.relation.field = category_field
+                       tables = tt_content
+                       conf.tt_content = TEXT
+                       conf.tt_content {
+                               field = header
+                               typolink.parameter = {field:pid}#{field:uid}
+                               typolink.parameter.insertData = 1
+                               wrap = <li>|</li>
+                       }
+                       wrap = <ul>|</ul>
+               }
        }
 
        20.stdWrap {
diff --git a/typo3/sysext/frontend/Classes/Category/Collection/CategoryCollection.php b/typo3/sysext/frontend/Classes/Category/Collection/CategoryCollection.php
new file mode 100644 (file)
index 0000000..ca4e7d7
--- /dev/null
@@ -0,0 +1,175 @@
+<?php
+namespace TYPO3\CMS\Frontend\Category\Collection;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2013 Francois Suter <francois.suter@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Extend category collection for the frontend, to collect related records
+ * while respecting language, enable fields, etc.
+ *
+ * @author Francois Suter <francois.suter@typo3.org>
+ */
+class CategoryCollection extends \TYPO3\CMS\Core\Category\Collection\CategoryCollection {
+
+       /**
+        * Creates a new collection objects and reconstitutes the
+        * given database record to the new object.
+        *
+        * Overrides the parent method to create a *frontend* category collection.
+        *
+        * @param array $collectionRecord Database record
+        * @param boolean $fillItems Populates the entries directly on load, might be bad for memory on large collections
+        * @return \TYPO3\CMS\Frontend\Category\Collection\CategoryCollection
+        */
+       static public function create(array $collectionRecord, $fillItems = FALSE) {
+               /** @var $collection \TYPO3\CMS\Frontend\Category\Collection\CategoryCollection */
+               $collection = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
+                       'TYPO3\\CMS\\Frontend\\Category\\Collection\\CategoryCollection',
+                       $collectionRecord['table_name'],
+                       $collectionRecord['field_name']
+               );
+               $collection->fromArray($collectionRecord);
+               if ($fillItems) {
+                       $collection->loadContents();
+               }
+               return $collection;
+       }
+
+       /**
+        * Loads the collection with the given id from persistence
+        * For memory reasons, only data for the collection itself is loaded by default.
+        * Entries can be loaded on first access or straightaway using the $fillItems flag.
+        *
+        * Overrides the parent method because of the call to "self::create()" which otherwise calls up
+        * \TYPO3\CMS\Core\Category\Collection\CategoryCollection
+        *
+        * @param integer $id Id of database record to be loaded
+        * @param boolean $fillItems Populates the entries directly on load, might be bad for memory on large collections
+        * @param string $tableName the table name
+        * @param string $fieldName Name of the categories relation field
+        * @return \TYPO3\CMS\Core\Collection\CollectionInterface
+        */
+       static public function load($id, $fillItems = FALSE, $tableName = '', $fieldName = '') {
+               $collectionRecord = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
+                       '*',
+                       static::$storageTableName,
+                       'uid = ' . intval($id) . self::getFrontendObject()->sys_page->enableFields(static::$storageTableName)
+               );
+               $collectionRecord['table_name'] = $tableName;
+               $collectionRecord['field_name'] = $fieldName;
+               return self::create($collectionRecord, $fillItems);
+       }
+
+       /**
+        * Gets the collected records in this collection, by
+        * looking up the MM relations of this record to the
+        * table name defined in the local field 'table_name'.
+        *
+        * Overrides its parent method to implement usage of language,
+        * enable fields, etc. Also performs overlays.
+        *
+        * @return array
+        */
+       protected function getCollectedRecords() {
+               $relatedRecords = array();
+               // Assemble where clause
+               $where = 'AND ' . self::$storageTableName . '.uid = ' . intval($this->getIdentifier());
+               // Add condition on tablenames fields
+               $where .= ' AND sys_category_record_mm.tablenames = ' . $this->getDatabase()->fullQuoteStr(
+                       $this->getItemTableName(),
+                       'sys_category_record_mm'
+               );
+               // Add condition on fieldname field
+               $where .= ' AND sys_category_record_mm.fieldname = ' . $this->getDatabase()->fullQuoteStr(
+                       $this->getRelationFieldName(),
+                       'sys_category_record_mm'
+               );
+               // Add enable fields for item table
+               $where .= self::getFrontendObject()->sys_page->enableFields($this->getItemTableName());
+               // If language handling is defined for item table, add language condition
+               if (isset($GLOBALS['TCA'][$this->getItemTableName()]['ctrl']['languageField'])) {
+                       // Consider default or "all" language
+                       $languageField = $this->getItemTableName() . '.' . $GLOBALS['TCA'][$this->getItemTableName()]['ctrl']['languageField'];
+                       $languageCondition = $languageField . ' IN (0, -1)';
+                       // If not in default language, also consider items in current language with no original
+                       if ($this->getFrontendObject()->sys_language_content > 0) {
+                               $languageCondition .= '
+                                       OR (' . $languageField . ' = ' . intval($this->getFrontendObject()->sys_language_content) . '
+                                       AND ' . $this->getItemTableName() . '.' .
+                                       $GLOBALS['TCA'][$this->getItemTableName()]['ctrl']['transOrigPointerField'] . ' = 0)
+                               ';
+                       }
+                       $where .= ' AND (' . $languageCondition . ')';
+               }
+               // Get the related records from the database
+               $resource = $this->getDatabase()->exec_SELECT_mm_query(
+                       $this->getItemTableName() . '.*',
+                       self::$storageTableName,
+                       'sys_category_record_mm',
+                       $this->getItemTableName(),
+                       $where
+               );
+
+               if ($resource) {
+                       while ($record = $this->getDatabase()->sql_fetch_assoc($resource)) {
+                               // Overlay the record for workspaces
+                               $this->getFrontendObject()->sys_page->versionOL(
+                                       $this->getItemTableName(),
+                                       $record
+                               );
+                               // Overlay the record for translations
+                               if (is_array($record) && $this->getFrontendObject()->sys_language_contentOL) {
+                                       if ($this->getItemTableName() === 'pages') {
+                                               $record = $this->getFrontendObject()->sys_page->getPageOverlay($record);
+                                       } else {
+                                               $record = $this->getFrontendObject()->sys_page->getRecordOverlay(
+                                                       $this->getItemTableName(),
+                                                       $record,
+                                                       $this->getFrontendObject()->sys_language_content,
+                                                       $this->getFrontendObject()->sys_language_contentOL
+                                               );
+                                       }
+                               }
+                               // Record may have been unset during the overlay process
+                               if (is_array($record)) {
+                                       $relatedRecords[] = $record;
+                               }
+                       }
+                       $this->getDatabase()->sql_free_result($resource);
+               }
+               return $relatedRecords;
+       }
+
+       /**
+        * Gets the TSFE object.
+        *
+        * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
+        */
+       static protected function getFrontendObject() {
+               return $GLOBALS['TSFE'];
+       }
+}
\ No newline at end of file
index 0a1ce29..3749b4e 100644 (file)
@@ -27,13 +27,30 @@ namespace TYPO3\CMS\Frontend\ContentObject;
  *
  *  This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
 /**
  * Contains RECORDS class object.
  *
  * @author Xavier Perseguers <typo3@perseguers.ch>
  * @author Steffen Kamper <steffen@typo3.org>
  */
-class RecordsContentObject extends \TYPO3\CMS\Frontend\ContentObject\AbstractContentObject {
+class RecordsContentObject extends AbstractContentObject {
+
+       /**
+        * List of all items with table and uid information
+        *
+        * @var array
+        */
+       protected $itemArray = array();
+
+       /**
+        * List of all selected records with full data, arranged per table
+        *
+        * @var array
+        */
+       protected $data = array();
 
        /**
         * Rendering the cObject, RECORDS
@@ -42,6 +59,10 @@ class RecordsContentObject extends \TYPO3\CMS\Frontend\ContentObject\AbstractCon
         * @return string Output
         */
        public function render($conf = array()) {
+               // Reset items and data
+               $this->itemArray = array();
+               $this->data = array();
+
                $theValue = '';
                $originalRec = $GLOBALS['TSFE']->currentRecord;
                // If the currentRecord is set, we register, that this record has invoked this function.
@@ -51,60 +72,67 @@ class RecordsContentObject extends \TYPO3\CMS\Frontend\ContentObject\AbstractCon
                }
                $tables = isset($conf['tables.']) ? $this->cObj->stdWrap($conf['tables'], $conf['tables.']) : $conf['tables'];
                $source = isset($conf['source.']) ? $this->cObj->stdWrap($conf['source'], $conf['source.']) : $conf['source'];
-               if ($tables && $source) {
-                       $allowedTables = $tables;
+               $categories = isset($conf['categories.']) ? $this->cObj->stdWrap($conf['categories'], $conf['categories.']) : $conf['categories'];
+
+               $tablesArray = array_unique(GeneralUtility::trimExplode(',', $tables, TRUE));
+               if ($tables) {
+                       // Add tables which have a configuration (note that this may create duplicate entries)
                        if (is_array($conf['conf.'])) {
-                               foreach ($conf['conf.'] as $k => $v) {
-                                       if (substr($k, -1) != '.') {
-                                               $allowedTables .= ',' . $k;
+                               foreach ($conf['conf.'] as $key => $value) {
+                                       if (substr($key, -1) != '.' && !in_array($key, $tablesArray)) {
+                                               $tablesArray[] = $key;
                                        }
                                }
                        }
-                       /** @var \TYPO3\CMS\Core\Database\RelationHandler $loadDB*/
-                       $loadDB = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
-                       $loadDB->setFetchAllFields(TRUE);
-                       $loadDB->start($source, $allowedTables);
-                       foreach ($loadDB->tableArray as $table => $v) {
-                               if (is_array($GLOBALS['TCA'][$table])) {
-                                       $loadDB->additionalWhere[$table] = $this->cObj->enableFields($table);
-                               }
+
+                       // Get the data, depending on collection method.
+                       // Property "source" is considered more precise and thus takes precedence over "categories"
+                       if ($source) {
+                               $this->collectRecordsFromSource($source, $tablesArray);
+                       } elseif ($categories) {
+                               $relationField = isset($conf['categories.']['relation.']) ? $this->cObj->stdWrap($conf['categories.']['relation'], $conf['categories.']['relation.']) : $conf['categories.']['relation'];
+                               $this->collectRecordsFromCategories($categories, $tablesArray, $relationField);
                        }
-                       $loadDB->getFromDB();
-                       reset($loadDB->itemArray);
-                       $data = $loadDB->results;
-                       $cObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer');
-                       $cObj->setParent($this->cObj->data, $this->cObj->currentRecord);
-                       $this->cObj->currentRecordNumber = 0;
-                       $this->cObj->currentRecordTotal = count($loadDB->itemArray);
-                       foreach ($loadDB->itemArray as $val) {
-                               $row = $data[$val['table']][$val['id']];
-                               // Versioning preview:
-                               $GLOBALS['TSFE']->sys_page->versionOL($val['table'], $row);
-                               // Language overlay:
-                               if (is_array($row) && $GLOBALS['TSFE']->sys_language_contentOL) {
-                                       if ($val['table'] === 'pages') {
-                                               $row = $GLOBALS['TSFE']->sys_page->getPageOverlay($row);
-                                       } else {
-                                               $row = $GLOBALS['TSFE']->sys_page->getRecordOverlay($val['table'], $row, $GLOBALS['TSFE']->sys_language_content, $GLOBALS['TSFE']->sys_language_contentOL);
-                                       }
-                               }
-                               // Might be unset in the content overlay things...
-                               if (is_array($row)) {
-                                       $dontCheckPid = isset($conf['dontCheckPid.']) ? $this->cObj->stdWrap($conf['dontCheckPid'], $conf['dontCheckPid.']) : $conf['dontCheckPid'];
-                                       if (!$dontCheckPid) {
-                                               $row = $this->cObj->checkPid($row['pid']) ? $row : '';
+
+                       if (count($this->itemArray) > 0) {
+                               /** @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $cObj */
+                               $cObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer');
+                               $cObj->setParent($this->cObj->data, $this->cObj->currentRecord);
+                               $this->cObj->currentRecordNumber = 0;
+                               $this->cObj->currentRecordTotal = count($this->itemArray);
+                               foreach ($this->itemArray as $val) {
+                                       $row = $this->data[$val['table']][$val['id']];
+                                       // Perform overlays if necessary (records coming from category collections are already overlaid)
+                                       if ($source) {
+                                               // Versioning preview
+                                               $GLOBALS['TSFE']->sys_page->versionOL($val['table'], $row);
+                                               // Language overlay
+                                               if (is_array($row) && $GLOBALS['TSFE']->sys_language_contentOL) {
+                                                       if ($val['table'] === 'pages') {
+                                                               $row = $GLOBALS['TSFE']->sys_page->getPageOverlay($row);
+                                                       } else {
+                                                               $row = $GLOBALS['TSFE']->sys_page->getRecordOverlay($val['table'], $row, $GLOBALS['TSFE']->sys_language_content, $GLOBALS['TSFE']->sys_language_contentOL);
+                                                       }
+                                               }
                                        }
-                                       if ($row && !$GLOBALS['TSFE']->recordRegister[($val['table'] . ':' . $val['id'])]) {
-                                               $renderObjName = $conf['conf.'][$val['table']] ? $conf['conf.'][$val['table']] : '<' . $val['table'];
-                                               $renderObjKey = $conf['conf.'][$val['table']] ? 'conf.' . $val['table'] : '';
-                                               $renderObjConf = $conf['conf.'][$val['table'] . '.'];
-                                               $this->cObj->currentRecordNumber++;
-                                               $cObj->parentRecordNumber = $this->cObj->currentRecordNumber;
-                                               $GLOBALS['TSFE']->currentRecord = $val['table'] . ':' . $val['id'];
-                                               $this->cObj->lastChanged($row['tstamp']);
-                                               $cObj->start($row, $val['table']);
-                                               $tmpValue = $cObj->cObjGetSingle($renderObjName, $renderObjConf, $renderObjKey);
-                                               $theValue .= $tmpValue;
+                                       // Might be unset during the overlay process
+                                       if (is_array($row)) {
+                                               $dontCheckPid = isset($conf['dontCheckPid.']) ? $this->cObj->stdWrap($conf['dontCheckPid'], $conf['dontCheckPid.']) : $conf['dontCheckPid'];
+                                               if (!$dontCheckPid) {
+                                                       $row = $this->cObj->checkPid($row['pid']) ? $row : '';
+                                               }
+                                               if ($row && !$GLOBALS['TSFE']->recordRegister[($val['table'] . ':' . $val['id'])]) {
+                                                       $renderObjName = $conf['conf.'][$val['table']] ? $conf['conf.'][$val['table']] : '<' . $val['table'];
+                                                       $renderObjKey = $conf['conf.'][$val['table']] ? 'conf.' . $val['table'] : '';
+                                                       $renderObjConf = $conf['conf.'][$val['table'] . '.'];
+                                                       $this->cObj->currentRecordNumber++;
+                                                       $cObj->parentRecordNumber = $this->cObj->currentRecordNumber;
+                                                       $GLOBALS['TSFE']->currentRecord = $val['table'] . ':' . $val['id'];
+                                                       $this->cObj->lastChanged($row['tstamp']);
+                                                       $cObj->start($row, $val['table']);
+                                                       $tmpValue = $cObj->cObjGetSingle($renderObjName, $renderObjConf, $renderObjKey);
+                                                       $theValue .= $tmpValue;
+                                               }
                                        }
                                }
                        }
@@ -121,4 +149,96 @@ class RecordsContentObject extends \TYPO3\CMS\Frontend\ContentObject\AbstractCon
                return $theValue;
        }
 
+       /**
+        * Collects records according to the configured source
+        *
+        * @param string $source Source of records
+        * @param array $tables List of tables
+        * @return void
+        */
+       protected function collectRecordsFromSource($source, array $tables) {
+               /** @var \TYPO3\CMS\Core\Database\RelationHandler $loadDB*/
+               $loadDB = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
+               $loadDB->setFetchAllFields(TRUE);
+               $loadDB->start($source, implode(',', $tables));
+               foreach ($loadDB->tableArray as $table => $v) {
+                       if (isset($GLOBALS['TCA'][$table])) {
+                               $loadDB->additionalWhere[$table] = $this->cObj->enableFields($table);
+                       }
+               }
+               $this->data = $loadDB->getFromDB();
+               reset($loadDB->itemArray);
+               $this->itemArray = $loadDB->itemArray;
+       }
+
+       /**
+        * Collects records for all selected tables and categories.
+        *
+        * @param string $selectedCategories Comma-separated list of categories
+        * @param array $tables List of tables
+        * @param string $relationField Name of the field containing the categories relation
+        * @return void
+        */
+       protected function collectRecordsFromCategories($selectedCategories, array $tables, $relationField) {
+               $selectedCategories = array_unique(GeneralUtility::intExplode(',', $selectedCategories, TRUE));
+
+               // Loop on all selected tables
+               foreach ($tables as $table) {
+
+                       // Get the records for each selected category
+                       $tableRecords = array();
+                       $categoriesPerRecord = array();
+                       foreach ($selectedCategories as $aCategory) {
+                               try {
+                                       $collection = \TYPO3\CMS\Frontend\Category\Collection\CategoryCollection::load(
+                                               $aCategory,
+                                               TRUE,
+                                               $table,
+                                               $relationField
+                                       );
+                                       if ($collection->count() > 0) {
+                                               // Add items to the collection of records for the current table
+                                               foreach ($collection as $item) {
+                                                       $tableRecords[$item['uid']] = $item;
+                                                       // Keep track of all categories a given item belongs to
+                                                       if (!isset($categoriesPerRecord[$item['uid']])) {
+                                                               $categoriesPerRecord[$item['uid']] = array();
+                                                       }
+                                                       $categoriesPerRecord[$item['uid']][] = $aCategory;
+                                               }
+                                       }
+                               } catch (\Exception $e) {
+                                       $message = sprintf(
+                                               'Could not get records for category id %d. Error: %s (%d)',
+                                               $aCategory,
+                                               $e->getMessage(),
+                                               $e->getCode()
+                                       );
+                                       $this->getTimeTracker()->setTSlogMessage($message, 2);
+                               }
+                       }
+                       // Store the resulting records into the itemArray and data results array
+                       if (count($tableRecords) > 0) {
+                               $this->data[$table] = array();
+                               foreach ($tableRecords as $record) {
+                                       $this->itemArray[] = array(
+                                               'id' => $record['uid'],
+                                               'table' => $table
+                                       );
+                                       // Add to the record the categories it belongs to
+                                       $record['_categories'] = implode(',', $categoriesPerRecord[$record['uid']]);
+                                       $this->data[$table][$record['uid']] = $record;
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Wrapper around the $GLOBALS['TT'] variable
+        *
+        * @return \TYPO3\CMS\Core\TimeTracker\TimeTracker
+        */
+       protected function getTimeTracker() {
+               return $GLOBALS['TT'];
+       }
 }
index 0d07180..c68851d 100644 (file)
@@ -1340,6 +1340,10 @@ return array(
                                        array(
                                                'LLL:EXT:cms/locallang_ttc.xlf:menu_type.I.9',
                                                '9'
+                                       ),
+                                       array(
+                                               'LLL:EXT:cms/locallang_ttc.xlf:menu_type.I.categorized_content',
+                                               'categorized_content'
                                        )
                                ),
                                'default' => '0'
@@ -1929,6 +1933,7 @@ return array(
                                '7' => 'selected_categories, category_field',
                                '8' => 'selected_categories, category_field',
                                '9' => 'pages',
+                               'categorized_content' => 'pages',
                        )
                ),
                'mailform' => array(