PageRenderTime 70ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/typo3/sysext/version/class.tx_version_tcemain.php

https://github.com/andreaswolf/typo3-tceforms
PHP | 1423 lines | 809 code | 170 blank | 444 comment | 231 complexity | 4f42a48afb365888df93c2e25d033914 MD5 | raw file
Possible License(s): Apache-2.0, BSD-2-Clause, LGPL-3.0
  1. <?php
  2. /***************************************************************
  3. * Copyright notice
  4. *
  5. * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
  6. * (c) 2010-2011 Benjamin Mack (benni@typo3.org)
  7. *
  8. * All rights reserved
  9. *
  10. * This script is part of the TYPO3 project. The TYPO3 project is
  11. * free software; you can redistribute it and/or modify
  12. * it under the terms of the GNU General Public License as published by
  13. * the Free Software Foundation; either version 2 of the License, or
  14. * (at your option) any later version.
  15. *
  16. * The GNU General Public License can be found at
  17. * http://www.gnu.org/copyleft/gpl.html.
  18. * A copy is found in the textfile GPL.txt and important notices to the license
  19. * from the author is found in LICENSE.txt distributed with these scripts.
  20. *
  21. *
  22. * This script is distributed in the hope that it will be useful,
  23. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. * GNU General Public License for more details.
  26. *
  27. * This copyright notice MUST APPEAR in all copies of the script!
  28. ***************************************************************/
  29. /**
  30. *
  31. * Contains some parts for staging, versioning and workspaces
  32. * to interact with the TYPO3 Core Engine
  33. *
  34. */
  35. class tx_version_tcemain {
  36. /**
  37. * For accumulating information about workspace stages raised
  38. * on elements so a single mail is sent as notification.
  39. * previously called "accumulateForNotifEmail" in tcemain
  40. *
  41. * @var array
  42. */
  43. protected $notificationEmailInfo = array();
  44. /**
  45. * General comment, eg. for staging in workspaces
  46. *
  47. * @var string
  48. */
  49. protected $generalComment = '';
  50. /**
  51. * Contains remapped IDs.
  52. *
  53. * @var array
  54. */
  55. protected $remappedIds = array();
  56. /****************************
  57. ***** Cmdmap Hooks ******
  58. ****************************/
  59. /**
  60. * hook that is called before any cmd of the commandmap is executed
  61. *
  62. * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object
  63. * @return void
  64. */
  65. public function processCmdmap_beforeStart(t3lib_TCEmain $tcemainObj) {
  66. // Reset notification array
  67. $this->notificationEmailInfo = array();
  68. // Resolve dependencies of version/workspaces actions:
  69. $tcemainObj->cmdmap = $this->getCommandMap($tcemainObj, $tcemainObj->cmdmap)->process()->get();
  70. }
  71. /**
  72. * hook that is called when no prepared command was found
  73. *
  74. * @param string $command the command to be executed
  75. * @param string $table the table of the record
  76. * @param integer $id the ID of the record
  77. * @param mixed $value the value containing the data
  78. * @param boolean $commandIsProcessed can be set so that other hooks or
  79. * TCEmain knows that the default cmd doesn't have to be called
  80. * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object
  81. * @return void
  82. */
  83. public function processCmdmap($command, $table, $id, $value, &$commandIsProcessed, t3lib_TCEmain $tcemainObj) {
  84. // custom command "version"
  85. if ($command == 'version') {
  86. $commandWasProcessed = TRUE;
  87. $action = (string) $value['action'];
  88. switch ($action) {
  89. case 'new':
  90. // check if page / branch versioning is needed,
  91. // or if "element" version can be used
  92. $versionizeTree = -1;
  93. if (isset($value['treeLevels'])) {
  94. $versionizeTree = t3lib_div::intInRange($value['treeLevels'], -1, 100);
  95. }
  96. if ($table == 'pages' && $versionizeTree >= 0) {
  97. $this->versionizePages($id, $value['label'], $versionizeTree, $tcemainObj);
  98. } else {
  99. $tcemainObj->versionizeRecord($table, $id, $value['label']);
  100. }
  101. break;
  102. case 'swap':
  103. $this->version_swap($table, $id, $value['swapWith'], $value['swapIntoWS'], $tcemainObj);
  104. break;
  105. case 'clearWSID':
  106. $this->version_clearWSID($table, $id, FALSE, $tcemainObj);
  107. break;
  108. case 'flush':
  109. $this->version_clearWSID($table, $id, TRUE, $tcemainObj);
  110. break;
  111. case 'setStage':
  112. $elementIds = t3lib_div::trimExplode(',', $id, TRUE);
  113. foreach ($elementIds as $elementId) {
  114. $this->version_setStage($table, $elementId, $value['stageId'],
  115. (isset($value['comment']) && $value['comment'] ? $value['comment'] : $this->generalComment),
  116. TRUE,
  117. $tcemainObj,
  118. $value['notificationAlternativeRecipients']
  119. );
  120. }
  121. break;
  122. }
  123. }
  124. }
  125. /**
  126. * hook that is called AFTER all commands of the commandmap was
  127. * executed
  128. *
  129. * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object
  130. * @return void
  131. */
  132. public function processCmdmap_afterFinish(t3lib_TCEmain $tcemainObj) {
  133. // Empty accumulation array:
  134. foreach ($this->notificationEmailInfo as $notifItem) {
  135. $this->notifyStageChange($notifItem['shared'][0], $notifItem['shared'][1], implode(', ', $notifItem['elements']), 0, $notifItem['shared'][2], $tcemainObj, $notifItem['alternativeRecipients']);
  136. }
  137. // Reset notification array
  138. $this->notificationEmailInfo = array();
  139. // Reset remapped IDs
  140. $this->remappedIds = array();
  141. }
  142. /**
  143. * hook that is called AFTER all commands of the commandmap was
  144. * executed
  145. *
  146. * @param string $table the table of the record
  147. * @param integer $id the ID of the record
  148. * @param array $record The accordant database record
  149. * @param boolean $recordWasDeleted can be set so that other hooks or
  150. * TCEmain knows that the default delete action doesn't have to be called
  151. * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object
  152. * @return void
  153. */
  154. public function processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, t3lib_TCEmain $tcemainObj) {
  155. // only process the hook if it wasn't processed
  156. // by someone else before
  157. if (!$recordWasDeleted) {
  158. $recordWasDeleted = TRUE;
  159. $id = $record['uid'];
  160. // For Live version, try if there is a workspace version because if so, rather "delete" that instead
  161. // Look, if record is an offline version, then delete directly:
  162. if ($record['pid'] != -1) {
  163. if ($wsVersion = t3lib_BEfunc::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $id)) {
  164. $record = $wsVersion;
  165. $id = $record['uid'];
  166. }
  167. }
  168. // Look, if record is an offline version, then delete directly:
  169. if ($record['pid'] == -1) {
  170. if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
  171. // In Live workspace, delete any. In other workspaces there must be match.
  172. if ($tcemainObj->BE_USER->workspace == 0 || (int) $record['t3ver_wsid'] == $tcemainObj->BE_USER->workspace) {
  173. $liveRec = t3lib_BEfunc::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state');
  174. // Delete those in WS 0 + if their live records state was not "Placeholder".
  175. if ($record['t3ver_wsid']==0 || (int) $liveRec['t3ver_state'] <= 0) {
  176. $tcemainObj->deleteEl($table, $id);
  177. } else {
  178. // If live record was placeholder (new/deleted), rather clear
  179. // it from workspace (because it clears both version and placeholder).
  180. $this->version_clearWSID($table, $id, FALSE, $tcemainObj);
  181. }
  182. } else $tcemainObj->newlog('Tried to delete record from another workspace',1);
  183. } else $tcemainObj->newlog('Versioning not enabled for record with PID = -1!',2);
  184. } elseif ($res = $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($record['pid'], $table)) {
  185. // Look, if record is "online" or in a versionized branch, then delete directly.
  186. if ($res>0) {
  187. $tcemainObj->deleteEl($table, $id);
  188. } else {
  189. $tcemainObj->newlog('Stage of root point did not allow for deletion',1);
  190. }
  191. } elseif ((int)$record['t3ver_state']===3) {
  192. // Placeholders for moving operations are deletable directly.
  193. // Get record which its a placeholder for and reset the t3ver_state of that:
  194. if ($wsRec = t3lib_BEfunc::getWorkspaceVersionOfRecord($record['t3ver_wsid'], $table, $record['t3ver_move_id'], 'uid')) {
  195. // Clear the state flag of the workspace version of the record
  196. // Setting placeholder state value for version (so it can know it is currently a new version...)
  197. $updateFields = array(
  198. 't3ver_state' => 0
  199. );
  200. $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($wsRec['uid']), $updateFields);
  201. }
  202. $tcemainObj->deleteEl($table, $id);
  203. } else {
  204. // Otherwise, try to delete by versioning:
  205. $tcemainObj->versionizeRecord($table, $id, 'DELETED!', TRUE);
  206. $tcemainObj->deleteL10nOverlayRecords($table, $id);
  207. }
  208. }
  209. }
  210. /**
  211. * hook for t3lib_TCEmain::moveRecord that cares about moving records that
  212. * are *not* in the live workspace
  213. *
  214. * @param string $table the table of the record
  215. * @param integer $id the ID of the record
  216. * @param integer $destPid Position to move to: $destPid: >=0 then it points to
  217. * a page-id on which to insert the record (as the first element).
  218. * <0 then it points to a uid from its own table after which to insert it
  219. * @param array $propArr Record properties, like header and pid (includes workspace overlay)
  220. * @param array $moveRec Record properties, like header and pid (without workspace overlay)
  221. * @param integer $resolvedPid The final page ID of the record
  222. * (workspaces and negative values are resolved)
  223. * @param boolean $recordWasMoved can be set so that other hooks or
  224. * TCEmain knows that the default move action doesn't have to be called
  225. * @param $table the table
  226. */
  227. public function moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, t3lib_TCEmain $tcemainObj) {
  228. global $TCA;
  229. // Only do something in Draft workspace
  230. if ($tcemainObj->BE_USER->workspace !== 0) {
  231. $recordWasMoved = TRUE;
  232. // Get workspace version of the source record, if any:
  233. $WSversion = t3lib_BEfunc::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
  234. // If no version exists and versioningWS is in version 2, a new placeholder is made automatically:
  235. if (!$WSversion['uid'] && (int)$TCA[$table]['ctrl']['versioningWS']>=2 && (int)$moveRec['t3ver_state']!=3) {
  236. $tcemainObj->versionizeRecord($table, $uid, 'Placeholder version for moving record');
  237. $WSversion = t3lib_BEfunc::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid'); // Will not create new versions in live workspace though...
  238. }
  239. // Check workspace permissions:
  240. $workspaceAccessBlocked = array();
  241. // Element was in "New/Deleted/Moved" so it can be moved...
  242. $recIsNewVersion = (int)$moveRec['t3ver_state']>0;
  243. $destRes = $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($resolvedPid, $table);
  244. $canMoveRecord = $recIsNewVersion || (int)$TCA[$table]['ctrl']['versioningWS'] >= 2;
  245. // Workspace source check:
  246. if (!$recIsNewVersion) {
  247. $errorCode = $tcemainObj->BE_USER->workspaceCannotEditRecord($table, $WSversion['uid'] ? $WSversion['uid'] : $uid);
  248. if ($errorCode) {
  249. $workspaceAccessBlocked['src1'] = 'Record could not be edited in workspace: ' . $errorCode . ' ';
  250. } elseif (!$canMoveRecord && $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($moveRec['pid'], $table) <= 0) {
  251. $workspaceAccessBlocked['src2'] = 'Could not remove record from table "' . $table . '" from its page "'.$moveRec['pid'].'" ';
  252. }
  253. }
  254. // Workspace destination check:
  255. // All records can be inserted if $destRes is greater than zero.
  256. // Only new versions can be inserted if $destRes is false.
  257. // NO RECORDS can be inserted if $destRes is negative which indicates a stage
  258. // not allowed for use. If "versioningWS" is version 2, moving can take place of versions.
  259. if (!($destRes > 0 || ($canMoveRecord && !$destRes))) {
  260. $workspaceAccessBlocked['dest1'] = 'Could not insert record from table "' . $table . '" in destination PID "' . $resolvedPid . '" ';
  261. } elseif ($destRes == 1 && $WSversion['uid']) {
  262. $workspaceAccessBlocked['dest2'] = 'Could not insert other versions in destination PID ';
  263. }
  264. if (!count($workspaceAccessBlocked)) {
  265. // If the move operation is done on a versioned record, which is
  266. // NOT new/deleted placeholder and versioningWS is in version 2, then...
  267. if ($WSversion['uid'] && !$recIsNewVersion && (int)$TCA[$table]['ctrl']['versioningWS'] >= 2) {
  268. $this->moveRecord_wsPlaceholders($table, $uid, $destPid, $WSversion['uid'], $tcemainObj);
  269. } else {
  270. // moving not needed, just behave like in live workspace
  271. $recordWasMoved = FALSE;
  272. }
  273. } else {
  274. $tcemainObj->newlog("Move attempt failed due to workspace restrictions: " . implode(' // ', $workspaceAccessBlocked), 1);
  275. }
  276. }
  277. }
  278. /****************************
  279. ***** Notifications ******
  280. ****************************/
  281. /**
  282. * Send an email notification to users in workspace
  283. *
  284. * @param array $stat Workspace access array (from t3lib_userauthgroup::checkWorkspace())
  285. * @param integer $stageId New Stage number: 0 = editing, 1= just ready for review, 10 = ready for publication, -1 = rejected!
  286. * @param string $table Table name of element (or list of element names if $id is zero)
  287. * @param integer $id Record uid of element (if zero, then $table is used as reference to element(s) alone)
  288. * @param string $comment User comment sent along with action
  289. * @param t3lib_TCEmain $tcemainObj TCEmain object
  290. * @param string $notificationAlternativeRecipients Comma separated list of recipients to notificate instead of be_users selected by sys_workspace, list is generated by workspace extension module
  291. * @return void
  292. */
  293. protected function notifyStageChange(array $stat, $stageId, $table, $id, $comment, t3lib_TCEmain $tcemainObj, $notificationAlternativeRecipients = FALSE) {
  294. $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $stat['uid']);
  295. // So, if $id is not set, then $table is taken to be the complete element name!
  296. $elementName = $id ? $table . ':' . $id : $table;
  297. if (is_array($workspaceRec)) {
  298. // Get the new stage title from workspaces library, if workspaces extension is installed
  299. if (t3lib_extMgm::isLoaded('workspaces')) {
  300. $stageService = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
  301. $newStage = $stageService->getStageTitle((int)$stageId);
  302. } else {
  303. // TODO: CONSTANTS SHOULD BE USED - tx_service_workspace_workspaces
  304. // TODO: use localized labels
  305. // Compile label:
  306. switch ((int)$stageId) {
  307. case 1:
  308. $newStage = 'Ready for review';
  309. break;
  310. case 10:
  311. $newStage = 'Ready for publishing';
  312. break;
  313. case -1:
  314. $newStage = 'Element was rejected!';
  315. break;
  316. case 0:
  317. $newStage = 'Rejected element was noticed and edited';
  318. break;
  319. default:
  320. $newStage = 'Unknown state change!?';
  321. break;
  322. }
  323. }
  324. if ($notificationAlternativeRecipients == false) {
  325. // Compile list of recipients:
  326. $emails = array();
  327. switch((int)$stat['stagechg_notification']) {
  328. case 1:
  329. switch((int)$stageId) {
  330. case 1:
  331. $emails = $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']);
  332. break;
  333. case 10:
  334. $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE);
  335. break;
  336. case -1:
  337. # $emails = $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']);
  338. # $emails = array_merge($emails,$this->getEmailsForStageChangeNotification($workspaceRec['members']));
  339. // List of elements to reject:
  340. $allElements = explode(',', $elementName);
  341. // Traverse them, and find the history of each
  342. foreach ($allElements as $elRef) {
  343. list($eTable, $eUid) = explode(':', $elRef);
  344. $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
  345. 'log_data,tstamp,userid',
  346. 'sys_log',
  347. 'action=6 and details_nr=30
  348. AND tablename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($eTable, 'sys_log') . '
  349. AND recuid=' . intval($eUid),
  350. '',
  351. 'uid DESC'
  352. );
  353. // Find all implicated since the last stage-raise from editing to review:
  354. foreach ($rows as $dat) {
  355. $data = unserialize($dat['log_data']);
  356. $emails = t3lib_div::array_merge($emails, $this->getEmailsForStageChangeNotification($dat['userid'], TRUE));
  357. if ($data['stage'] == 1) {
  358. break;
  359. }
  360. }
  361. }
  362. break;
  363. case 0:
  364. $emails = $this->getEmailsForStageChangeNotification($workspaceRec['members']);
  365. break;
  366. default:
  367. $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE);
  368. break;
  369. }
  370. break;
  371. case 10:
  372. $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE);
  373. $emails = t3lib_div::array_merge($emails, $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']));
  374. $emails = t3lib_div::array_merge($emails, $this->getEmailsForStageChangeNotification($workspaceRec['members']));
  375. break;
  376. }
  377. } else {
  378. $emails = array();
  379. foreach ($notificationAlternativeRecipients as $emailAddress) {
  380. $emails[] = array('email' => $emailAddress);
  381. }
  382. }
  383. // prepare and then send the emails
  384. if (count($emails)) {
  385. // Path to record is found:
  386. list($elementTable, $elementUid) = explode(':', $elementName);
  387. $elementUid = intval($elementUid);
  388. $elementRecord = t3lib_BEfunc::getRecord($elementTable, $elementUid);
  389. $recordTitle = t3lib_BEfunc::getRecordTitle($elementTable, $elementRecord);
  390. if ($elementTable == 'pages') {
  391. $pageUid = $elementUid;
  392. } else {
  393. t3lib_BEfunc::fixVersioningPid($elementTable, $elementRecord);
  394. $pageUid = $elementUid = $elementRecord['pid'];
  395. }
  396. // fetch the TSconfig settings for the email
  397. // old way, options are TCEMAIN.notificationEmail_body/subject
  398. $TCEmainTSConfig = $tcemainObj->getTCEMAIN_TSconfig($pageUid);
  399. // these options are deprecated since TYPO3 4.5, but are still
  400. // used in order to provide backwards compatibility
  401. $emailMessage = trim($TCEmainTSConfig['notificationEmail_body']);
  402. $emailSubject = trim($TCEmainTSConfig['notificationEmail_subject']);
  403. // new way, options are
  404. // pageTSconfig: tx_version.workspaces.stageNotificationEmail.subject
  405. // userTSconfig: page.tx_version.workspaces.stageNotificationEmail.subject
  406. $pageTsConfig = t3lib_BEfunc::getPagesTSconfig($pageUid);
  407. $emailConfig = $pageTsConfig['tx_version.']['workspaces.']['stageNotificationEmail.'];
  408. $markers = array(
  409. '###RECORD_TITLE###' => $recordTitle,
  410. '###RECORD_PATH###' => t3lib_BEfunc::getRecordPath($elementUid, '', 20),
  411. '###SITE_NAME###' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
  412. '###SITE_URL###' => t3lib_div::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir,
  413. '###WORKSPACE_TITLE###' => $workspaceRec['title'],
  414. '###WORKSPACE_UID###' => $workspaceRec['uid'],
  415. '###ELEMENT_NAME###' => $elementName,
  416. '###NEXT_STAGE###' => $newStage,
  417. '###COMMENT###' => $comment,
  418. '###USER_REALNAME###' => $tcemainObj->BE_USER->user['realName'],
  419. '###USER_USERNAME###' => $tcemainObj->BE_USER->user['username']
  420. );
  421. // sending the emails the old way with sprintf(),
  422. // because it was set explicitly in TSconfig
  423. if ($emailMessage && $emailSubject) {
  424. t3lib_div::deprecationLog('This TYPO3 installation uses Workspaces staging notification by setting the TSconfig options "TCEMAIN.notificationEmail_subject" / "TCEMAIN.notificationEmail_body". Please use the more flexible marker-based options tx_version.workspaces.stageNotificationEmail.message / tx_version.workspaces.stageNotificationEmail.subject');
  425. $emailSubject = sprintf($emailSubject, $elementName);
  426. $emailMessage = sprintf($emailMessage,
  427. $markers['###SITE_NAME###'],
  428. $markers['###SITE_URL###'],
  429. $markers['###WORKSPACE_TITLE###'],
  430. $markers['###WORKSPACE_UID###'],
  431. $markers['###ELEMENT_NAME###'],
  432. $markers['###NEXT_STAGE###'],
  433. $markers['###COMMENT###'],
  434. $markers['###USER_REALNAME###'],
  435. $markers['###USER_USERNAME###'],
  436. $markers['###RECORD_PATH###'],
  437. $markers['###RECORD_TITLE###']
  438. );
  439. // filter out double email addresses
  440. $emailRecipients = array();
  441. foreach ($emails as $recip) {
  442. $emailRecipients[$recip['email']] = $recip['email'];
  443. }
  444. $emailRecipients = implode(',', $emailRecipients);
  445. // Send one email to everybody
  446. t3lib_div::plainMailEncoded(
  447. $emailRecipients,
  448. $emailSubject,
  449. $emailMessage
  450. );
  451. } else {
  452. // send an email to each individual user, to ensure the
  453. // multilanguage version of the email
  454. $emailHeaders = $emailConfig['additionalHeaders'];
  455. $emailRecipients = array();
  456. // an array of language objects that are needed
  457. // for emails with different languages
  458. $languageObjects = array(
  459. $GLOBALS['LANG']->lang => $GLOBALS['LANG']
  460. );
  461. // loop through each recipient and send the email
  462. foreach ($emails as $recipientData) {
  463. // don't send an email twice
  464. if (isset($emailRecipients[$recipientData['email']])) {
  465. continue;
  466. }
  467. $emailSubject = $emailConfig['subject'];
  468. $emailMessage = $emailConfig['message'];
  469. $emailRecipients[$recipientData['email']] = $recipientData['email'];
  470. // check if the email needs to be localized
  471. // in the users' language
  472. if (t3lib_div::isFirstPartOfStr($emailSubject, 'LLL:') || t3lib_div::isFirstPartOfStr($emailMessage, 'LLL:')) {
  473. $recipientLanguage = ($recipientData['lang'] ? $recipientData['lang'] : 'default');
  474. if (!isset($languageObjects[$recipientLanguage])) {
  475. // a LANG object in this language hasn't been
  476. // instantiated yet, so this is done here
  477. /** @var $languageObject language */
  478. $languageObject = t3lib_div::makeInstance('language');
  479. $languageObject->init($recipientLanguage);
  480. $languageObjects[$recipientLanguage] = $languageObject;
  481. } else {
  482. $languageObject = $languageObjects[$recipientLanguage];
  483. }
  484. if (t3lib_div::isFirstPartOfStr($emailSubject, 'LLL:')) {
  485. $emailSubject = $languageObject->sL($emailSubject);
  486. }
  487. if (t3lib_div::isFirstPartOfStr($emailMessage, 'LLL:')) {
  488. $emailMessage = $languageObject->sL($emailMessage);
  489. }
  490. }
  491. $emailSubject = t3lib_parseHtml::substituteMarkerArray($emailSubject, $markers, '', TRUE, TRUE);
  492. $emailMessage = t3lib_parseHtml::substituteMarkerArray($emailMessage, $markers, '', TRUE, TRUE);
  493. // Send an email to the recipient
  494. t3lib_div::plainMailEncoded(
  495. $recipientData['email'],
  496. $emailSubject,
  497. $emailMessage,
  498. $emailHeaders
  499. );
  500. }
  501. $emailRecipients = implode(',', $emailRecipients);
  502. }
  503. $tcemainObj->newlog2('Notification email for stage change was sent to "' . $emailRecipients . '"', $table, $id);
  504. }
  505. }
  506. }
  507. /**
  508. * Return be_users that should be notified on stage change from input list.
  509. * previously called notifyStageChange_getEmails() in tcemain
  510. *
  511. * @param string $listOfUsers List of backend users, on the form "be_users_10,be_users_2" or "10,2" in case noTablePrefix is set.
  512. * @param boolean $noTablePrefix If set, the input list are integers and not strings.
  513. * @return array Array of emails
  514. */
  515. protected function getEmailsForStageChangeNotification($listOfUsers, $noTablePrefix = FALSE) {
  516. $users = t3lib_div::trimExplode(',', $listOfUsers, 1);
  517. $emails = array();
  518. foreach ($users as $userIdent) {
  519. if ($noTablePrefix) {
  520. $id = intval($userIdent);
  521. } else {
  522. list($table, $id) = t3lib_div::revExplode('_', $userIdent, 2);
  523. }
  524. if ($table === 'be_users' || $noTablePrefix) {
  525. if ($userRecord = t3lib_BEfunc::getRecord('be_users', $id, 'uid,email,lang,realName')) {
  526. if (strlen(trim($userRecord['email']))) {
  527. $emails[$id] = $userRecord;
  528. }
  529. }
  530. }
  531. }
  532. return $emails;
  533. }
  534. /****************************
  535. ***** Stage Changes ******
  536. ****************************/
  537. /**
  538. * Setting stage of record
  539. *
  540. * @param string $table Table name
  541. * @param integer $integer Record UID
  542. * @param integer $stageId Stage ID to set
  543. * @param string $comment Comment that goes into log
  544. * @param boolean $notificationEmailInfo Accumulate state changes in memory for compiled notification email?
  545. * @param t3lib_TCEmain $tcemainObj TCEmain object
  546. * @param string $notificationAlternativeRecipients comma separated list of recipients to notificate instead of normal be_users
  547. * @return void
  548. */
  549. protected function version_setStage($table, $id, $stageId, $comment = '', $notificationEmailInfo = FALSE, t3lib_TCEmain $tcemainObj, $notificationAlternativeRecipients = FALSE) {
  550. if ($errorCode = $tcemainObj->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
  551. $tcemainObj->newlog('Attempt to set stage for record failed: ' . $errorCode, 1);
  552. } elseif ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
  553. $record = t3lib_BEfunc::getRecord($table, $id);
  554. $stat = $tcemainObj->BE_USER->checkWorkspace($record['t3ver_wsid']);
  555. // check if the usere is allowed to the current stage, so it's also allowed to send to next stage
  556. if ($GLOBALS['BE_USER']->workspaceCheckStageForCurrent($record['t3ver_stage'])) {
  557. // Set stage of record:
  558. $updateData = array(
  559. 't3ver_stage' => $stageId
  560. );
  561. $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $updateData);
  562. $tcemainObj->newlog2('Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', $table, $id);
  563. // TEMPORARY, except 6-30 as action/detail number which is observed elsewhere!
  564. $tcemainObj->log($table, $id, 6, 0, 0, 'Stage raised...', 30, array('comment' => $comment, 'stage' => $stageId));
  565. if ((int)$stat['stagechg_notification'] > 0) {
  566. if ($notificationEmailInfo) {
  567. $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['shared'] = array($stat, $stageId, $comment);
  568. $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['elements'][] = $table . ':' . $id;
  569. $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['alternativeRecipients'] = $notificationAlternativeRecipients;
  570. } else {
  571. $this->notifyStageChange($stat, $stageId, $table, $id, $comment, $tcemainObj, $notificationAlternativeRecipients);
  572. }
  573. }
  574. } else $tcemainObj->newlog('The member user tried to set a stage value "' . $stageId . '" that was not allowed', 1);
  575. } else $tcemainObj->newlog('Attempt to set stage for record failed because you do not have edit access', 1);
  576. }
  577. /*****************************
  578. ***** CMD versioning ******
  579. *****************************/
  580. /**
  581. * Creates a new version of a page including content and possible subpages.
  582. *
  583. * @param integer $uid Page uid to create new version of.
  584. * @param string $label Version label
  585. * @param integer $versionizeTree Indicating "treeLevel" - "page" (0) or "branch" (>=1) ["element" type must call versionizeRecord() directly]
  586. * @param t3lib_TCEmain $tcemainObj TCEmain object
  587. * @return void
  588. * @see copyPages()
  589. */
  590. protected function versionizePages($uid, $label, $versionizeTree, t3lib_TCEmain $tcemainObj) {
  591. global $TCA;
  592. $uid = intval($uid);
  593. // returns the branch
  594. $brExist = $tcemainObj->doesBranchExist('', $uid, $tcemainObj->pMap['show'], 1);
  595. // Checks if we had permissions
  596. if ($brExist != -1) {
  597. // Make list of tables that should come along with a new version of the page:
  598. $verTablesArray = array();
  599. $allTables = array_keys($TCA);
  600. foreach ($allTables as $tableName) {
  601. if ($tableName != 'pages' && ($versionizeTree > 0 || $TCA[$tableName]['ctrl']['versioning_followPages'])) {
  602. $verTablesArray[] = $tableName;
  603. }
  604. }
  605. // Remove the possible inline child tables from the tables to be versioniozed automatically:
  606. $verTablesArray = array_diff($verTablesArray, $this->getPossibleInlineChildTablesOfParentTable('pages'));
  607. // Begin to copy pages if we're allowed to:
  608. if ($tcemainObj->BE_USER->workspaceVersioningTypeAccess($versionizeTree)) {
  609. // Versionize this page:
  610. $theNewRootID = $tcemainObj->versionizeRecord('pages', $uid, $label, FALSE, $versionizeTree);
  611. if ($theNewRootID) {
  612. $this->rawCopyPageContent($uid, $theNewRootID, $verTablesArray, $tcemainObj);
  613. // If we're going to copy recursively...:
  614. if ($versionizeTree > 0) {
  615. // Get ALL subpages to copy (read permissions respected - they should NOT be...):
  616. $CPtable = $tcemainObj->int_pageTreeInfo(array(), $uid, intval($versionizeTree), $theNewRootID);
  617. // Now copying the subpages
  618. foreach ($CPtable as $thePageUid => $thePagePid) {
  619. $newPid = $tcemainObj->copyMappingArray['pages'][$thePagePid];
  620. if (isset($newPid)) {
  621. $theNewRootID = $tcemainObj->copyRecord_raw('pages', $thePageUid, $newPid);
  622. $this->rawCopyPageContent($thePageUid, $theNewRootID, $verTablesArray, $tcemainObj);
  623. } else {
  624. $tcemainObj->newlog('Something went wrong during copying branch (for versioning)', 1);
  625. break;
  626. }
  627. }
  628. } // else the page was not copied. Too bad...
  629. } else $tcemainObj->newlog('The root version could not be created!',1);
  630. } else $tcemainObj->newlog('Versioning type "'.$versionizeTree.'" was not allowed in workspace',1);
  631. } else $tcemainObj->newlog('Could not read all subpages to versionize.',1);
  632. }
  633. /**
  634. * Swapping versions of a record
  635. * Version from archive (future/past, called "swap version") will get the uid of the "t3ver_oid", the official element with uid = "t3ver_oid" will get the new versions old uid. PIDs are swapped also
  636. *
  637. * @param string $table Table name
  638. * @param integer $id UID of the online record to swap
  639. * @param integer $swapWith UID of the archived version to swap with!
  640. * @param boolean $swapIntoWS If set, swaps online into workspace instead of publishing out of workspace.
  641. * @param t3lib_TCEmain $tcemainObj TCEmain object
  642. * @return void
  643. */
  644. protected function version_swap($table, $id, $swapWith, $swapIntoWS=0, t3lib_TCEmain $tcemainObj) {
  645. global $TCA;
  646. // First, check if we may actually edit the online record
  647. if ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
  648. // Select the two versions:
  649. $curVersion = t3lib_BEfunc::getRecord($table, $id, '*');
  650. $swapVersion = t3lib_BEfunc::getRecord($table, $swapWith, '*');
  651. $movePlh = array();
  652. $movePlhID = 0;
  653. if (is_array($curVersion) && is_array($swapVersion)) {
  654. if ($tcemainObj->BE_USER->workspacePublishAccess($swapVersion['t3ver_wsid'])) {
  655. $wsAccess = $tcemainObj->BE_USER->checkWorkspace($swapVersion['t3ver_wsid']);
  656. if ($swapVersion['t3ver_wsid'] <= 0 || !($wsAccess['publish_access'] & 1) || (int)$swapVersion['t3ver_stage'] === -10) {
  657. if ($tcemainObj->doesRecordExist($table,$swapWith,'show') && $tcemainObj->checkRecordUpdateAccess($table,$swapWith)) {
  658. if (!$swapIntoWS || $tcemainObj->BE_USER->workspaceSwapAccess()) {
  659. // Check if the swapWith record really IS a version of the original!
  660. if ((int)$swapVersion['pid'] == -1 && (int)$curVersion['pid'] >= 0 && !strcmp($swapVersion['t3ver_oid'], $id)) {
  661. // Lock file name:
  662. $lockFileName = PATH_site.'typo3temp/swap_locking/' . $table . ':' . $id . '.ser';
  663. if (!@is_file($lockFileName)) {
  664. // Write lock-file:
  665. t3lib_div::writeFileToTypo3tempDir($lockFileName, serialize(array(
  666. 'tstamp' => $GLOBALS['EXEC_TIME'],
  667. 'user' => $tcemainObj->BE_USER->user['username'],
  668. 'curVersion' => $curVersion,
  669. 'swapVersion' => $swapVersion
  670. )));
  671. // Find fields to keep
  672. $keepFields = $tcemainObj->getUniqueFields($table);
  673. if ($TCA[$table]['ctrl']['sortby']) {
  674. $keepFields[] = $TCA[$table]['ctrl']['sortby'];
  675. }
  676. // l10n-fields must be kept otherwise the localization
  677. // will be lost during the publishing
  678. if (!isset($TCA[$table]['ctrl']['transOrigPointerTable']) && $TCA[$table]['ctrl']['transOrigPointerField']) {
  679. $keepFields[] = $TCA[$table]['ctrl']['transOrigPointerField'];
  680. }
  681. // Swap "keepfields"
  682. foreach ($keepFields as $fN) {
  683. $tmp = $swapVersion[$fN];
  684. $swapVersion[$fN] = $curVersion[$fN];
  685. $curVersion[$fN] = $tmp;
  686. }
  687. // Preserve states:
  688. $t3ver_state = array();
  689. $t3ver_state['swapVersion'] = $swapVersion['t3ver_state'];
  690. $t3ver_state['curVersion'] = $curVersion['t3ver_state'];
  691. // Modify offline version to become online:
  692. $tmp_wsid = $swapVersion['t3ver_wsid'];
  693. // Set pid for ONLINE
  694. $swapVersion['pid'] = intval($curVersion['pid']);
  695. // We clear this because t3ver_oid only make sense for offline versions
  696. // and we want to prevent unintentional misuse of this
  697. // value for online records.
  698. $swapVersion['t3ver_oid'] = 0;
  699. // In case of swapping and the offline record has a state
  700. // (like 2 or 4 for deleting or move-pointer) we set the
  701. // current workspace ID so the record is not deselected
  702. // in the interface by t3lib_BEfunc::versioningPlaceholderClause()
  703. $swapVersion['t3ver_wsid'] = 0;
  704. if ($swapIntoWS) {
  705. if ($t3ver_state['swapVersion'] > 0) {
  706. $swapVersion['t3ver_wsid'] = $tcemainObj->BE_USER->workspace;
  707. } else {
  708. $swapVersion['t3ver_wsid'] = intval($curVersion['t3ver_wsid']);
  709. }
  710. }
  711. $swapVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
  712. $swapVersion['t3ver_stage'] = 0;
  713. if (!$swapIntoWS) {
  714. $swapVersion['t3ver_state'] = 0;
  715. }
  716. // Moving element.
  717. if ((int)$TCA[$table]['ctrl']['versioningWS']>=2) { // && $t3ver_state['swapVersion']==4 // Maybe we don't need this?
  718. if ($plhRec = t3lib_BEfunc::getMovePlaceholder($table, $id, 't3ver_state,pid,uid' . ($TCA[$table]['ctrl']['sortby'] ? ',' . $TCA[$table]['ctrl']['sortby'] : ''))) {
  719. $movePlhID = $plhRec['uid'];
  720. $movePlh['pid'] = $swapVersion['pid'];
  721. $swapVersion['pid'] = intval($plhRec['pid']);
  722. $curVersion['t3ver_state'] = intval($swapVersion['t3ver_state']);
  723. $swapVersion['t3ver_state'] = 0;
  724. if ($TCA[$table]['ctrl']['sortby']) {
  725. // sortby is a "keepFields" which is why this will work...
  726. $movePlh[$TCA[$table]['ctrl']['sortby']] = $swapVersion[$TCA[$table]['ctrl']['sortby']];
  727. $swapVersion[$TCA[$table]['ctrl']['sortby']] = $plhRec[$TCA[$table]['ctrl']['sortby']];
  728. }
  729. }
  730. }
  731. // Take care of relations in each field (e.g. IRRE):
  732. if (is_array($GLOBALS['TCA'][$table]['columns'])) {
  733. foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $fieldConf) {
  734. $this->version_swap_procBasedOnFieldType(
  735. $table, $field, $fieldConf['config'], $curVersion, $swapVersion, $tcemainObj
  736. );
  737. }
  738. }
  739. unset($swapVersion['uid']);
  740. // Modify online version to become offline:
  741. unset($curVersion['uid']);
  742. // Set pid for OFFLINE
  743. $curVersion['pid'] = -1;
  744. $curVersion['t3ver_oid'] = intval($id);
  745. $curVersion['t3ver_wsid'] = ($swapIntoWS ? intval($tmp_wsid) : 0);
  746. $curVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
  747. $curVersion['t3ver_count'] = $curVersion['t3ver_count']+1; // Increment lifecycle counter
  748. $curVersion['t3ver_stage'] = 0;
  749. if (!$swapIntoWS) {
  750. $curVersion['t3ver_state'] = 0;
  751. }
  752. // Keeping the swapmode state
  753. if ($table === 'pages') {
  754. $curVersion['t3ver_swapmode'] = $swapVersion['t3ver_swapmode'];
  755. }
  756. // Registering and swapping MM relations in current and swap records:
  757. $tcemainObj->version_remapMMForVersionSwap($table, $id, $swapWith);
  758. // Generating proper history data to prepare logging
  759. $tcemainObj->compareFieldArrayWithCurrentAndUnset($table, $id, $swapVersion);
  760. $tcemainObj->compareFieldArrayWithCurrentAndUnset($table, $swapWith, $curVersion);
  761. // Execute swapping:
  762. $sqlErrors = array();
  763. $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $swapVersion);
  764. if ($GLOBALS['TYPO3_DB']->sql_error()) {
  765. $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
  766. } else {
  767. $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($swapWith), $curVersion);
  768. if ($GLOBALS['TYPO3_DB']->sql_error()) {
  769. $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
  770. } else {
  771. unlink($lockFileName);
  772. }
  773. }
  774. if (!count($sqlErrors)) {
  775. // Register swapped ids for later remapping:
  776. $this->remappedIds[$table][$id] =$swapWith;
  777. $this->remappedIds[$table][$swapWith] = $id;
  778. // If a moving operation took place...:
  779. if ($movePlhID) {
  780. // Remove, if normal publishing:
  781. if (!$swapIntoWS) {
  782. // For delete + completely delete!
  783. $tcemainObj->deleteEl($table, $movePlhID, TRUE, TRUE);
  784. } else {
  785. // Otherwise update the movePlaceholder:
  786. $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($movePlhID), $movePlh);
  787. $tcemainObj->addRemapStackRefIndex($table, $movePlhID);
  788. }
  789. }
  790. // Checking for delete:
  791. // Delete only if new/deleted placeholders are there.
  792. if (!$swapIntoWS && ((int)$t3ver_state['swapVersion'] === 1 || (int)$t3ver_state['swapVersion'] === 2)) {
  793. // Force delete
  794. $tcemainObj->deleteEl($table, $id, TRUE);
  795. }
  796. $tcemainObj->newlog2(($swapIntoWS ? 'Swapping' : 'Publishing') . ' successful for table "' . $table . '" uid ' . $id . '=>' . $swapWith, $table, $id, $swapVersion['pid']);
  797. // Update reference index of the live record:
  798. $tcemainObj->addRemapStackRefIndex($table, $id);
  799. // Set log entry for live record:
  800. $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $swapVersion);
  801. if ($propArr['_ORIG_pid'] == -1) {
  802. $label = $GLOBALS['LANG']->sL ('LLL:EXT:lang/locallang_tcemain.xml:version_swap.offline_record_updated');
  803. } else {
  804. $label = $GLOBALS['LANG']->sL ('LLL:EXT:lang/locallang_tcemain.xml:version_swap.online_record_updated');
  805. }
  806. $theLogId = $tcemainObj->log($table, $id, 2, $propArr['pid'], 0, $label , 10, array($propArr['header'], $table . ':' . $id), $propArr['event_pid']);
  807. $tcemainObj->setHistory($table, $id, $theLogId);
  808. // Update reference index of the offline record:
  809. $tcemainObj->addRemapStackRefIndex($table, $swapWith);
  810. // Set log entry for offline record:
  811. $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $curVersion);
  812. if ($propArr['_ORIG_pid'] == -1) {
  813. $label = $GLOBALS['LANG']->sL ('LLL:EXT:lang/locallang_tcemain.xml:version_swap.offline_record_updated');
  814. } else {
  815. $label = $GLOBALS['LANG']->sL ('LLL:EXT:lang/locallang_tcemain.xml:version_swap.online_record_updated');
  816. }
  817. $theLogId = $tcemainObj->log($table, $swapWith, 2, $propArr['pid'], 0, $label, 10, array($propArr['header'], $table . ':' . $swapWith), $propArr['event_pid']);
  818. $tcemainObj->setHistory($table, $swapWith, $theLogId);
  819. // SWAPPING pids for subrecords:
  820. if ($table=='pages' && $swapVersion['t3ver_swapmode'] >= 0) {
  821. // Collect table names that should be copied along with the tables:
  822. foreach ($TCA as $tN => $tCfg) {
  823. // For "Branch" publishing swap ALL,
  824. // otherwise for "page" publishing, swap only "versioning_followPages" tables
  825. if ($swapVersion['t3ver_swapmode'] > 0 || $TCA[$tN]['ctrl']['versioning_followPages']) {
  826. $temporaryPid = -($id+1000000);
  827. $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN, 'pid=' . intval($id), array('pid' => $temporaryPid));
  828. if ($GLOBALS['TYPO3_DB']->sql_error()) {
  829. $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
  830. }
  831. $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN, 'pid=' . intval($swapWith), array('pid' => $id));
  832. if ($GLOBALS['TYPO3_DB']->sql_error()) {
  833. $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
  834. }
  835. $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN, 'pid=' . intval($temporaryPid), array('pid' => $swapWith));
  836. if ($GLOBALS['TYPO3_DB']->sql_error()) {
  837. $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
  838. }
  839. if (count($sqlErrors)) {
  840. $tcemainObj->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2);
  841. }
  842. }
  843. }
  844. }
  845. // Clear cache:
  846. $tcemainObj->clear_cache($table, $id);
  847. // Checking for "new-placeholder" and if found, delete it (BUT FIRST after swapping!):
  848. if (!$swapIntoWS && $t3ver_state['curVersion']>0) {
  849. // For delete + completely delete!
  850. $tcemainObj->deleteEl($table, $swapWith, TRUE, TRUE);
  851. }
  852. } else $tcemainObj->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2);
  853. } else $tcemainObj->newlog('A swapping lock file was present. Either another swap process is already running or a previous swap process failed. Ask your administrator to handle the situation.', 2);
  854. } else $tcemainObj->newlog('In swap version, either pid was not -1 or the t3ver_oid didn\'t match the id of the online version as it must!', 2);
  855. } else $tcemainObj->newlog('Workspace #' . $swapVersion['t3ver_wsid'] . ' does not support swapping.', 1);
  856. } else $tcemainObj->newlog('You cannot publish a record you do not have edit and show permissions for', 1);
  857. } else $tcemainObj->newlog('Records in workspace #' . $swapVersion['t3ver_wsid'] . ' can only be published when in "Publish" stage.', 1);
  858. } else $tcemainObj->newlog('User could not publish records from workspace #' . $swapVersion['t3ver_wsid'], 1);
  859. } else $tcemainObj->newlog('Error: Either online or swap version could not be selected!', 2);
  860. } else $tcemainObj->newlog('Error: You cannot swap versions for a record you do not have access to edit!', 1);
  861. }
  862. /**
  863. * Update relations on version/workspace swapping.
  864. *
  865. * @param string $table: Record Table
  866. * @param string $field: Record field
  867. * @param array $conf: TCA configuration of current field
  868. * @param array $curVersion: Reference to the current (original) record
  869. * @param array $swapVersion: Reference to the record (workspace/versionized) to publish in or swap with
  870. * @param t3lib_TCEmain $tcemainObj TCEmain object
  871. * @return void
  872. */
  873. protected function version_swap_procBasedOnFieldType($table, $field, array $conf, array &$curVersion, array &$swapVersion, t3lib_TCEmain $tcemainObj) {
  874. $inlineType = $tcemainObj->getInlineFieldType($conf);
  875. // Process pointer fields on normalized database:
  876. if ($inlineType == 'field') {
  877. // Read relations that point to the current record (e.g. live record):
  878. /** @var $dbAnalysisCur t3lib_loadDBGroup */
  879. $dbAnalysisCur = t3lib_div::makeInstance('t3lib_loadDBGroup');
  880. $dbAnalysisCur->setUpdateReferenceIndex(FALSE);
  881. $dbAnalysisCur->start('', $conf['foreign_table'], '', $curVersion['uid'], $table, $conf);
  882. // Read relations that point to the record to be swapped with e.g. draft record):
  883. /** @var $dbAnalysisSwap t3lib_loadDBGroup */
  884. $dbAnalysisSwap = t3lib_div::makeInstance('t3lib_loadDBGroup');
  885. $dbAnalysisSwap->setUpdateReferenceIndex(FALSE);
  886. $dbAnalysisSwap->start('', $conf['foreign_table'], '', $swapVersion['uid'], $table, $conf);
  887. // Update relations for both (workspace/versioning) sites:
  888. if (count($dbAnalysisCur->itemArray)) {
  889. $dbAnalysisCur->writeForeignField($conf, $curVersion['uid'], $swapVersion['uid']);
  890. $tcemainObj->addRemapAction(
  891. $table, $curVersion['uid'],
  892. array($this, 'writeRemappedForeignField'),
  893. array($dbAnalysisCur, $conf, $swapVersion['uid'])
  894. );
  895. }
  896. if (count($dbAnalysisSwap->itemArray)) {
  897. $dbAnalysisSwap->writeForeignField($conf, $swapVersion['uid'], $curVersion['uid']);
  898. $tcemainObj->addRemapAction(
  899. $table, $curVersion['uid'],
  900. array($this, 'writeRemappedForeignField'),
  901. array($dbAnalysisSwap, $conf, $curVersion['uid'])
  902. );
  903. }
  904. $items = array_merge($dbAnalysisCur->itemArray, $dbAnalysisSwap->itemArray);
  905. foreach ($items as $item) {
  906. $tcemainObj->addRemapStackRefIndex($item['table'], $item['id']);
  907. }
  908. // Swap field values (CSV):
  909. // BUT: These values will be swapped back in the next steps, when the *CHILD RECORD ITSELF* is swapped!
  910. } elseif ($inlineType == 'list') {
  911. $tempValue = $curVersion[$field];
  912. $curVersion[$field] = $swapVersion[$field];
  913. $swapVersion[$field] = $tempValue;
  914. }
  915. }
  916. /**
  917. * Writes remapped foreign field (IRRE).
  918. *
  919. * @param t3lib_loadDBGroup $dbAnalysis Instance that holds the sorting order of child records
  920. * @param array $configuration The TCA field configuration
  921. * @param integer $parentId The uid of the parent record
  922. * @return void
  923. */
  924. public function writeRemappedForeignField(t3lib_loadDBGroup $dbAnalysis, array $configuration, $parentId) {
  925. foreach ($dbAnalysis->itemArray as &$item) {
  926. if (isset($this->remappedIds[$item['table']][$item['id']])) {
  927. $item['id'] = $this->remappedIds[$item['table']][$item['id']];
  928. }
  929. }
  930. $dbAnalysis->writeForeignField($configuration, $parentId);
  931. }
  932. /**
  933. * Release version from this workspace (and into "Live" workspace but as an offline version).
  934. *
  935. * @param string $table Table name
  936. * @param integer $id Record UID
  937. * @param boolean $flush If set, will completely delete element
  938. * @param t3lib_TCEmain $tcemainObj TCEmain object
  939. * @return void
  940. */
  941. protected function version_clearWSID($table, $id, $flush = FALSE, t3lib_TCEmain $tcemainObj) {
  942. global $TCA;
  943. if ($errorCode = $tcemainObj->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
  944. $tcemainObj->newlog('Attempt to reset workspace for record failed: ' . $errorCode, 1);
  945. } elseif ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
  946. if ($liveRec = t3lib_BEfunc::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state')) {
  947. // Clear workspace ID:
  948. $updateData = array(
  949. 't3ver_wsid' => 0
  950. );
  951. $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $updateData);
  952. // Clear workspace ID for live version AND DELETE IT as well because it is a new record!
  953. if ((int) $liveRec['t3ver_state'] == 1 || (int) $liveRec['t3ver_state'] == 2) {
  954. $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table,'uid=' . intval($liveRec['uid']), $updateData);
  955. // THIS assumes that the record was placeholder ONLY for ONE record (namely $id)
  956. $tcemainObj->deleteEl($table, $liveRec['uid'], TRUE);
  957. }
  958. // If "deleted" flag is set for the version that got released
  959. // it doesn't make sense to keep that "placeholder" anymore and we delete it completly.
  960. $wsRec = t3lib_BEfunc::getRecord($table, $id);
  961. if ($flush || ((int) $wsRec['t3ver_state'] == 1 || (int) $wsRec['t3ver_state'] == 2)) {
  962. $tcemainObj->deleteEl($table, $id, TRUE, TRUE);
  963. }
  964. // Remove the move-placeholder if found for live record.
  965. if ((int)$TCA[$table]['ctrl']['versioningWS'] >= 2) {
  966. if ($plhRec = t3lib_BEfunc::getMovePlaceholder($table, $liveRec['uid'], 'uid')) {
  967. $tcemainObj->deleteEl($table, $plhRec['uid'], TRUE, TRUE);
  968. }
  969. }
  970. }
  971. } else $tcemainObj->newlog('Attempt to reset workspace for record failed because you do not have edit access',1);
  972. }
  973. /*******************************
  974. ***** helper functions ******
  975. *******************************/
  976. /**
  977. * Copies all records from tables in $copyTablesArray from page with $old_pid to page with $new_pid
  978. * Uses raw-copy for the operation (meant for versioning!)
  979. *
  980. * @param integer $oldPageId Current page id.
  981. * @param integer $newPageId New page id
  982. * @param array $copyTablesArray Array of tables from which to copy
  983. * @param t3lib_TCEmain $tcemainObj TCEmain object
  984. * @return void
  985. * @see versionizePages()
  986. */
  987. protected function rawCopyPageContent($oldPageId, $newPageId, array $copyTablesArray, t3lib_TCEmain $tcemainObj) {
  988. global $TCA;
  989. if ($newPageId) {
  990. foreach ($copyTablesArray as $table) {
  991. // all records under the page is copied.
  992. if ($table && is_array($TCA[$table]) && $table != 'pages') {
  993. $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
  994. 'uid',
  995. $table,
  996. 'pid=' . intval($oldPageId) . $tcemainObj->deleteClause($table)
  997. );
  998. while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
  999. // Check, if this record has already been copied by a parent record as relation:
  1000. if (!$tcemainObj->copyMappingArray[$table][$row['uid']]) {
  1001. // Copying each of the underlying records (method RAW)
  1002. $tcemainObj->copyRecord_raw($table, $row['uid'], $newPageId);
  1003. }
  1004. }
  1005. $GLOBALS['TYPO3_DB']->sql_free_result($mres);
  1006. }
  1007. }
  1008. }
  1009. }
  1010. /**
  1011. * Finds all elements for swapping versions in workspace
  1012. *
  1013. * @param string $table Table name of the original element to swap
  1014. * @param integer $id UID of the original element to swap (online)
  1015. * @param integer $offlineId As above but offline
  1016. * @return array Element data. Key is table name, values are array with first element as online UID, second - offline UID
  1017. */
  1018. protected function findPageElementsForVersionSwap($table, $id, $offlineId) {
  1019. global $TCA;
  1020. $rec = t3lib_BEfunc::getRecord($table, $offlineId, 't3ver_wsid');
  1021. $workspaceId = $rec['t3ver_wsid'];
  1022. $elementData = array();
  1023. if ($workspaceId != 0) {
  1024. // Get page UID for LIVE and workspace
  1025. if ($table != 'pages') {
  1026. $rec = t3lib_BEfunc::getRecord($table, $id, 'pid');
  1027. $pageId = $rec['pid'];
  1028. $rec = t3lib_BEfunc::getRecord('pages', $pageId);
  1029. t3lib_BEfunc::workspaceOL('pages', $rec, $workspaceId);
  1030. $offlinePageId = $rec['_ORIG_uid'];
  1031. } else {
  1032. $pageId = $id;
  1033. $offlinePageId = $offlineId;
  1034. }
  1035. // Traversing all tables supporting versioning:
  1036. foreach ($TCA as $table => $cfg) {
  1037. if ($TCA[$table]['ctrl']['versioningWS'] && $table != 'pages') {
  1038. $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('A.uid AS offlineUid, B.uid AS uid',
  1039. $table . ' A,' . $table . ' B',
  1040. 'A.pid=-1 AND B.pid=' . $pageId . ' AND A.t3ver_wsid=' . $workspaceId .
  1041. ' AND B.uid=A.t3ver_oid' .
  1042. t3lib_BEfunc::deleteClause($table, 'A') . t3lib_BEfunc::deleteClause($table, 'B'));
  1043. while (FALSE != ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
  1044. $elementData[$table][] = array($row[1], $row[0]);
  1045. }
  1046. $GLOBALS['TYPO3_DB']->sql_free_result($res);
  1047. }
  1048. }
  1049. if ($offlinePageId && $offlinePageId != $pageId) {
  1050. $elementData['pages'][] = array($pageId, $offlinePageId);
  1051. }
  1052. }
  1053. return $elementData;
  1054. }
  1055. /**
  1056. * Searches for all elements from all tables on the given pages in the same workspace.
  1057. *
  1058. * @param array $pageIdList List of PIDs to search
  1059. * @param integer $workspaceId Workspace ID
  1060. * @param array $elementList List of found elements. Key is table name, value is array of element UIDs
  1061. * @return void
  1062. */
  1063. protected function findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList) {
  1064. global $TCA;
  1065. if ($workspaceId != 0) {
  1066. // Traversing all tables supporting versioning:
  1067. foreach ($TCA as $table => $cfg) {
  1068. if ($TCA[$table]['ctrl']['versioningWS'] && $table != 'pages') {
  1069. $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT A.uid',
  1070. $table . ' A,' . $table . ' B',
  1071. 'A.pid=-1' . // Offline version
  1072. ' AND A.t3ver_wsid=' . $workspaceId .
  1073. ' AND B.pid IN (' . implode(',', $pageIdList) . ') AND A.t3ver_oid=B.uid' .
  1074. t3lib_BEfunc::deleteClause($table,'A').
  1075. t3lib_BEfunc::deleteClause($table,'B')
  1076. );
  1077. while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
  1078. $elementList[$table][] = $row[0];
  1079. }
  1080. $GLOBALS['TYPO3_DB']->sql_free_result($res);
  1081. if (is_array($elementList[$table])) {
  1082. // Yes, it is possible to get non-unique array even with DISTINCT above!
  1083. // It happens because several UIDs are passed in the array already.
  1084. $elementList[$table] = array_unique($elementList[$table]);
  1085. }
  1086. }
  1087. }
  1088. }
  1089. }
  1090. /**
  1091. * Finds page UIDs for the element from table <code>$table</code> with UIDs from <code>$idList</code>
  1092. *
  1093. * @param string $table Table to search
  1094. * @param array $idList List of records' UIDs
  1095. * @param integer $workspaceId Workspace ID. We need this parameter because user can be in LIVE but he still can publisg DRAFT from ws module!
  1096. * @param array $pageIdList List of found page UIDs
  1097. * @param array $elementList List of found element UIDs. Key is table name, value is list of UIDs
  1098. * @return void
  1099. */
  1100. protected function findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList) {
  1101. if ($workspaceId != 0) {
  1102. $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT B.pid',
  1103. $table . ' A,' . $table . ' B',
  1104. 'A.pid=-1' . // Offline version
  1105. ' AND A.t3ver_wsid=' . $workspaceId .
  1106. ' AND A.uid IN (' . implode(',', $idList) . ') AND A.t3ver_oid=B.uid' .
  1107. t3lib_BEfunc::deleteClause($table,'A').
  1108. t3lib_BEfunc::deleteClause($table,'B')
  1109. );
  1110. while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
  1111. $pageIdList[] = $row[0];
  1112. // Find ws version
  1113. // Note: cannot use t3lib_BEfunc::getRecordWSOL()
  1114. // here because it does not accept workspace id!
  1115. $rec = t3lib_BEfunc::getRecord('pages', $row[0]);
  1116. t3lib_BEfunc::workspaceOL('pages', $rec, $workspaceId);
  1117. if ($rec['_ORIG_uid']) {
  1118. $elementList['pages'][$row[0]] = $rec['_ORIG_uid'];
  1119. }
  1120. }
  1121. $GLOBALS['TYPO3_DB']->sql_free_result($res);
  1122. // The line below is necessary even with DISTINCT
  1123. // because several elements can be passed by caller
  1124. $pageIdList = array_unique($pageIdList);
  1125. }
  1126. }
  1127. /**
  1128. * Finds real page IDs for state change.
  1129. *
  1130. * @param array $idList List of page UIDs, possibly versioned
  1131. * @return void
  1132. */
  1133. protected function findRealPageIds(array &$idList) {
  1134. foreach ($idList as $key => $id) {
  1135. $rec = t3lib_BEfunc::getRecord('pages', $id, 't3ver_oid');
  1136. if ($rec['t3ver_oid'] > 0) {
  1137. $idList[$key] = $rec['t3ver_oid'];
  1138. }
  1139. }
  1140. }
  1141. /**
  1142. * Creates a move placeholder for workspaces.
  1143. * USE ONLY INTERNALLY
  1144. * Moving placeholder: Can be done because the system sees it as a placeholder for NEW elements like t3ver_state=1
  1145. * Moving original: Will either create the placeholder if it doesn't exist or move existing placeholder in workspace.
  1146. *
  1147. * @param string $table Table name to move
  1148. * @param integer $uid Record uid to move (online record)
  1149. * @param integer $destPid Position to move to: $destPid: >=0 then it points to a page-id on which to insert the record (as the first element). <0 then it points to a uid from its own table after which to insert it (works if
  1150. * @param integer $wsUid UID of offline version of online record
  1151. * @param t3lib_TCEmain $tcemainObj TCEmain object
  1152. * @return void
  1153. * @see moveRecord()
  1154. */
  1155. protected function moveRecord_wsPlaceholders($table, $uid, $destPid, $wsUid, t3lib_TCEmain $tcemainObj) {
  1156. global $TCA;
  1157. if ($plh = t3lib_BEfunc::getMovePlaceholder($table, $uid, 'uid')) {
  1158. // If already a placeholder exists, move it:
  1159. $tcemainObj->moveRecord_raw($table, $plh['uid'], $destPid);
  1160. } else {
  1161. // First, we create a placeholder record in the Live workspace that
  1162. // represents the position to where the record is eventually moved to.
  1163. $newVersion_placeholderFieldArray = array();
  1164. if ($TCA[$table]['ctrl']['crdate']) {
  1165. $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
  1166. }
  1167. if ($TCA[$table]['ctrl']['cruser_id']) {
  1168. $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['cruser_id']] = $tcemainObj->userid;
  1169. }
  1170. if ($TCA[$table]['ctrl']['tstamp']) {
  1171. $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
  1172. }
  1173. if ($table == 'pages') {
  1174. // Copy page access settings from original page to placeholder
  1175. $perms_clause = $tcemainObj->BE_USER->getPagePermsClause(1);
  1176. $access = t3lib_BEfunc::readPageAccess($uid, $perms_clause);
  1177. $newVersion_placeholderFieldArray['perms_userid'] = $access['perms_userid'];
  1178. $newVersion_placeholderFieldArray['perms_groupid'] = $access['perms_groupid'];
  1179. $newVersion_placeholderFieldArray['perms_user'] = $access['perms_user'];
  1180. $newVersion_placeholderFieldArray['perms_group'] = $access['perms_group'];
  1181. $newVersion_placeholderFieldArray['perms_everybody'] = $access['perms_everybody'];
  1182. }
  1183. $newVersion_placeholderFieldArray['t3ver_label'] = 'MOVE-TO PLACEHOLDER for #' . $uid;
  1184. $newVersion_placeholderFieldArray['t3ver_move_id'] = $uid;
  1185. // Setting placeholder state value for temporary record
  1186. $newVersion_placeholderFieldArray['t3ver_state'] = 3;
  1187. // Setting workspace - only so display of place holders can filter out those from other workspaces.
  1188. $newVersion_placeholderFieldArray['t3ver_wsid'] = $tcemainObj->BE_USER->workspace;
  1189. $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['label']] = '[MOVE-TO PLACEHOLDER for #' . $uid . ', WS#' . $tcemainObj->BE_USER->workspace . ']';
  1190. // moving localized records requires to keep localization-settings for the placeholder too
  1191. if (array_key_exists('languageField', $GLOBALS['TCA'][$table]['ctrl']) && array_key_exists('transOrigPointerField', $GLOBALS['TCA'][$table]['ctrl'])) {
  1192. $l10nParentRec = t3lib_BEfunc::getRecord($table, $uid);
  1193. $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
  1194. $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
  1195. unset($l10nParentRec);
  1196. }
  1197. // Initially, create at root level.
  1198. $newVersion_placeholderFieldArray['pid'] = 0;
  1199. $id = 'NEW_MOVE_PLH';
  1200. // Saving placeholder as 'original'
  1201. $tcemainObj->insertDB($table, $id, $newVersion_placeholderFieldArray, FALSE);
  1202. // Move the new placeholder from temporary root-level to location:
  1203. $tcemainObj->moveRecord_raw($table, $tcemainObj->substNEWwithIDs[$id], $destPid);
  1204. // Move the workspace-version of the original to be the version of the move-to-placeholder:
  1205. // Setting placeholder state value for version (so it can know it is currently a new version...)
  1206. $updateFields = array(
  1207. 't3ver_state' => 4
  1208. );
  1209. $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($wsUid), $updateFields);
  1210. }
  1211. // Check for the localizations of that element and move them as well
  1212. $tcemainObj->moveL10nOverlayRecords($table, $uid, $destPid);
  1213. }
  1214. /**
  1215. * Gets all possible child tables that are used on each parent table as field.
  1216. *
  1217. * @param string $parentTable Name of the parent table
  1218. * @param array $possibleInlineChildren Collected possible inline children
  1219. * (will be filled automatically during recursive calls)
  1220. * @return array
  1221. */
  1222. protected function getPossibleInlineChildTablesOfParentTable($parentTable, array $possibleInlineChildren = array()) {
  1223. t3lib_div::loadTCA($parentTable);
  1224. foreach ($GLOBALS['TCA'][$parentTable]['columns'] as $parentField => $parentFieldDefinition) {
  1225. if (isset($parentFieldDefinition['config']['type'])) {
  1226. $parentFieldConfiguration = $parentFieldDefinition['config'];
  1227. if ($parentFieldConfiguration['type'] == 'inline' && isset($parentFieldConfiguration['foreign_table'])) {
  1228. if (!in_array($parentFieldConfiguration['foreign_table'], $possibleInlineChildren)) {
  1229. $possibleInlineChildren = $this->getPossibleInlineChildTablesOfParentTable(
  1230. $parentFieldConfiguration['foreign_table'],
  1231. array_merge($possibleInlineChildren, $parentFieldConfiguration['foreign_table'])
  1232. );
  1233. }
  1234. }
  1235. }
  1236. }
  1237. return $possibleInlineChildren;
  1238. }
  1239. /**
  1240. * Gets an instance of the command map helper.
  1241. *
  1242. * @param t3lib_TCEmain $tceMain TCEmain object
  1243. * @param array $commandMap The command map as submitted to t3lib_TCEmain
  1244. * @return tx_version_tcemain_CommandMap
  1245. */
  1246. public function getCommandMap(t3lib_TCEmain $tceMain, array $commandMap) {
  1247. return t3lib_div::makeInstance('tx_version_tcemain_CommandMap', $this, $tceMain, $commandMap);
  1248. }
  1249. }
  1250. if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/version/class.tx_version_tcemain.php'])) {
  1251. include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/version/class.tx_version_tcemain.php']);
  1252. }
  1253. ?>