PageRenderTime 62ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/typo3/mod/user/ws/class.wslib_gui.php

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