* Fixed possible XSS in workspace/version modules (requires valid BE login / patch...
[Packages/TYPO3.CMS.git] / typo3 / mod / user / ws / class.wslib_gui.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2005 Kasper Skaarhoj (kasperYYYY@typo3.com)
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 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Library with Workspace GUI related functionality. It is used by main workspace
29 * module but also can be used from extensions. Originally 99.9%% of the code
30 * was written by Kasper and only transfered here by Dmitry.
31 *
32 * @author Kasper Skaarhoj <kasperYYYY@typo3.com>
33 * @author Dmitry Dulepov <dmitry@typo3.org>
34 */
35 /**
36 * [CLASS/FUNCTION INDEX of SCRIPT]
37 *
38 *
39 *
40 * 84: class wslib_gui
41 *
42 * SECTION: Public functions
43 * 128: function getWorkspaceOverview(&$doc, $wsid = null, $filter = 0, $pageId = -1)
44 * 192: function hlSubelements(origId, verId, over, diffLayer)
45 *
46 * SECTION: Private functions (do not use outside of this class!)
47 * 224: function initVars()
48 * 253: function displayWorkspaceOverview_setInPageArray(&$pArray, $rlArr, $table, $row)
49 * 284: function markupNewOriginals()
50 * 309: function displayWorkspaceOverview_list($pArray, $tableRows=array(), $c=0, $warnAboutVersions=FALSE)
51 * 504: function displayWorkspaceOverview_pageTreeIconTitle($pageUid, $title, $indentCount)
52 * 518: function formatVerId($verId)
53 * 529: function formatWorkspace($wsid)
54 * 559: function createDiffView($table, $diff_1_record, $diff_2_record)
55 * 676: function versionsInOtherWS($table, $uid)
56 * 705: function showStageChangeLog($table,$id,$stageCommands)
57 * 757: function displayWorkspaceOverview_commandLinks($table,&$rec_on,&$rec_off,$vType)
58 * 830: function formatCount($count)
59 * 860: function subElements($uid,$treeLevel,$origId=0)
60 * 963: function subElements_getNonPageRecords($tN, $uid, &$recList)
61 * 993: function subElements_renderItem(&$tCell,$tN,$uid,$rec,$origId,$iconMode,$HTMLdata)
62 * 1066: function displayWorkspaceOverview_commandLinksSub($table,$rec,$origId)
63 * 1113: function displayWorkspaceOverview_stageCmd($table,&$rec_off)
64 *
65 * TOTAL FUNCTIONS: 19
66 * (This index is automatically created/updated by the extension "extdeveval")
67 *
68 */
69
70 require_once(PATH_typo3 . 'mod/user/ws/class.wslib.php');
71 $LANG->includeLLFile('EXT:lang/locallang_mod_user_ws.xml');
72 $LANG->includeLLFile('EXT:lang/locallang_misc.xml');
73
74 /**
75 * Library with Workspace GUI related functionality. It is used by main workspace
76 * module but also can be used from extensions. Originally 99.9%% of the code
77 * was written by Kasper and only transfered here by Dmitry.
78 *
79 * @author Kasper Skaarhoj <kasperYYYY@typo3.com>
80 * @author Dmitry Dulepov <dmitry@typo3.org>
81 * @package TYPO3
82 * @subpackage core
83 */
84 class wslib_gui {
85
86 // Static:
87 var $pageTreeIndent = 8;
88 var $pageTreeIndent_titleLgd = 30;
89
90 // Options
91 var $diff = false;
92 var $expandSubElements = false;
93 var $alwaysDisplayHeader = false;
94
95 // Internal
96 var $showWorkspaceCol = 0;
97 var $doc;
98 var $formatWorkspace_cache = array();
99 var $targets = array(); // Accumulation of online targets.
100 var $be_user_Array = array();
101 var $pageModule = '';
102 var $formatCount_cache = array();
103 var $stageIndex = array();
104 var $addHlSubelementsScript = false;
105
106 /*********************************
107 *
108 * Public functions
109 *
110 *********************************/
111
112 /**
113 * Creates HTML to display workspace overview. Can be used to display overview for all possible records or only for single page.
114 *
115 * The following code is <strong>required</strong> in BE module when this function is used:
116 * <code>
117 * $CMparts = $this->doc->getContextMenuCode();
118 * $this->doc->JScode.= $CMparts[0];
119 * $this->doc->bodyTagAdditions = $CMparts[1];
120 * $this->doc->postCode.= $CMparts[2];
121 * </code>
122 * or click-menu will not be gerated properly!
123 *
124 * @param object $doc Document (to use for formatting)
125 * @param int $wsid Workspace ID, If <code>null</code>, the value is obtained from current BE user
126 * @param int $filter If 0, than no filtering, if 10 than select for publishing, otherwise stage value
127 * @param int $pageId If greater than zero, than it is UID of page in LIVE workspaces to select records for
128 * @return string Generated HTML
129 */
130 function getWorkspaceOverview(&$doc, $wsid = null, $filter = 0, $pageId = -1) {
131 global $LANG;
132
133 // Setup
134 $this->workspaceId = (!is_null($wsid) ? $wsid : $GLOBALS['BE_USER']->workspace);
135 $this->showWorkspaceCol = $GLOBALS['BE_USER']->workspace == 0 && $this->workspaceId <= -98;
136 $this->doc = $doc;
137 $this->initVars();
138
139 // Initialize workspace object and request all pending versions:
140 $wslibObj = t3lib_div::makeInstance('wslib');
141
142 // Selecting ALL versions belonging to the workspace:
143 $versions = $wslibObj->selectVersionsInWorkspace($this->workspaceId, $filter, -99, $pageId);
144
145 // Traverse versions and build page-display array:
146 $pArray = array();
147 $wmArray = array(); // is page in web mount?
148 $rlArray = array(); // root line of page
149 $pagePermsClause = $GLOBALS['BE_USER']->getPagePermsClause(1);
150 foreach($versions as $table => $records) {
151 if (is_array($records)) {
152 foreach($records as $rec) {
153 $pageIdField = $table==='pages' ? 't3ver_oid' : 'realpid';
154 $recPageId = $rec[$pageIdField];
155 if (!isset($wmArray[$recPageId])) {
156 $wmArray[$recPageId] = $GLOBALS['BE_USER']->isInWebMount($recPageId,$pagePermsClause);
157 }
158 if ($wmArray[$recPageId]) {
159 if (!isset($rlArray[$recPageId])) {
160 $rlArray[$recPageId] = t3lib_BEfunc::BEgetRootLine($recPageId, 'AND 1=1');
161 }
162 $this->displayWorkspaceOverview_setInPageArray(
163 $pArray,
164 $rlArray[$recPageId],
165 $table,
166 $rec
167 );
168 }
169 }
170 }
171 }
172
173 $workspaceOverviewList = $this->displayWorkspaceOverview_list($pArray);
174 if ($workspaceOverviewList || $this->alwaysDisplayHeader) {
175 // Make header of overview:
176 $tableRows = array();
177 $tableRows[] = '
178 <tr class="bgColor5 tableheader">
179 <td nowrap="nowrap" width="100">' . $LANG->getLL('label_pagetree') . '</td>
180 <td nowrap="nowrap" colspan="2">' . $LANG->getLL('label_live_version') . '</td>
181 <td nowrap="nowrap" colspan="2">' . $LANG->getLL('label_draft_versions') . '</td>
182 <td nowrap="nowrap">' . $LANG->getLL('label_stage') . '</td>
183 <td nowrap="nowrap">' . $LANG->getLL('label_publish') . '</td>
184 <td>' . $LANG->getLL('label_lifecycle') . '</td>
185 '.($this->showWorkspaceCol ? '<td>' . $LANG->getLL('label_workspace') . '</td>' : '').'
186 </tr>';
187
188 // Add lines from overview:
189 $tableRows = array_merge($tableRows, $workspaceOverviewList);
190
191 $table = '<table border="0" cellpadding="0" cellspacing="1" class="lrPadding workspace-overview">'.implode('',$tableRows).'</table>';
192
193 // script
194 if ($this->addHlSubelementsScript && !strstr($this->doc->JScode, 'function hlSubelements(')) {
195 $table = $this->doc->wrapScriptTags('
196 function hlSubelements(origId, verId, over, diffLayer) {
197 if (over) {
198 document.getElementById(\'orig_\'+origId).attributes.getNamedItem("class").nodeValue = \'typo3-ver-hl\';
199 document.getElementById(\'ver_\'+verId).attributes.getNamedItem("class").nodeValue = \'typo3-ver-hl\';
200 if (diffLayer) {
201 document.getElementById(\'diff_\'+verId).style.visibility = \'visible\';
202 }
203 } else {
204 document.getElementById(\'orig_\'+origId).attributes.getNamedItem("class").nodeValue = \'typo3-ver\';
205 document.getElementById(\'ver_\'+verId).attributes.getNamedItem("class").nodeValue = \'typo3-ver\';
206 if (diffLayer) {
207 document.getElementById(\'diff_\'+verId).style.visibility = \'hidden\';
208 }
209 }
210 }
211 ') . $table;
212 }
213
214 return $table . $this->markupNewOriginals();
215 }
216 return '';
217 }
218
219 /*********************************
220 *
221 * Private functions (do not use outside of this class!)
222 *
223 *********************************/
224
225 /**
226 * Initializes several class variables
227 *
228 * @return void
229 */
230 function initVars() {
231 // Init users
232 $be_group_Array = t3lib_BEfunc::getListGroupNames('title,uid');
233 $groupArray = array_keys($be_group_Array);
234 // Need 'admin' field for t3lib_iconWorks::getIconImage()
235 $this->be_user_Array = t3lib_BEfunc::getUserNames('username,usergroup,usergroup_cached_list,uid,admin,workspace_perms');
236 if (!$GLOBALS['BE_USER']->isAdmin()) {
237 $this->be_user_Array = t3lib_BEfunc::blindUserNames($this->be_user_Array,$groupArray,1);
238 }
239
240 // If another page module was specified, replace the default Page module with the new one
241 $newPageModule = trim($GLOBALS['BE_USER']->getTSConfigVal('options.overridePageModule'));
242 $this->pageModule = t3lib_BEfunc::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
243
244 // Setting publish access permission for workspace:
245 $this->publishAccess = $GLOBALS['BE_USER']->workspacePublishAccess($GLOBALS['BE_USER']->workspace); // FIXME Should be $this->workspaceId here?
246 }
247
248 /**
249 * Building up of the $pArray variable which is a hierarchical storage of table-rows arranged according to the level in the rootline the element was found
250 * (Internal)
251 * Made for recursive calling
252 *
253 * @param array Array that is built up with the page tree structure
254 * @param array Root line for element (table / row); The element is stored in pArray according to this root line.
255 * @param string Table name
256 * @param array Table row
257 * @return void $pArray is passed by reference and modified internally
258 */
259 function displayWorkspaceOverview_setInPageArray(&$pArray, $rlArr, $table, $row) {
260 // Initialize:
261 ksort($rlArr);
262 reset($rlArr);
263 if (!$rlArr[0]['uid']) {
264 array_shift($rlArr);
265 }
266
267 // Get and remove first element in root line:
268 $cEl = current($rlArr);
269 $pUid = $cEl['t3ver_oid'] ? $cEl['t3ver_oid'] : $cEl['uid']; // Done to pile up "false versions" in the right branch...
270
271 $pArray[$pUid] = $cEl['title'];
272 array_shift($rlArr);
273
274 // If there are elements left in the root line, call this function recursively (to build $pArray in depth)
275 if (count($rlArr)) {
276 if (!isset($pArray[$pUid.'.'])) {
277 $pArray[$pUid.'.'] = array();
278 }
279 $this->displayWorkspaceOverview_setInPageArray($pArray[$pUid.'.'], $rlArr, $table, $row);
280 } else { // If this was the last element, set the value:
281 $pArray[$pUid.'_'][$table][$row['t3ver_oid']][] = $row;
282 }
283 }
284
285 /**
286 * JavaScript code to mark up new records that are online (in sub element lists)
287 *
288 * @return string HTML javascript section
289 */
290 function markupNewOriginals() {
291
292 if (count($this->targets)) {
293 $scriptCode = '';
294 foreach($this->targets as $key => $rec) {
295 $scriptCode.='
296 document.getElementById(\''.$key.'\').attributes.getNamedItem("class").nodeValue = \'typo3-ver-new\';
297 ';
298 }
299
300 return $this->doc->wrapScriptTags($scriptCode);
301 }
302 }
303
304
305 /**
306 * Rendering the content for the publish / review overview:
307 * (Made for internal recursive calling)
308 *
309 * @param array Hierarchical storage of the elements to display (see displayWorkspaceOverview() / displayWorkspaceOverview_setInPageArray())
310 * @param array Existing array of table rows to add to
311 * @param array Depth counter
312 * @param boolean If set, a warning is shown if versions are found (internal flag)
313 * @return array Table rows, see displayWorkspaceOverview()
314 */
315 function displayWorkspaceOverview_list($pArray, $tableRows=array(), $c=0, $warnAboutVersions=FALSE) {
316 global $TCA, $LANG;
317
318 // Initialize:
319 $fullColSpan = $this->showWorkspaceCol ? 9 : 8;
320
321 // Traverse $pArray
322 if (is_array($pArray)) {
323 foreach($pArray as $k => $v) {
324 if (t3lib_div::testInt($k)) {
325
326 // If there are elements on this level, output a divider row which just creates a little visual space.
327 if (is_array($pArray[$k.'_'])) {
328 $tableRows[] = '
329 <tr>
330 <td colspan="'.$fullColSpan.'"><img src="clear.gif" width="1" height="3" alt="" /></td>
331 </tr>';
332 }
333
334 // Printing a level from the page tree with no additional content:
335 // If there are NO elements on this level OR if there are NO pages on a level with elements, then show page tree icon and title (otherwise it is shown with the display of the elements)
336 if (!is_array($pArray[$k.'_']) || !is_array($pArray[$k.'_']['pages'])) {
337 $tableRows[] = '
338 <tr class="bgColor4-20">
339 <td nowrap="nowrap" colspan="'.$fullColSpan.'">'.
340 $this->displayWorkspaceOverview_pageTreeIconTitle($k,$pArray[$k],$c).
341 '</td>
342 </tr>';
343 }
344
345 // If there ARE elements on this level, print them:
346 $warnAboutVersions_next = $warnAboutVersions;
347 $warnAboutVersions_nonPages = FALSE;
348 $warnAboutVersions_page = FALSE;
349 if (is_array($pArray[$k.'_'])) {
350 foreach($pArray[$k.'_'] as $table => $oidArray) {
351 foreach($oidArray as $oid => $recs) {
352
353 // Get CURRENT online record and icon based on "t3ver_oid":
354 $rec_on = t3lib_BEfunc::getRecord($table,$oid);
355 $icon = t3lib_iconWorks::getIconImage($table, $rec_on, $this->doc->backPath,' align="top" title="'.t3lib_BEfunc::getRecordIconAltText($rec_on,$table).'"');
356 if ($GLOBALS['BE_USER']->workspace===0) { // Only edit online records if in ONLINE workspace:
357 $icon = $this->doc->wrapClickMenuOnIcon($icon, $table, $rec_on['uid'], 2, '', '+edit,view,info,delete');
358 }
359
360 // MAIN CELL / Online version display:
361 // Create the main cells which will span over the number of versions there is. If the table is "pages" then it will show the page tree icon and title (which was not shown by the code above)
362 $verLinkUrl = t3lib_extMgm::isLoaded('version') && $TCA[$table]['ctrl']['versioningWS'];
363 $origElement = $icon.
364 ($verLinkUrl ? '<a href="'.htmlspecialchars($this->doc->backPath.t3lib_extMgm::extRelPath('version').'cm1/index.php?table='.$table.'&uid='.$rec_on['uid']).'">' : '').
365 t3lib_BEfunc::getRecordTitle($table,$rec_on,TRUE).
366 ($verLinkUrl ? '</a>' : '');
367 $mainCell_rowSpan = count($recs)>1 ? ' rowspan="'.count($recs).'"' : '';
368 $mainCell = $table==='pages' ? '
369 <td class="bgColor4-20" nowrap="nowrap"'.$mainCell_rowSpan.'>'.
370 $this->displayWorkspaceOverview_pageTreeIconTitle($k,$pArray[$k],$c).
371 '</td>' : '
372 <td class="bgColor"'.$mainCell_rowSpan.'></td>';
373 $mainCell.= '
374 <td align="center"'.$mainCell_rowSpan.'>'.$this->formatVerId($rec_on['t3ver_id']).'</td>
375 <td nowrap="nowrap"'.$mainCell_rowSpan.'>'.
376 $origElement.
377 '###SUB_ELEMENTS###'. // For substitution with sub-elements, if any.
378 '</td>';
379
380 // Offline versions display:
381 // Traverse the versions of the element
382 foreach($recs as $rec) {
383
384 // Get the offline version record:
385 $rec_off = t3lib_BEfunc::getRecord($table,$rec['uid']);
386
387 // Prepare swap-mode values:
388 if ($table==='pages' && $rec_off['t3ver_swapmode']!=-1) {
389 if ($rec_off['t3ver_swapmode']>0) {
390 $vType = 'branch'; // Do not translate!
391 } else {
392 $vType = 'page'; // Do not translate!
393 }
394 } else {
395 $vType = 'element'; // Do not translate!
396 }
397
398 // Get icon:
399 $icon = t3lib_iconWorks::getIconImage($table, $rec_off, $this->doc->backPath, ' align="top" title="'.t3lib_BEfunc::getRecordIconAltText($rec_off,$table).'"');
400 $tempUid = ($table != 'pages' || $vType==='branch' || $GLOBALS['BE_USER']->workspace == 0 ? $rec_off['uid'] : $rec_on['uid']);
401 $icon = $this->doc->wrapClickMenuOnIcon($icon, $table, $tempUid, 2, '', '+edit,' . ($table == 'pages' ? 'view,info,' : '') . 'delete');
402
403 // Prepare diff-code:
404 if ($this->diff) {
405 $diffCode = '';
406 list($diffHTML,$diffPct) = $this->createDiffView($table, $rec_off, $rec_on);
407 if ($rec_on['t3ver_state']==1) { // New record:
408 $diffCode.= $this->doc->icons(1).$LANG->getLL('label_newrecord').'<br/>';
409 $diffCode.= $diffHTML;
410 } elseif ($rec_off['t3ver_state']==2) {
411 $diffCode.= $this->doc->icons(2).'Deleted element<br/>';
412 $diffCode.= $this->doc->icons(2).$LANG->getLL('label_deletedrecord').'<br/>';
413 } else {
414 $diffCode.= ($diffPct<0 ? 'N/A' : ($diffPct ? $diffPct.'% change:' : ''));
415 $diffCode.= ($diffPct<0 ? $LANG->getLL('label_notapplicable') : ($diffPct ? sprintf($LANG->getLL('label_percentchange'), $diffPct) : ''));
416 $diffCode.= $diffHTML;
417 }
418
419 } else $diffCode = '';
420
421 switch($vType) {
422 case 'element':
423 $swapLabel = ' ['.$LANG->getLL('label_element').']';
424 $swapClass = 'ver-element'; // Do not translate!
425 $warnAboutVersions_nonPages = $warnAboutVersions_page; // Setting this if sub elements are found with a page+content (must be rendered prior to this of course!)
426 break;
427 case 'page':
428 $swapLabel = ' ['.$LANG->getLL('label_page').']';
429 $swapClass = 'ver-page'; // Do not translate!
430 $warnAboutVersions_page = !$this->showWorkspaceCol; // This value is true only if multiple workspaces are shown and we need the opposite here.
431 break;
432 case 'branch':
433 $swapLabel = ' ['.$LANG->getLL('label_branch').']';
434 $swapClass = 'ver-branch'; // Do not translate!
435 $warnAboutVersions_next = !$this->showWorkspaceCol; // This value is true only if multiple workspaces are shown and we need the opposite here.
436 break;
437 }
438
439 // Modify main cell based on first version shown:
440 $subElements = array();
441 if ($table==='pages' && $rec_off['t3ver_swapmode']!=-1 && $mainCell) { // For "Page" and "Branch" swap modes where $mainCell is still carrying content (only first version)
442 $subElements['on'] = $this->subElements($rec_on['uid'], $rec_off['t3ver_swapmode']);
443 $subElements['off'] = $this->subElements($rec_off['uid'],$rec_off['t3ver_swapmode'],$rec_on['uid']);
444 }
445 $mainCell = str_replace('###SUB_ELEMENTS###', $subElements['on'], $mainCell);
446
447 // Create version element:
448 $versionsInOtherWS = $this->versionsInOtherWS($table, $rec_on['uid']);
449 $versionsInOtherWSWarning = $versionsInOtherWS && $GLOBALS['BE_USER']->workspace!==0 ? '<br/>'.$this->doc->icons(2).$LANG->getLL('label_otherversions').' '.$versionsInOtherWS : '';
450 $multipleWarning = (!$mainCell && $GLOBALS['BE_USER']->workspace!==0? '<br/>'.$this->doc->icons(3).'<b>'.$LANG->getLL('label_multipleversions').'</b>' : '');
451 $verWarning = $warnAboutVersions || ($warnAboutVersions_nonPages && $GLOBALS['TCA'][$table]['ctrl']['versioning_followPages'])? '<br/>'.$this->doc->icons(3).'<b>'.$LANG->getLL('label_nestedversions').'</b>' : '';
452 $verElement = $icon.
453 '<a href="'.htmlspecialchars($this->doc->backPath.t3lib_extMgm::extRelPath('version').'cm1/index.php?id='.($table==='pages'?$rec_on['uid']:$rec_on['pid']).'&details='.rawurlencode($table.':'.$rec_off['uid']).'&returnUrl='.rawurlencode(t3lib_div::getIndpEnv('REQUEST_URI'))).'">'.
454 t3lib_BEfunc::getRecordTitle($table,$rec_off,TRUE).
455 '</a>'.
456 $versionsInOtherWSWarning.
457 $multipleWarning.
458 $verWarning;
459 if ($diffCode) {
460 $verElement = '
461 <table border="0" cellpadding="0" cellspacing="0" class="ver-verElement">
462 <tr>
463 <td nowrap="nowrap" width="180">'.$verElement.'&nbsp;&nbsp;</td>
464 <td class="c-diffCell">'.$diffCode.'</td>
465 </tr>
466 </table>';
467 }
468
469 // Create version cell:
470 $verCell = '
471 <td align="center">'.$this->formatVerId($rec_off['t3ver_id']).'</td>
472 <td nowrap="nowrap">'.
473 $verElement.
474 $subElements['off'].
475 '</td>';
476
477 // Compile table row:
478 $tableRows[] = '
479 <tr class="bgColor4">
480 '.$mainCell.$verCell.'
481 <td nowrap="nowrap">'.$this->showStageChangeLog($table,$rec_off['uid'],$this->displayWorkspaceOverview_stageCmd($table,$rec_off)).'</td>
482 <td nowrap="nowrap" class="'.$swapClass.'">'.
483 $this->displayWorkspaceOverview_commandLinks($table,$rec_on,$rec_off,$vType).
484 htmlspecialchars($swapLabel).
485 '</td>
486 <td nowrap="nowrap">'.htmlspecialchars($this->formatCount($rec_off['t3ver_count'])).'</td>'. // Lifecycle
487 ($this->showWorkspaceCol ? '
488 <td nowrap="nowrap">'.htmlspecialchars($this->formatWorkspace($rec_off['t3ver_wsid'])).'</td>' : '').'
489 </tr>';
490
491 // Reset the main cell:
492 $mainCell = '';
493 }
494 }
495 }
496 }
497 // Call recursively for sub-rows:
498 $tableRows = $this->displayWorkspaceOverview_list($pArray[$k.'.'], $tableRows, $c+1, $warnAboutVersions_next);
499 }
500 }
501 }
502 return $tableRows;
503 }
504
505 /**
506 * Create indentation, icon and title for the page tree identification for the list.
507 *
508 * @param integer Page UID (record will be looked up again)
509 * @param string Page title
510 * @param integer Depth counter from displayWorkspaceOverview_list() used to indent the icon and title
511 * @return string HTML content
512 */
513 function displayWorkspaceOverview_pageTreeIconTitle($pageUid, $title, $indentCount) {
514 $pRec = t3lib_BEfunc::getRecord('pages',$pageUid);
515 return '<img src="clear.gif" width="1" height="1" hspace="'.($indentCount * $this->pageTreeIndent).'" align="top" alt="" />'. // Indenting page tree
516 t3lib_iconWorks::getIconImage('pages',$pRec,$this->doc->backPath,' align="top" title="'.t3lib_BEfunc::getRecordIconAltText($pRec,'pages').'"').
517 htmlspecialchars(t3lib_div::fixed_lgd_cs($title,$this->pageTreeIndent_titleLgd)).
518 '&nbsp;&nbsp;';
519 }
520
521 /**
522 * Formatting the version number for HTML output
523 *
524 * @param integer Version number
525 * @return string Version number for output
526 */
527 function formatVerId($verId) {
528 return '1.'.$verId;
529 }
530
531
532 /**
533 * Formatting workspace ID into a visual label
534 *
535 * @param integer Workspace ID
536 * @return string Workspace title
537 */
538 function formatWorkspace($wsid) {
539
540 // Render, if not cached:
541 if (!isset($this->formatWorkspace_cache[$wsid])) {
542 switch($wsid) {
543 case -1:
544 $this->formatWorkspace_cache[$wsid] = '[Draft]';
545 break;
546 case 0:
547 $this->formatWorkspace_cache[$wsid] = ''; // Does not output anything for ONLINE because it might confuse people to think that the elemnet IS online which is not the case - only that it exists as an offline version in the online workspace...
548 break;
549 default:
550 list($titleRec) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('title','sys_workspace','uid='.intval($wsid).t3lib_BEfunc::deleteClause('sys_workspace'));
551 $this->formatWorkspace_cache[$wsid] = '['.$wsid.'] '.$titleRec['title'];
552 break;
553 }
554 }
555
556 return $this->formatWorkspace_cache[$wsid];
557 }
558
559
560 /**
561 * Create visual difference view of two records. Using t3lib_diff library
562 *
563 * @param string Table name
564 * @param array New version record (green)
565 * @param array Old version record (red)
566 * @return array Array with two keys (0/1) with HTML content / percentage integer (if -1, then it means N/A) indicating amount of change
567 */
568 function createDiffView($table, $diff_1_record, $diff_2_record) {
569 global $TCA, $LANG;
570
571 // Initialize:
572 $pctChange = 'N/A';
573
574 // Check that records are arrays:
575 if (is_array($diff_1_record) && is_array($diff_2_record)) {
576
577 // Load full table description and initialize diff-object:
578 t3lib_div::loadTCA($table);
579 $t3lib_diff_Obj = t3lib_div::makeInstance('t3lib_diff');
580
581 // Add header row:
582 $tRows = array();
583 $tRows[] = '
584 <tr class="bgColor5 tableheader">
585 <td>' . $LANG->getLL('diffview_label_field_name') . '</td>
586 <td width="98%" nowrap="nowrap">' . $LANG->getLL('diffview_label_colored_diff_view') . '</td>
587 </tr>
588 ';
589
590 // Initialize variables to pick up string lengths in:
591 $allStrLen = 0;
592 $diffStrLen = 0;
593
594 // Traversing the first record and process all fields which are editable:
595 foreach($diff_1_record as $fN => $fV) {
596 if ($TCA[$table]['columns'][$fN] && $TCA[$table]['columns'][$fN]['config']['type']!='passthrough' && !t3lib_div::inList('t3ver_label',$fN)) {
597
598 // Check if it is files:
599 $isFiles = FALSE;
600 if (strcmp(trim($diff_1_record[$fN]),trim($diff_2_record[$fN])) &&
601 $TCA[$table]['columns'][$fN]['config']['type']=='group' &&
602 $TCA[$table]['columns'][$fN]['config']['internal_type']=='file') {
603
604 // Initialize:
605 $uploadFolder = $TCA[$table]['columns'][$fN]['config']['uploadfolder'];
606 $files1 = array_flip(t3lib_div::trimExplode(',', $diff_1_record[$fN],1));
607 $files2 = array_flip(t3lib_div::trimExplode(',', $diff_2_record[$fN],1));
608
609 // Traverse filenames and read their md5 sum:
610 foreach($files1 as $filename => $tmp) {
611 $files1[$filename] = @is_file(PATH_site.$uploadFolder.'/'.$filename) ? md5(t3lib_div::getUrl(PATH_site.$uploadFolder.'/'.$filename)) : $filename;
612 }
613 foreach($files2 as $filename => $tmp) {
614 $files2[$filename] = @is_file(PATH_site.$uploadFolder.'/'.$filename) ? md5(t3lib_div::getUrl(PATH_site.$uploadFolder.'/'.$filename)) : $filename;
615 }
616
617 // Implode MD5 sums and set flag:
618 $diff_1_record[$fN] = implode(' ',$files1);
619 $diff_2_record[$fN] = implode(' ',$files2);
620 $isFiles = TRUE;
621 }
622
623 // If there is a change of value:
624 if (strcmp(trim($diff_1_record[$fN]),trim($diff_2_record[$fN]))) {
625
626
627 // Get the best visual presentation of the value and present that:
628 $val1 = t3lib_BEfunc::getProcessedValue($table,$fN,$diff_2_record[$fN],0,1);
629 $val2 = t3lib_BEfunc::getProcessedValue($table,$fN,$diff_1_record[$fN],0,1);
630
631 // Make diff result and record string lenghts:
632 $diffres = $t3lib_diff_Obj->makeDiffDisplay($val1,$val2,$isFiles?'div':'span');
633 $diffStrLen+= $t3lib_diff_Obj->differenceLgd;
634 $allStrLen+= strlen($val1.$val2);
635
636 // If the compared values were files, substituted MD5 hashes:
637 if ($isFiles) {
638 $allFiles = array_merge($files1,$files2);
639 foreach($allFiles as $filename => $token) {
640 if (strlen($token)==32 && strstr($diffres,$token)) {
641 $filename =
642 t3lib_BEfunc::thumbCode(array($fN=>$filename),$table,$fN,$this->doc->backPath).
643 $filename;
644 $diffres = str_replace($token,$filename,$diffres);
645 }
646 }
647 }
648
649 // Add table row with result:
650 $tRows[] = '
651 <tr class="bgColor4">
652 <td>'.htmlspecialchars($GLOBALS['LANG']->sL(t3lib_BEfunc::getItemLabel($table,$fN))).'</td>
653 <td width="98%">'.$diffres.'</td>
654 </tr>
655 ';
656 } else {
657 // Add string lengths even if value matched - in this was the change percentage is not high if only a single field is changed:
658 $allStrLen+=strlen($diff_1_record[$fN].$diff_2_record[$fN]);
659 }
660 }
661 }
662
663 // Calculate final change percentage:
664 $pctChange = $allStrLen ? ceil($diffStrLen*100/$allStrLen) : -1;
665
666 // Create visual representation of result:
667 if (count($tRows)>1) {
668 $content.= '<table border="0" cellpadding="1" cellspacing="1" class="diffTable">'.implode('',$tRows).'</table>';
669 } else {
670 $content.= '<span class="nobr">'.$this->doc->icons(1).$LANG->getLL('diffview_complete_match').'</span>';
671 }
672 } else $content.= $this->doc->icons(3).$LANG->getLL('diffview_cannot_find_records');
673
674 // Return value:
675 return array($content,$pctChange);
676 }
677
678 /**
679 * Looking for versions of a record in other workspaces than the current
680 *
681 * @param string Table name
682 * @param integer Record uid
683 * @return string List of other workspace IDs
684 */
685 function versionsInOtherWS($table, $uid) {
686 // Check for duplicates:
687 // Select all versions of record NOT in this workspace:
688 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
689 't3ver_wsid',
690 $table,
691 'pid=-1
692 AND t3ver_oid='.intval($uid).'
693 AND t3ver_wsid!='.intval($GLOBALS['BE_USER']->workspace).// TODO should be $this->workspaceId here???
694 ' AND (t3ver_wsid=-1 OR t3ver_wsid>0)'.
695 t3lib_BEfunc::deleteClause($table),
696 '',
697 't3ver_wsid',
698 '',
699 't3ver_wsid'
700 );
701 if (count($rows)) {
702 return implode(',',array_keys($rows));
703 }
704 }
705
706 /**
707 * Looks up stage changes for version and displays a formatted view on mouseover.
708 *
709 * @param string Table name
710 * @param integer Record ID
711 * @param string HTML string to wrap the mouseover around (should be stage change links)
712 * @return string HTML code.
713 */
714 function showStageChangeLog($table,$id,$stageCommands) {
715 global $LANG;
716
717 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
718 'log_data,tstamp,userid',
719 'sys_log',
720 'action=6 and details_nr=30
721 AND tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($table,'sys_log').'
722 AND recuid='.intval($id)
723 );
724
725 $entry = array();
726 foreach($rows as $dat) {
727 $data = unserialize($dat['log_data']);
728 $username = $this->be_user_Array[$dat['userid']] ? $this->be_user_Array[$dat['userid']]['username'] : '['.$dat['userid'].']';
729
730 switch($data['stage']) {
731 case 1:
732 $text = $LANG->getLL('stage_sent_to_review');
733 break;
734 case 10:
735 $text = $LANG->getLL('stage_approved_for_publish');
736 break;
737 case -1:
738 $text = $LANG->getLL('stage_rejected');
739 break;
740 case 0:
741 $text = $LANG->getLL('stage_reset_to_editing');
742 break;
743 default:
744 $text = $LANG->getLL('stage_undefined');
745 break;
746 }
747 $text = t3lib_BEfunc::datetime($dat['tstamp']).': ' . sprintf($text, $username);
748 $text.= ($data['comment']?'<br/>' . $LANG->getLL('stage_label_user_comment'). ' <em>'.htmlspecialchars($data['comment']).'</em>':'');
749
750 $entry[] = $text;
751 }
752
753 return count($entry) ? '<span onmouseover="document.getElementById(\'log_'.$table.$id.'\').style.visibility = \'visible\';" onmouseout="document.getElementById(\'log_'.$table.$id.'\').style.visibility = \'hidden\';">'.$stageCommands.' ('.count($entry).')</span>'.
754 '<div class="logLayer" style="visibility: hidden; position: absolute;" id="log_'.$table.$id.'">'.implode('<hr/>',$entry).'</div>' : $stageCommands;
755 }
756
757 /**
758 * Links to publishing etc of a version
759 *
760 * @param string Table name
761 * @param array Online record
762 * @param array Offline record (version)
763 * @param string Swap type, "branch", "page" or "element"
764 * @return string HTML content, mainly link tags and images.
765 */
766 function displayWorkspaceOverview_commandLinks($table,&$rec_on,&$rec_off,$vType) {
767 global $LANG;
768
769 if ($this->publishAccess && (!($GLOBALS['BE_USER']->workspaceRec['publish_access']&1) || (int)$rec_off['t3ver_stage']===10)) {
770 $actionLinks =
771 '<a href="'.htmlspecialchars($this->doc->issueCommand(
772 '&cmd['.$table.']['.$rec_on['uid'].'][version][action]=swap'.
773 '&cmd['.$table.']['.$rec_on['uid'].'][version][swapWith]='.$rec_off['uid']
774 )).'">'.
775 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/insert1.gif','width="14" height="14"').' alt="" align="top" title="' . $LANG->getLL('img_title_publish') . '" />'.
776 '</a>';
777 if ($GLOBALS['BE_USER']->workspaceSwapAccess() && (int)$rec_on['t3ver_state']!==1 && (int)$rec_off['t3ver_state']!==2) {
778 $actionLinks.=
779 '<a href="'.htmlspecialchars($this->doc->issueCommand(
780 '&cmd['.$table.']['.$rec_on['uid'].'][version][action]=swap'.
781 '&cmd['.$table.']['.$rec_on['uid'].'][version][swapWith]='.$rec_off['uid'].
782 '&cmd['.$table.']['.$rec_on['uid'].'][version][swapIntoWS]=1'
783 )).'">'.
784 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/swap.png','width="14" height="14"').' alt="" align="top" title="' . $LANG->getLL('img_title_swap') . '" />'.
785 '</a>';
786 }
787 }
788
789 if (!$GLOBALS['BE_USER']->workspaceCannotEditOfflineVersion($table,$rec_off)) {
790 if ($GLOBALS['BE_USER']->workspace!==0) {
791 // Release
792 $confirm = $LANG->JScharCode($LANG->getLL('remove_from_ws_confirmation'));
793 $actionLinks.=
794 '<a href="'.htmlspecialchars($this->doc->issueCommand('&cmd['.$table.']['.$rec_off['uid'].'][version][action]=clearWSID')).'" onclick="return confirm(' . $confirm . ');">'.
795 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/group_clear.gif','width="14" height="14"').' alt="" align="top" title="' . $LANG->getLL('img_title_remove_from_ws') . '" />'.
796 '</a>';
797 }
798
799 // Edit
800 if ($table==='pages' && $vType!=='element') {
801 $tempUid = ($vType==='branch' || $GLOBALS['BE_USER']->workspace===0 ? $rec_off['uid'] : $rec_on['uid']);
802 $actionLinks.=
803 '<a href="#" onclick="top.loadEditId('.$tempUid.');top.goToModule(\''.$this->pageModule.'\'); return false;">'.
804 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,t3lib_extMgm::extRelPath('cms').'layout/layout.gif','width="14" height="12"').' title="' . $LANG->getLL('img_title_edit_page') . '" alt="" />'.
805 '</a>';
806 } else {
807 $params = '&edit['.$table.']['.$rec_off['uid'].']=edit';
808 $actionLinks.=
809 '<a href="#" onclick="'.htmlspecialchars(t3lib_BEfunc::editOnClick($params,$this->doc->backPath)).'">'.
810 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/edit2.gif','width="12" height="12"').' title="' . $LANG->getLL('img_title_edit_element') . '" alt="" />'.
811 '</a>';
812 }
813 }
814
815 // History/Log
816 $actionLinks.=
817 '<a href="'.htmlspecialchars($this->doc->backPath.'show_rechis.php?element='.rawurlencode($table.':'.$rec_off['uid']).'&returnUrl='.rawurlencode(t3lib_div::getIndpEnv('REQUEST_URI'))).'">'.
818 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/history2.gif','width="13" height="12"').' title="' . $LANG->getLL('img_title_show_log') . '" alt="" />'.
819 '</a>';
820
821 // View
822 if ($table==='pages') {
823 $tempUid = ($vType==='branch' || $GLOBALS['BE_USER']->workspace===0 ? $rec_off['uid'] : $rec_on['uid']);
824 $actionLinks.=
825 '<a href="#" onclick="'.htmlspecialchars(t3lib_BEfunc::viewOnClick($tempUid,$this->doc->backPath,t3lib_BEfunc::BEgetRootLine($tempUid))).'">'.
826 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/zoom.gif','width="12" height="12"').' title="" alt="" />'.
827 '</a>';
828 }
829
830 return $actionLinks;
831 }
832
833 /**
834 * Format publishing count for version (lifecycle state)
835 *
836 * @param integer t3ver_count value (number of times it has been online)
837 * @return string String translation of count.
838 */
839 function formatCount($count) {
840 global $LANG;
841
842 // Render, if not cached:
843 if (!isset($this->formatCount_cache[$count])) {
844 switch($count) {
845 case 0:
846 $this->formatCount_cache[$count] = $LANG->getLL('workspace_list_publishing_count_draft');
847 break;
848 case 1:
849 $this->formatCount_cache[$count] = $LANG->getLL('workspace_list_publishing_count_archive');
850 break;
851 default:
852 $this->formatCount_cache[$count] = sprintf($LANG->getLL('workspace_list_publishing_count'), $count);
853 break;
854 }
855 }
856
857 return $this->formatCount_cache[$count];
858 }
859
860
861 /**
862 * Creates display of sub elements of a page when the swap mode is either "Page" or "Branch" (0 / ALL)
863 *
864 * @param integer Page uid (for either online or offline version, but it MUST have swapmode/treeLevel set to >0 (not -1 indicating element versioning)
865 * @param integer The treeLevel value, >0 indicates "branch" while 0 means page+content. (-1 would have meant element versioning, but that should never happen for a call to this function!)
866 * @param integer For offline versions; This is t3ver_oid, the original ID of the online page.
867 * @return string HTML content.
868 */
869 function subElements($uid,$treeLevel,$origId=0) {
870 global $TCA, $LANG;
871
872 if ($GLOBALS['BE_USER']->workspace===0 || !$this->expandSubElements) { // In online workspace we have a reduced view because otherwise it will bloat the listing:
873 return '<br/>
874 <img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/ol/joinbottom.gif','width="18" height="16"').' align="top" alt="" title="" />'.
875 ($origId ?
876 '<a href="'.htmlspecialchars($this->doc->backPath.t3lib_extMgm::extRelPath('version').'cm1/index.php?id='.$uid.'&details='.rawurlencode('pages:'.$uid).'&returnUrl='.rawurlencode(t3lib_div::getIndpEnv('REQUEST_URI'))).'">'.
877 '<span class="typo3-dimmed"><em>['.$LANG->getLL('label_subelementsdetails').']</em><span></a>' :
878 '<span class="typo3-dimmed"><em>['.$LANG->getLL('label_subelements').']</em><span>');
879 } else { // For an offline workspace, show sub elements:
880
881 $tCell = array();
882
883 // Find records that follow pages when swapping versions:
884 $recList = array();
885 foreach($TCA as $tN => $tCfg) {
886 if ($tN!='pages' && ($treeLevel>0 || $TCA[$tN]['ctrl']['versioning_followPages'])) {
887 $this->subElements_getNonPageRecords($tN, $uid, $recList);
888 }
889 }
890
891 // Render records collected above:
892 $elCount = count($recList)-1;
893 foreach($recList as $c => $comb) {
894 list($tN,$rec) = $comb;
895
896 $this->subElements_renderItem(
897 $tCell,
898 $tN,
899 $uid,
900 $rec,
901 $origId,
902 $c==$elCount && $treeLevel==0 ? 1 : 0, // If true, will show bottom-join icon.
903 ''
904 );
905 }
906
907 // For branch, dive into the subtree:
908 if ($treeLevel>0) {
909
910 // Drawing tree:
911 $tree = t3lib_div::makeInstance('t3lib_pageTree');
912 $tree->init('AND '.$GLOBALS['BE_USER']->getPagePermsClause(1));
913 $tree->makeHTML = 2; // 2=Also rendering depth-data into the result array
914 $tree->getTree($uid, 99, '');
915
916 // Traverse page tree:
917 foreach($tree->tree as $data) {
918
919 // Render page in table cell:
920 $this->subElements_renderItem(
921 $tCell,
922 'pages',
923 $uid,
924 t3lib_BEfunc::getRecord('pages',$data['row']['uid']), // Needs all fields, at least more than what is given in $data['row']...
925 $origId,
926 2, // 2=the join icon and icon for the record is not rendered for pages (where all is in $data['HTML']
927 $data['HTML']
928 );
929
930 // Find all records from page and collect in $recList:
931 $recList = array();
932 foreach($TCA as $tN => $tCfg) {
933 if ($tN!=='pages') {
934 $this->subElements_getNonPageRecords($tN, $data['row']['uid'], $recList);
935 }
936 }
937
938 // Render records collected above:
939 $elCount = count($recList)-1;
940 foreach($recList as $c => $comb) {
941 list($tN,$rec) = $comb;
942
943 $this->subElements_renderItem(
944 $tCell,
945 $tN,
946 $uid,
947 $rec,
948 $origId,
949 $c==$elCount?1:0, // If true, will show bottom-join icon.
950 $data['HTML_depthData']
951 );
952 }
953 }
954 }
955
956 return '
957 <!-- Sub-element tree for versions -->
958 <table border="0" cellpadding="0" cellspacing="1" class="ver-subtree">
959 '.implode('',$tCell).'
960 </table>';
961 }
962 }
963
964 /**
965 * Select records from a table and add them to recList
966 *
967 * @param string Table name (from TCA)
968 * @param integer PID to select records from
969 * @param array Array where records are accumulated, passed by reference
970 * @return void
971 */
972 function subElements_getNonPageRecords($tN, $uid, &$recList) {
973 global $TCA;
974
975 $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
976 '*',
977 $tN,
978 'pid='.intval($uid).
979 ($TCA[$tN]['ctrl']['versioningWS'] ? ' AND t3ver_state=0' : '').
980 t3lib_BEfunc::deleteClause($tN),
981 '',
982 $TCA[$tN]['ctrl']['sortby'] ? $TCA[$tN]['ctrl']['sortby'] : $GLOBALS['TYPO3_DB']->stripOrderBy($TCA[$tN]['ctrl']['default_sortby'])
983 );
984
985 foreach($records as $rec) {
986 $recList[] = array($tN,$rec);
987 }
988 }
989
990 /**
991 * Render a single item in a subelement list into a table row:
992 *
993 * @param array Table rows, passed by reference
994 * @param string Table name
995 * @param integer Page uid for which the subelements are selected/shown
996 * @param array Row of element in list
997 * @param integer The uid of the online version of $uid. If zero it means we are drawing a row for the online version itself while a value means we are drawing display for an offline version.
998 * @param integer Mode of icon display: 0=not the last, 1= is the last in list (make joinbottom icon then), 2=do not shown icons are all (for pages from the page tree already rendered)
999 * @param string Prefix HTML data (icons for tree rendering)
1000 * @return void (Content accumulated in $tCell!)
1001 */
1002 function subElements_renderItem(&$tCell,$tN,$uid,$rec,$origId,$iconMode,$HTMLdata) {
1003 global $TCA;
1004
1005 // Initialize:
1006 $origUidFields = $TCA[$tN]['ctrl']['origUid'];
1007 $diffCode = '';
1008
1009 if ($origUidFields) { // If there is a field for this table with original uids we will use that to connect records:
1010 if (!$origId) { // In case we are displaying the online originals:
1011 $this->targets['orig_'.$uid.'_'.$tN.'_'.$rec['uid']] = $rec; // Build up target array (important that
1012 $tdParams = ' id="orig_'.$uid.'_'.$tN.'_'.$rec['uid'].'" class="typo3-ver"'; // Setting ID of the table row
1013 } else { // Version branch:
1014 if ($this->targets['orig_'.$origId.'_'.$tN.'_'.$rec[$origUidFields]]) { // If there IS a corresponding original record...:
1015
1016 // Prepare Table row parameters:
1017 $this->addHlSubelementsScript = true;
1018 $tdParams = ' onmouseover="hlSubelements(\''.$origId.'_'.$tN.'_'.$rec[$origUidFields].'\', \''.$uid.'_'.$tN.'_'.$rec[$origUidFields].'\', 1, '.($this->diff==2?1:0).');"'.
1019 ' onmouseout="hlSubelements(\''.$origId.'_'.$tN.'_'.$rec[$origUidFields].'\', \''.$uid.'_'.$tN.'_'.$rec[$origUidFields].'\', 0, '.($this->diff==2?1:0).');"'.
1020 ' id="ver_'.$uid.'_'.$tN.'_'.$rec[$origUidFields].'" class="typo3-ver"';
1021
1022 // Create diff view:
1023 if ($this->diff) {
1024 list($diffHTML,$diffPct) = $this->createDiffView($tN, $rec, $this->targets['orig_'.$origId.'_'.$tN.'_'.$rec[$origUidFields]]);
1025
1026 if ($this->diff==2) {
1027 $diffCode =
1028 ($diffPct ? '<span class="nobr">'.$diffPct.'% change</span>' : '-').
1029 '<div style="visibility: hidden; position: absolute;" id="diff_'.$uid.'_'.$tN.'_'.$rec[$origUidFields].'" class="diffLayer">'.
1030 $diffHTML.
1031 '</div>';
1032 } else {
1033 $diffCode =
1034 ($diffPct<0 ? 'N/A' : ($diffPct ? $diffPct.'% change:' : '')).
1035 $diffHTML;
1036 }
1037 }
1038
1039 // Unsetting the target fields allows us to mark all originals without a version in the subtree (see ->markupNewOriginals())
1040 unset($this->targets['orig_'.$origId.'_'.$tN.'_'.$rec[$origUidFields]]);
1041 } else { // No original record, so must be new:
1042 $tdParams = ' class="typo3-ver-new"';
1043 }
1044 }
1045 } else { // If no original uid column is supported for this table we are forced NOT to display any diff or highlighting.
1046 $tdParams = ' class="typo3-ver-noComp"';
1047 }
1048
1049 // Compile the cell:
1050 $tCell[] = '
1051 <tr'.$tdParams.'>
1052 <td class="iconTitle">'.
1053 $HTMLdata.
1054 ($iconMode < 2 ?
1055 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/ol/join'.($iconMode ? 'bottom' : '').'.gif','width="18" height="16"').' alt="" />'.
1056 t3lib_iconWorks::getIconImage($tN, $rec, $this->doc->backPath,'') : '').
1057 t3lib_BEfunc::getRecordTitle($tN, $rec, TRUE).
1058 '</td>
1059 <td class="cmdCell">'.
1060 $this->displayWorkspaceOverview_commandLinksSub($tN,$rec,$origId).
1061 '</td>'.($origId ? '<td class="diffCell">'.
1062 $diffCode.
1063 '</td>':'').'
1064 </tr>';
1065 }
1066
1067 /**
1068 * Links to publishing etc of a version
1069 *
1070 * @param string Table name
1071 * @param array Record array
1072 * @param integer The uid of the online version of $uid. If zero it means we are drawing a row for the online version itself while a value means we are drawing display for an offline version.
1073 * @return string HTML content, mainly link tags and images.
1074 */
1075 function displayWorkspaceOverview_commandLinksSub($table,$rec,$origId) {
1076 global $LANG;
1077
1078 $uid = $rec['uid'];
1079 if ($origId || $GLOBALS['BE_USER']->workspace===0) {
1080 if (!$GLOBALS['BE_USER']->workspaceCannotEditRecord($table,$rec)) {
1081 // Edit
1082 if ($table==='pages') {
1083 $actionLinks.=
1084 '<a href="#" onclick="top.loadEditId('.$uid.');top.goToModule(\''.$this->pageModule.'\'); return false;">'.
1085 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,t3lib_extMgm::extRelPath('cms').'layout/layout.gif','width="14" height="12"').' title="' . $LANG->getLL('img_title_edit_page') . '" alt="" />'.
1086 '</a>';
1087 } else {
1088 $params = '&edit['.$table.']['.$uid.']=edit';
1089 $actionLinks.=
1090 '<a href="#" onclick="'.htmlspecialchars(t3lib_BEfunc::editOnClick($params,$this->doc->backPath)).'">'.
1091 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/edit2.gif','width="12" height="12"').' title="' . $LANG->getLL('img_title_edit_element') . '" alt="" />'.
1092 '</a>';
1093 }
1094 }
1095
1096 // History/Log
1097 $actionLinks.=
1098 '<a href="'.htmlspecialchars($this->doc->backPath.'show_rechis.php?element='.rawurlencode($table.':'.$uid).'&returnUrl='.rawurlencode(t3lib_div::getIndpEnv('REQUEST_URI'))).'">'.
1099 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/history2.gif','width="13" height="12"').' title="' . $LANG->getLL('img_title_show_log') . '" alt="" />'.
1100 '</a>';
1101 }
1102
1103 // View
1104 if ($table==='pages') {
1105 $actionLinks.=
1106 '<a href="#" onclick="'.htmlspecialchars(t3lib_BEfunc::viewOnClick($uid,$this->doc->backPath,t3lib_BEfunc::BEgetRootLine($uid))).'">'.
1107 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/zoom.gif','width="12" height="12"').' title="" alt="" />'.
1108 '</a>';
1109 }
1110
1111 return $actionLinks;
1112 }
1113
1114
1115 /**
1116 * Links to stage change of a version
1117 *
1118 * @param string Table name
1119 * @param array Offline record (version)
1120 * @return string HTML content, mainly link tags and images.
1121 */
1122 function displayWorkspaceOverview_stageCmd($table,&$rec_off) {
1123 global $LANG;
1124
1125 switch((int)$rec_off['t3ver_stage']) {
1126 case 0:
1127 $sId = 1;
1128 $sLabel = $LANG->getLL('stage_editing');
1129 $color = '#666666'; // TODO Use CSS?
1130 $label = $LANG->getLL('label_commentforreviewer');
1131 $titleAttrib = $LANG->getLL('label_sendtoreview');
1132 break;
1133 case 1:
1134 $sId = 10;
1135 $sLabel = $LANG->getLL('label_review');
1136 $color = '#6666cc'; // TODO Use CSS?
1137 $label = $LANG->getLL('label_commentforpublisher');
1138 $titleAttrib = $LANG->getLL('label_approveforpublishing');
1139 break;
1140 case 10:
1141 $sLabel = $LANG->getLL('label_publish');
1142 $color = '#66cc66'; // TODO Use CSS?
1143 break;
1144 case -1:
1145 $sLabel = $this->doc->icons(2).$LANG->getLL('label_rejected');
1146 $sId = 0;
1147 $color = '#ff0000'; // TODO Use CSS?
1148 $label = $LANG->getLL('stage_label_user_comment');
1149 $titleAttrib = $LANG->getLL('label_resetstage');
1150 break;
1151 default:
1152 $sLabel = $LANG->getLL('label_undefined');
1153 $sId = 0;
1154 $color = '';
1155 break;
1156 }
1157 #debug($sId);
1158
1159 $raiseOk = !$GLOBALS['BE_USER']->workspaceCannotEditOfflineVersion($table,$rec_off);
1160
1161 if ($raiseOk && $rec_off['t3ver_stage']!=-1) {
1162 $onClick = 'var commentTxt=window.prompt("'.$LANG->getLL('explain_reject').'","");
1163 if (commentTxt!=null) {window.location.href="'.$this->doc->issueCommand(
1164 '&cmd['.$table.']['.$rec_off['uid'].'][version][action]=setStage'.
1165 '&cmd['.$table.']['.$rec_off['uid'].'][version][stageId]=-1'
1166 ).'&cmd['.$table.']['.$rec_off['uid'].'][version][comment]="+escape(commentTxt);}'.
1167 ' return false;';
1168 // Reject:
1169 $actionLinks.=
1170 '<a href="#" onclick="'.htmlspecialchars($onClick).'">'.
1171 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/down.gif','width="14" height="14"').' alt="" align="top" title="'.$LANG->getLL('label_reject').'" />'.
1172 '</a>';
1173 } else {
1174 // Reject:
1175 $actionLinks.=
1176 '<img src="'.$this->doc->backPath.'gfx/clear.gif" width="14" height="14" alt="" align="top" title="" />';
1177 }
1178
1179 // TODO Use CSS?
1180 $actionLinks.= '<span style="background-color: '.$color.'; color: white;">'.$sLabel.'</span>';
1181
1182 // Raise
1183 if ($raiseOk) {
1184 $onClick = 'var commentTxt=window.prompt("'.$label.'","");
1185 if (commentTxt!=null) {window.location.href="'.$this->doc->issueCommand(
1186 '&cmd['.$table.']['.$rec_off['uid'].'][version][action]=setStage'.
1187 '&cmd['.$table.']['.$rec_off['uid'].'][version][stageId]='.$sId
1188 ).'&cmd['.$table.']['.$rec_off['uid'].'][version][comment]="+escape(commentTxt);}'.
1189 ' return false;';
1190 if ($rec_off['t3ver_stage']!=10) {
1191 $actionLinks.=
1192 '<a href="#" onclick="'.htmlspecialchars($onClick).'">'.
1193 '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/up.gif','width="14" height="14"').' alt="" align="top" title="'.htmlspecialchars($titleAttrib).'" />'.
1194 '</a>';
1195
1196 $this->stageIndex[$sId][$table][] = $rec_off['uid'];
1197 }
1198 }
1199
1200 return $actionLinks;
1201 }
1202 }
1203
1204 // Include extension?
1205 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/mod/user/ws/class.wslib_gui.php']) {
1206 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/mod/user/ws/class.wslib_gui.php']);
1207 }
1208
1209 ?>