ac6f94ad8f259f670999f9ce2f733c71ef69359f
[TYPO3CMS/Extensions/overlays.git] / class.tx_overlays.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2008 Francois Suter (Cobweb) <typo3@cobweb.ch>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 *
24 * $Id$
25 ***************************************************************/
26 /**
27 * [CLASS/FUNCTION INDEX of SCRIPT]
28 *
29 * Hint: use extdeveval to insert/update function index above.
30 */
31
32
33 /**
34 * Main library of the 'overlays' extension.
35 * It aims to improve on the performance of the original overlaying mechanism provided by t3lib_page
36 * and to provide a more useful API for developers
37 *
38 * @author Francois Suter (Cobweb) <typo3@cobweb.ch>
39 * @package TYPO3
40 * @subpackage tx_overlays
41 */
42 class tx_overlays {
43
44 /**
45 * This method is designed to get all the records from a given table, properly overlaid with versions and translations
46 * Its parameters are the same as t3lib_db::exec_SELECTquery()
47 * A small difference is that it will take only a single table
48 * The big difference is that it returns an array of properly overlaid records and not a result pointer
49 *
50 * @param string $selectFields: List of fields to select from the table. This is what comes right after "SELECT ...". Required value.
51 * @param string $fromTable: Table from which to select. This is what comes right after "FROM ...". Required value.
52 * @param string $whereClause: Optional additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself! DO NOT PUT IN GROUP BY, ORDER BY or LIMIT!
53 * @param string $groupBy: Optional GROUP BY field(s), if none, supply blank string.
54 * @param string $orderBy: Optional ORDER BY field(s), if none, supply blank string.
55 * @param string $limit: Optional LIMIT value ([begin,]max), if none, supply blank string.
56 * @return array Fully overlaid recordset
57 */
58 public function getAllRecordsForTable($selectFields, $fromTable, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '') {
59 // SQL WHERE clause is the base clause passed to the function, plus language condition, plus enable fields condition
60 $where = $whereClause;
61 $condition = self::getLanguageCondition($fromTable);
62 if (!empty($condition)) {
63 if (!empty($where)) $where .= ' AND ';
64 $where .= $condition;
65 }
66 $condition = self::getEnableFieldsCondition($fromTable);
67 if (!empty($condition)) {
68 if (!empty($where)) $where .= ' AND ';
69 $where .= $condition;
70 }
71
72 // If the language is not default, prepare for overlays
73 if ($GLOBALS['TSFE']->sys_language_content > 0) {
74 // Make sure the list of selected fields includes "uid", "pid" and language fields so that language overlays can be gotten properly
75 // If these do not exist in the queried table, the recordset is returned as is, without overlay
76 try {
77 $selectFields = self::selectOverlayFields($fromTable, $selectFields);
78 $doOverlays = true;
79 }
80 catch (Exception $e) {
81 $doOverlays = false;
82 }
83 }
84 else {
85 $doOverlays = false;
86 }
87
88 // Execute the query itself
89 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($selectFields, $fromTable, $where, $groupBy, $orderBy, $limit);
90 // Assemble a raw recordset
91 $records = array();
92 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
93 $records[] = $row;
94 }
95
96 // If we have both a uid and a pid field, we can proceed with overlaying the records
97 if ($doOverlays) {
98 $records = self::overlayRecordSet($fromTable, $records, $GLOBALS['TSFE']->sys_language_content, $GLOBALS['TSFE']->sys_language_contentOL);
99 }
100 return $records;
101 }
102
103 /**
104 * This method gets the SQL condition to apply for fetching the proper language
105 * depending on the localization settings in the TCA
106 *
107 * @param string $table: name of the table to assemble the condition for
108 * @return string SQL to add to the WHERE clause (without "AND")
109 */
110 public function getLanguageCondition($table) {
111 $languageCondition = '';
112
113 // First check if there's actually a TCA for the given table
114 if (isset($GLOBALS['TCA'][$table]['ctrl'])) {
115 $tableCtrlTCA = $GLOBALS['TCA'][$table]['ctrl'];
116
117 // Assemble language condition only if a language field is defined
118 if (!empty($tableCtrlTCA['languageField'])) {
119 if (isset($GLOBALS['TSFE']->sys_language_contentOL) && isset($tableCtrlTCA['transOrigPointerField'])) {
120 $languageCondition = $table.'.'.$tableCtrlTCA['languageField'].' IN (0,-1)'; // Default language and "all" language
121
122 // If current language is not default, select elements that exist only for current language
123 // That means elements that exist for current language but have no parent element
124 if ($GLOBALS['TSFE']->sys_language_content > 0) {
125 $languageCondition .= ' OR ('.$table.'.'.$tableCtrlTCA['languageField']." = '".$GLOBALS['TSFE']->sys_language_content."' AND ".$table.'.'.$tableCtrlTCA['transOrigPointerField']." = '0')";
126 }
127 }
128 else {
129 $languageCondition = $table.'.'.$tableCtrlTCA['languageField']." = '".$GLOBALS['TSFE']->sys_language_content."'";
130 }
131 }
132 }
133 // TODO: throw an exception if table has no language mechanism?
134 return $languageCondition;
135 }
136
137 /**
138 * This method returns the condition on enable fields for the given table
139 * Basically it calls on the method provided by t3lib_page, but without the " AND " in front
140 *
141 * @param string $table: name of the table to build the condition for
142 * @param boolean $showHidden: If set, then you want NOT to filter out hidden records. Otherwise hidden record are filtered based on the current preview settings.
143 * @return string SQL to add to the WHERE clause (without "AND")
144 */
145 public function getEnableFieldsCondition($table, $showHidden = 0) {
146 $enableCondition = '';
147 // First check if table has a TCA ctrl section, otherwise t3lib_page::enableFields() will die() (stupid thing!)
148 if (isset($GLOBALS['TCA'][$table]['ctrl'])) {
149 $enableCondition = $GLOBALS['TSFE']->sys_page->enableFields($table, $show_hidden ? $show_hidden : ($table == 'pages' ? $GLOBALS['TSFE']->showHiddenPage : $GLOBALS['TSFE']->showHiddenRecords));
150 // If an enable clause was returned, strip the first ' AND '
151 if (!empty($enableCondition)) {
152 $enableCondition = substr($enableCondition, strlen(' AND '));
153 }
154 }
155 // TODO: throw an exception if the given table has no TCA? (t3lib_page::enableFields() used a die)
156 return $enableCondition;
157 }
158
159 /**
160 * This method makes sure that all the fields necessary for proper overlaying are included
161 * in the list of selected fields and exist in the table being queried
162 * If not, it throws an exception
163 *
164 * @param string $table: Table from which to select. This is what comes right after "FROM ...". Required value.
165 * @param string $selectFields: List of fields to select from the table. This is what comes right after "SELECT ...". Required value.
166 * @return string Possibly modified list of fields to select
167 */
168 public function selectOverlayFields($table, $selectFields) {
169 $select = $selectFields;
170
171 // Check if the table indeed has a TCA
172 if (isset($GLOBALS['TCA'][$table]['ctrl'])) {
173
174 // If the table uses a foreign table for translations, there are no fields to add
175 // Return original select fields directly
176 if ($GLOBALS['TCA'][$table]['ctrl']['transForeignTable']) {
177 return $selectFields;
178 }
179 else {
180 $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
181
182 // In order to be properly overlaid, a table has to have a uid, a pid and languageField
183 $hasUidField = strpos($selectFields, 'uid');
184 $hasPidField = strpos($selectFields, 'pid');
185 $hasLanguageField = strpos($selectFields, $languageField);
186 if ($hasUidField === false || $hasPidField === false || $hasLanguageField === false) {
187 $availableFields = $GLOBALS['TYPO3_DB']->admin_get_fields($table);
188 if (isset($availableFields['uid'])) {
189 if ($selectFields != '*') $select .= ', '.$table.'.uid';
190 $hasUidField = true;
191 }
192 if (isset($availableFields['pid'])) {
193 if ($selectFields != '*') $select .= ', '.$table.'.pid';
194 $hasPidField = true;
195 }
196 if (isset($availableFields[$languageField])) {
197 if ($selectFields != '*') $select .= ', '.$table.'.'.$languageField;
198 $hasLanguageField = true;
199 }
200 }
201 // If one of the fields is still missing after that, throw an exception
202 if ($hasUidField === false || $hasPidField === false || $hasLanguageField === false) {
203 throw new Exception('Not all overlay fields available.');
204 }
205 // Else return the modified list of fields to select
206 else {
207 return $select;
208 }
209 }
210 }
211 // The table has no TCA, throw an exception
212 else {
213 throw new Exception('No TCA for table, cannot add overlay fields.');
214 }
215 }
216
217 /**
218 * Creates language-overlay for records in general (where translation is found in records from the same table)
219 * This is originally copied from t3lib_page::getRecordOverlay()
220 *
221 * @param string $table: Table name
222 * @param array $recordset: Full recordset to overlay. Must containt uid, pid and $table]['ctrl']['languageField']
223 * @param integer $sys_language_contentPointer to the sys_language uid for content on the site.
224 * @param string $OLmodeOverlay mode. If "hideNonTranslated" then records without translation will not be returned un-translated but unset (and return value is false)
225 * @return array Returns the full overlaid recordset. If $OLmode is "hideNonTranslated" then some records may be missing if no translation was found.
226 */
227 public function overlayRecordSet($table, $recordset, $sys_language_content, $OLmode = '') {
228
229 // Test with the first row if uid and pid fields are present
230 if (!empty($recordset[0]['uid']) && !empty($recordset[0]['pid'])) {
231
232 // Test if the table has a TCA definition
233 if (isset($GLOBALS['TCA'][$table])) {
234 $tableCtrl = $GLOBALS['TCA'][$table]['ctrl'];
235
236 // Test if the TCA definition includes translation information
237 if ($tableCtrl['languageField'] && $tableCtrl['transOrigPointerField']) {
238
239 // Test with the first row if languageField is present
240 if (isset($recordset[0][$tableCtrl['languageField']])) {
241 if ($tableCtrl['transOrigPointerTable']) {
242 // TODO: Handle overlays stored in separate table (see Olly's patch)
243 // In the meantime, return recordset unchanged
244 return $recordset;
245 }
246 else {
247 // Filter out records that are not in the default or [ALL] language, should there be any
248 $filteredRecordset = array();
249 foreach ($recordset as $row) {
250 if ($row[$tableCtrl['languageField']] <= 0) {
251 $filteredRecordset[] = $row;
252 }
253 }
254
255 // Will try to overlay a record only if the sys_language_content value is larger than zero,
256 // that is, it is not default or [ALL] language
257 if ($sys_language_content > 0) {
258 // Assemble a list of uid's for getting the overlays,
259 // but only from the filtered recordset
260 $uidList = array();
261 foreach ($filteredRecordset as $row) {
262 $uidList[] = $row['uid'];
263 }
264
265 /*
266 // Select overlays for all records
267 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
268 '*',
269 $table,
270 $tableCtrl['languageField'].' = '.intval($sys_language_content).
271 ' AND '.$tableCtrl['transOrigPointerField'].' IN ('.implode(', ', $uidList).')'.
272 ' AND '.self::getEnableFieldsCondition($table)
273 );
274 // Arrange overlay records according to transOrigPointerField, so that it's easy to relate them to the originals
275 // This structure is actually a 2-dimensional array, with the pid as the second key
276 // Because of versioning, there may be several overlays for a given original and matching the pid too
277 // ensures that we are refering to the correct overlay
278 $overlays = array();
279 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
280 if (!isset($overlays[$row[$tableCtrl['transOrigPointerField']]])) $overlays[$row[$tableCtrl['transOrigPointerField']]] = array();
281 $overlays[$row[$tableCtrl['transOrigPointerField']]][$row['pid']] = $row;
282 }
283 */
284 $overlays = self::getOverlayRecords($table, $uidList, $sys_language_content);
285
286 // Now loop on the filtered recordset and try to overlay each record
287 $overlaidRecordset = array();
288 foreach ($filteredRecordset as $row) {
289 // An overlay exists, apply it
290 if (isset($overlays[$row['uid']][$row['pid']])) {
291 $overlaidRecordset[] = self::overlaySingleRecord($table, $row, $overlays[$row['uid']][$row['pid']]);
292 }
293 // No overlay exists
294 else {
295 // Take original record, only if non-translated are not hidden, or if language is [All]
296 if ($OLmode != 'hideNonTranslated' || $row[$tableCtrl['languageField']] == -1) {
297 $overlaidRecordset[] = $row;
298 }
299 }
300 }
301 // Return the overlaid recordset
302 return $overlaidRecordset;
303 }
304 else {
305 // When default language is displayed, we never want to return a record carrying another language!
306 // Return the filtered recordset
307 return $filteredRecordset;
308 }
309 }
310 }
311 // Provided recordset does not contain languageField field, return recordset unchanged
312 else {
313 return $recordset;
314 }
315 }
316 // No appropriate language fields defined in TCA, return recordset unchanged
317 else {
318 return $recordset;
319 }
320 }
321 // No TCA for table, return recordset unchanged
322 else {
323 return $recordset;
324 }
325 }
326 // Recordset did not contain uid or pid field, return recordset unchanged
327 else {
328 return $recordset;
329 }
330 }
331
332 /**
333 * This method is used to retrieve all the records for overlaying other records
334 *
335 * @param string $table: name of the table for which to fetch the records
336 * @param array $uids: array of all uid's of the original records for which to fetch the translation
337 * @param integer $sys_language_content: uid of the system language to translate to
338 * @return array All overlay records arranged per original uid and per pid, so that they can be checked (this is related to workspaces)
339 */
340 public function getOverlayRecords($table, $uids, $sys_language_content) {
341 $tableCtrl = $GLOBALS['TCA'][$table]['ctrl'];
342 // Select overlays for all records
343 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
344 '*',
345 $table,
346 $tableCtrl['languageField'].' = '.intval($sys_language_content).
347 ' AND '.$tableCtrl['transOrigPointerField'].' IN ('.implode(', ', $uids).')'.
348 ' AND '.self::getEnableFieldsCondition($table)
349 );
350 // Arrange overlay records according to transOrigPointerField, so that it's easy to relate them to the originals
351 // This structure is actually a 2-dimensional array, with the pid as the second key
352 // Because of versioning, there may be several overlays for a given original and matching the pid too
353 // ensures that we are refering to the correct overlay
354 $overlays = array();
355 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
356 if (!isset($overlays[$row[$tableCtrl['transOrigPointerField']]])) $overlays[$row[$tableCtrl['transOrigPointerField']]] = array();
357 $overlays[$row[$tableCtrl['transOrigPointerField']]][$row['pid']] = $row;
358 }
359 return $overlays;
360 }
361
362 /**
363 * This method takes a record and its overlay and performs the overlay according to active translation rules
364 * This piece of code is extracted from t3lib_page::getRecordOverlay()
365 *
366 * @param string $table: name of the table for which the operation is taking place
367 * @param array $record: record to overlay
368 * @param array $overlay: overlay of the record
369 * @return array Overlaid record
370 */
371 public function overlaySingleRecord($table, $record, $overlay) {
372 $overlaidRecord = $record;
373 $overlaidRecord['_LOCALIZED_UID'] = $overlay['uid'];
374 foreach($record as $key => $value) {
375 if ($key != 'uid' && $key != 'pid' && isset($overlay[$key])) {
376 if ($GLOBALS['TSFE']->TCAcachedExtras[$table]['l10n_mode'][$key] != 'exclude'
377 && ($GLOBALS['TSFE']->TCAcachedExtras[$table]['l10n_mode'][$key] != 'mergeIfNotBlank' || strcmp(trim($overlay[$key]), ''))) {
378 $overlaidRecord[$key] = $overlay[$key];
379 }
380 }
381 }
382 return $overlaidRecord;
383 }
384 }
385
386
387
388 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/overlays/class.tx_overlays.php']) {
389 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/overlays/class.tx_overlays.php']);
390 }
391
392 ?>