[TASK] Merge submodule workspaces into core
authorThomas Maroschik <tmaroschik@dfau.de>
Mon, 27 May 2013 17:05:54 +0000 (19:05 +0200)
committerThomas Maroschik <tmaroschik@dfau.de>
Mon, 27 May 2013 17:05:54 +0000 (19:05 +0200)
99 files changed:
.gitmodules
typo3/sysext/workspaces [deleted submodule]
typo3/sysext/workspaces/ChangeLog [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Controller/AbstractController.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Controller/PreviewController.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Controller/ReviewController.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Domain/Model/CombinedRecord.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Domain/Model/DatabaseRecord.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/AbstractHandler.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/ExtDirectServer.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/MassActionHandler.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/PagetreeCollectionsProcessor.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/ToolbarMenu.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/WorkspaceSelectorToolbarItem.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Hook/BackendUtilityHook.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Hook/TypoScriptFrontendControllerHook.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/AutoPublishService.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/GridDataService.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/HistoryService.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/IntegrityService.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/StagesService.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/WorkspaceService.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Task/AutoPublishTask.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Task/CleanupPreviewLinkTask.php [new file with mode: 0644]
typo3/sysext/workspaces/Configuration/TCA/sys_workspace.php [new file with mode: 0644]
typo3/sysext/workspaces/Configuration/TCA/sys_workspace_stage.php [new file with mode: 0644]
typo3/sysext/workspaces/Documentation/manual.odt [new file with mode: 0644]
typo3/sysext/workspaces/Documentation/manual.pdf [new file with mode: 0644]
typo3/sysext/workspaces/Documentation/manual.sxw [new file with mode: 0644]
typo3/sysext/workspaces/Migrations/Code/ClassAliasMap.php [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Language/locallang.xlf [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Language/locallang_csh_sysws_stage.xlf [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xlf [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Language/locallang_mod.xlf [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Layouts/Module.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Layouts/Nodoc.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Layouts/Popup.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Partials/legend.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Partials/navigation.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Preview/Help.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Preview/Index.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Preview/NewPage.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Preview/Preview.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Review/FullIndex.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Review/Index.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Review/SingleIndex.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/bg.gif [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/button_approve.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/button_discard.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/button_reject.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/generate-ws-preview-link.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/moduleicon.gif [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/slider-bg.png [new file with mode: 0755]
typo3/sysext/workspaces/Resources/Public/Images/slider-thumb.png [new file with mode: 0755]
typo3/sysext/workspaces/Resources/Public/Images/typo3-logo.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtonextstage.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtoprevstage.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/workspaces-comments-arrow.gif [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/Ext.ux.plugins.TabStripContainer.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/Store/mainstore.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/component.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/configuration.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/grid.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/GridFilters.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/GridFilters.css [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/RangeMenu.css [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/BooleanFilter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/DateFilter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/Filter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/ListFilter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/NumericFilter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/StringFilter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/equals.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/find.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/greater_than.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/less_than.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_asc.gif [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_desc.gif [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/ListMenu.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/RangeMenu.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/helpers.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/preview.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/toolbar.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/workspacemenu.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/workspaces.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/StyleSheet/module.css [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/StyleSheet/preview.css [new file with mode: 0644]
typo3/sysext/workspaces/Tests/Functional/Service/WorkspaceTest.php [new file with mode: 0644]
typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbDefaultPages.xml [new file with mode: 0644]
typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbDefaultWorkspaces.xml [new file with mode: 0644]
typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbMovedContent.xml [new file with mode: 0644]
typo3/sysext/workspaces/ext_emconf.php [new file with mode: 0644]
typo3/sysext/workspaces/ext_icon.gif [new file with mode: 0644]
typo3/sysext/workspaces/ext_localconf.php [new file with mode: 0644]
typo3/sysext/workspaces/ext_tables.php [new file with mode: 0644]
typo3/sysext/workspaces/ext_tables.sql [new file with mode: 0644]

index 160fa16..685b147 100644 (file)
@@ -1,6 +1,3 @@
 [submodule "typo3/sysext/version"]
        path = typo3/sysext/version
        url = git://git.typo3.org/TYPO3v4/CoreProjects/workspaces/version.git
-[submodule "typo3/sysext/workspaces"]
-       path = typo3/sysext/workspaces
-       url = git://git.typo3.org/TYPO3v4/CoreProjects/workspaces/workspaces.git
diff --git a/typo3/sysext/workspaces b/typo3/sysext/workspaces
deleted file mode 160000 (submodule)
index 5c17d03..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 5c17d038410a779493d2f5cf05b79dd62d616393
diff --git a/typo3/sysext/workspaces/ChangeLog b/typo3/sysext/workspaces/ChangeLog
new file mode 100644 (file)
index 0000000..2eed64b
--- /dev/null
@@ -0,0 +1,333 @@
+2011-02-22  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #12457: Split view und grid don't use StateProvider yet
+       * Fixed bug #12855: Generate preview link visible in Live-Workspace and on non-content pages
+
+2011-02-21  Marco Bresch <typo3@starfinanz.de>
+
+       * Fixed bug #13074: missing cache_frontend configuration
+
+2011-02-20  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #13098:Hide sys_workspace_stage in list view (Thanks to Andreas Kiessling)
+
+2011-02-20  Steffen Kamper  <steffen@typo3.org>
+
+       * Changes according to changed ExtDirect inclusion (#17592)
+
+2011-02-17  Susanne Moog  <typo3@susanne-moog.de>
+
+       * Fixed bug #12621: Documentation for new options concerning stageNotificationEmail
+       * Fixed bug #12693: Naming of Documentation folder
+
+2011-02-16  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #13003: singleIndex rows not showing up due to browser security violations
+       * Fixed bug #13002: singleIndex page much have invalid workspace id
+
+2011-01-26  Susanne Moog  <typo3@susanne-moog.de>
+
+       * Fixed bug #3523: Rewrite of workspace manual started
+
+2011-01-26  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Raised version to 4.5.0
+
+2011-01-26  Marco Bresch  <typo3@starfinanz.de>
+
+       * Fixed bug #12534: Positioning of toolbar in IE6 (Thanks to Markus Antecki)
+
+2011-01-25  Sonja Scholz  <ss@cabag.ch>
+
+       * Fixed bug #10636: workspace module not usable in IE6 (Thanks to Markus Antecki)
+
+2011-01-25  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Raised version to 4.5.0rc2
+       * Fixed bug #12406: Not possible to publish change of IRRE ordering
+       * Fixed bug #12464: Slider element is not always visible during the dragging process..
+
+2011-01-24  Steffen Kamper  <steffen@typo3.org>
+
+       * Fixed bug #17222: fitToParent calculates wrong height - workspace grid needs 40 pixel offset for render the legend
+
+2011-01-21  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Followup to #11539: Split view / initial height calculated right
+       * Followup to #11539: Split view / tooltip styling added
+
+2011-01-21  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #12448: grid preview links are broken
+       * Raised version to 4.5.0rc1
+       * Fixed bug #12415: Respect change #17167: Cache ExtDirect::getAPI calls
+       * Fixed bug #12308: Workspace non-admin not workspace owner sees icon to swap updated version with Live site
+       * Fixed bug #12384: Mount points are not taken into account in element-list
+       * Fixed bug #12325: Hide icons that don't work in the current context
+       * Fixed bug #11539: Split view
+
+2011-01-20  Susanne Moog  <typo3@susanne-moog.de>
+
+       * Fixed bug #12055: Publishing a page does not update the new page tree
+       * Fixed bug #12413: Rename extension "list" to "recordlist"
+       * Fixed bug #12000: Cache and Favorites submenus shifts when in Workspaces
+       * Fixed bug #11574: Workspaces grid: stages should "sit" on one line
+       * Fixed bug #12446: Fixed instantiation of UriBuilder (broken since extbase merge)
+       * Fixed bug #12453: Autopublish task crashes because of misspelled class name (Thanks to Francois Suter)
+
+2011-01-20  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #12386: "Publish only content in publish stage" doesn't work anymore
+       * Fixed bug #12077: Tooltips for slider
+       * Fxied bug #12334:"Allow members to edit records in "Review" stage" should be removed
+       * Fixed bug #12286: Wrong icon for "Generate workspace preview link" - use eye icon instead
+
+2011-01-18  Susanne Moog  <typo3@susanne-moog.de>
+
+       * Fixed bug #11972: Switching workspaces with tabs causes module menu to loose its selection
+       * Fixed bug #12405: The workspaces module does not work in Internet Explorer
+
+2011-01-18  Sonja Scholz  <ss@cabag.ch>
+
+       * Fixed bug #11481: Naming of "Release" mass action is unclear
+       * Fixed bug #12350: Wrong backpath for thumbnail generation in diff view
+
+2011-01-18  Marco Bresch  <typo3@starfinanz.de>
+
+       * Fixed bug #10439: Data array used in ws-grid should be cached
+
+2011-01-18  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #12347: Incorrect registration of status report
+       * Fixed bug #12333: "Un-Publish:" should be hidden
+       * Fixed bug #12322: "null" is shown in diff view if original fields are empty in Firefox
+       * Fixed bug #11587: Trash icon should "discard" too
+       * Fixed bug #11529: Infinite not infinite / moved pages not always visible in element list
+
+2011-01-16  Sonja Scholz  <ss@cabag.ch>
+
+       * Fixed bug #11825: non-admin editors can't see non-page records within the review module
+       * Fixed bug #12272: Topbar isn't highlighted if there's only on workspace
+       * Fixed bug #12273: "Go to workspace module" link appears even if user has no access to it
+
+2011-01-16  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Followup to #16630: migrateWorkspaces in Install-Tool does not work, if extbase is not intalled
+       * Fixed bug #12076: Remove "help" tab
+       * Fixed bug #12192: Description of workspace module for "about modules" missing
+       * Fixed bug #12194: Legend too wide
+
+2011-01-16  Sonja Scholz  <ss@cabag.ch>
+
+       * Fixed bug #12291: Icon "Open version of page" does not go the the page of the clicked element
+
+2011-01-15  Marco Bresch  <typo3@starfinanz.de>
+
+       * Fixed bug: include htmlspecialchars for labels to display html-tags
+       * Bug #11585: Hide difference column for default
+
+2011-01-12  Oliver Hader  <oliver.hader@typo3.org>
+
+       * Fixed bug: migrateWorkspaces in Install-Tool does not work, if extbase is not intalled
+       * Raised version to 4.5.0beta4
+
+2011-01-09  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #11971: Live Tab is linked in Live view
+
+2011-01-06  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #11815: WorkspacePreviewLink should be a link
+       * Fixed bug #11029: Workspaces module: Row detail view (Thanks to Lars Zimmermann and Berit Jensen)
+
+2011-01-06  Steffen Kamper  <steffen@typo3.org>
+
+       * Fixed bug #11674: Selection of row deletes checkbox selection
+       * Fixed bug #11647: Switching workspace doesn't refresh new pagetree
+
+2011-01-01  Benjamin Mack  <benni@typo3.org>
+
+       * Fixed bug #3358: make sure only "element" versioning type is supported / used - added status report for Reports module
+
+2010-12-30  Susanne Moog  <typo3@susanne-moog.de>
+
+       * Fixed bug #11789: fluid viewhelper renderFlashMessages is deprecated
+
+2010-12-28  Steffen Kamper  <steffen@typo3.org>
+
+       * Follow-up to #11635: Removed remaining event listener
+
+2010-12-26  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed issue #11722: TempFlashMessageQueueViewHelper is not required anymore
+       * Fixed bug #11718: ExtJS confirmation window contents might be "cached"
+
+2010-12-25  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #11473: Send to stage window may be too large
+       * Fixed bug #11608: Missing space in Mass Release Confirmation
+       * Fixed bug #11635: Remove "Preview of workspace ..." box when in workspace preview module
+       * Fixed bug #11657: singleView should not have depth and mass-action combos
+       * Fixed bug #11600: [All] tab in ws-module should not have action-buttons
+
+2010-12-22  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #11568: Remove "Enable comparison view" switch
+       * Fixed bug #11479: Label of publish action is confusing
+
+2010-12-22  Steffen Gebert  <steffen@steffen-gebert.de>
+
+       * Added feature #10642: Design of workspace preview/comparison view (Thanks to Lars Zimmermann)
+
+2010-12-21  Sonja Scholz  <ss@cabag.ch>
+
+       * Fixed bug #11480: Add warning when choosing mass actions
+
+2010-12-21  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Followup-to #9819: Workspace preview window - browser compatibility fixed
+
+2010-12-20  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #10693: Save and view command previews wrong page record
+       * Fixed bug #11532: Generate Workspace Preview Link not working
+       * Fixed bug #11605: Do you really want to swap?? - one question mark is enough
+       * Fixed bug #11531 Check if email is set before trying to send an email
+       * Fixed bug #11141: switching with the tabs in the workspace module should also switch to the related workspace
+
+2010-12-19  Marco Bresch  <marco.bresch@starfinanz.de>
+
+       * Fixed bug #11142: The 'All' tab throws Exception 'No such workspace defined'
+
+2010-12-19  Sonja Scholz  <ss@cabag.ch>
+
+       * Cleanup: #11153: Determine whether Tx_Workspaces_Service_Stages::encodeStageUid and resolveStageUid are required
+
+2010-12-19  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Followup-to #9819: Workspace preview window - "live" and "workspace" buttons are clickable now
+
+2010-12-18  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #11318: workspaces task miss title
+
+2010-12-17  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Followup-to #9819: Workspace preview window - empty preview.css included in order to support the skin team
+
+2010-12-14  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #9819: Workspace preview window
+
+2010-12-05  Steffen Gebert  <steffen@steffen-gebert.de>
+
+       * Fixed bug #11290: PHP notices from XCLASS inclusions
+
+2010-12-01  Oliver Hader  <oliver@typo3.org>
+
+       * Raised version to 4.5.0beta2b
+
+2010-12-01  Steffen Ritter  <typo3@steffen-ritter.net>
+
+       * Follow-Up #11131: Usability: labels improvements, change the label as a new one is introduced
+
+2010-12-01  Oliver Hader  <oliver@typo3.org>
+
+       * Raised version to 4.5.0beta2a
+
+2010-12-01  Steffen Gebert  <steffen@steffen-gebert.de>
+
+       * Fixed bug #11193: Remove calls to disable compression and merging of CSS/JS
+
+2010-12-01  Oliver Hader  <oliver@typo3.org>
+
+       * Cleanup: Fixed ext_emconf.php
+       * Raised version to 4.5.0beta2
+
+2010-11-30  Steffen Ritter  <typo3@steffen-ritter.net>
+
+       * Fixed bug #11143: Editing workspaces record shows PHP warning
+       * Fixed bug #10291: change action icons
+       * Fixed bug #7050: Remove UID from WSP Dropdown
+       * Fixed bug #11152 Calls about this-> in static context in workspaces-lib
+       * Cleanup: Added several small changes from the Skin-Team at the xTemplate
+
+2010-11-30  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug #11138: preview links in page- and list-module point to mod.php instead of typo3/mod.php
+
+2010-11-30  Oliver Hader  <oliver@tpyo3.org>
+
+       * Fixed bug #11124: Stages should use suggest wizard for persons
+       * Cleanup: Fixed PHPdoc comments and exception timestamp
+       * Fixed bug #11115: ExtDirect exception sendToNextStageWindow: StageId is supposed to be an integer
+       * Fixed task #11161: Simplify outputting errors from PHP to the grid component
+       * Fixed bug #11144: Records that belong to a particular workspace shall be removed when the workspace is removed
+
+2010-11-29  Sonja Scholz  <ss@cabag.ch>
+
+       * Fixed bug: #11131: Usability: labels improvements
+
+2010-11-29  Tolleiv Nietsch  <typo3@tolleiv.de>
+
+       * Fixed bug: #11006: Error when using old-style workspace
+       * Fixed bug: #10831: New preview window shows wrong behavior for new page in LIVE WS
+
+2010-11-28  Sonja Scholz  <ss@cabag.ch>
+
+       * Fixed bug: #11116: Add some CSS classes for detailView
+
+2010-11-22  Oliver Hader  <oliver@tpyo3.org>
+
+       * Cleanup: Fixed PHPdoc comments
+       * Fixed bug #11033: DB error in query after publish to Live action
+
+2010-11-17  Tolleiv Nietsch  <info@tolleiv.de>
+
+       * Fixed bug: #10916 workspace preview module raises error in conjunction with the newest extbase version
+
+2010-11-17  Oliver Hader  <oliver@tpyo3.org>
+
+       * Raised version to 4.5.0beta1a
+
+2010-11-17  Tolleiv Nietsch  <info@tolleiv.de>
+
+       * Fixed bug: #10896 php-warnings after merge into Core - missing argument
+       * Fixed bug: #10895 php-warnings after merge into Core - array_merge on non-array
+
+2010-11-17  Steffen Ritter  <info@rs-websystems.de>
+
+       * Fixed bug #10894: Workspaces Extbase Controller has to be adapted according to breaking changes in Extbase 1.3.beta1
+
+2010-11-17  Oliver Hader  <oliver@tpyo3.org>
+
+       * Cleanup: Fixed ext_emconf.php
+       * Raised version to 4.5.0beta1
+
+2010-11-16  Tolleiv Nietsch  <info@tolleiv.de>
+
+       * Fixed bug: #10778 Consider http://bugs.typo3.org/view.php?id=9508 when implementing preview link in new module
+       * Fixed bug: #10819 viewOnClick causes error if frontendpreview is disabled
+       * Fixed issue: #10817 DAU WS-Module warning
+
+2010-11-12  Oliver Hader  <oliver@tpyo3.org>
+
+       * Fixed bug: Typing error and superfluous labels in locallang file
+       * Fixed bug: Missing fields in SQL definition reviewers, stagechg_notification
+       * Cleanup: Defined svn:eol-style property
+       * Cleanup: Fixed formatting and configuration in the ExtJS part (thanks to Steffen Kamper)
+       * Cleanup: Added language label for swap workspace column (thanks to Steffen Kamper)
+       * Cleanup: Defined fixed width of ExtJS components (thanks to Steffen Kamper)
+       * Cleanup: Remover superfluous quotes (thanks to Steffen Kamper)
+       * Cleanup: Fixed undefined variables and added PHPdoc comments
+
+2010-11-11  Oliver Hader  <oliver@tpyo3.org>
+
+       * Cleanup: Added PHPdoc comments and some formatting changes
+       * Cleanup: Fixed formatting issues
+       * Cleanup: Fixed copyright notices
+       * Cleanup: Fixed ext_emconf.php
+
+2010-11-11  Workspaces Team  http://forge.typo3.org/projects/show/typo3v4-workspaces
+
+       * Imported workspaces system extension from GitHub repository after commit f1cd6371d96faa8a8d35
diff --git a/typo3/sysext/workspaces/Classes/Controller/AbstractController.php b/typo3/sysext/workspaces/Classes/Controller/AbstractController.php
new file mode 100644 (file)
index 0000000..6e5ac75
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Controller;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Abstract action controller.
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class AbstractController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
+
+       /**
+        * @var string Key of the extension this controller belongs to
+        */
+       protected $extensionName = 'Workspaces';
+
+       /**
+        * @var \TYPO3\CMS\Core\Page\PageRenderer
+        */
+       protected $pageRenderer;
+
+       /**
+        * @var integer
+        */
+       protected $pageId;
+
+       /**
+        * Initializes the controller before invoking an action method.
+        *
+        * @return void
+        */
+       protected function initializeAction() {
+               // @todo Evaluate how the intval() call can be used with Extbase validators/filters
+               $this->pageId = intval(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('id'));
+               $icons = array(
+                       'language' => \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconClasses('flags-multiple'),
+                       'integrity' => \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconClasses('status-dialog-information'),
+                       'success' => \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconClasses('status-dialog-ok'),
+                       'info' => \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconClasses('status-dialog-information'),
+                       'warning' => \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconClasses('status-dialog-warning'),
+                       'error' => \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconClasses('status-dialog-error')
+               );
+               $this->pageRenderer->addInlineSetting('Workspaces', 'icons', $icons);
+               $this->pageRenderer->addInlineSetting('Workspaces', 'id', $this->pageId);
+               $this->pageRenderer->addInlineSetting('Workspaces', 'depth', $this->pageId === 0 ? 999 : 1);
+               $this->pageRenderer->addInlineSetting('Workspaces', 'language', $this->getLanguageSelection());
+               $this->pageRenderer->addCssFile(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('workspaces') . 'Resources/Public/StyleSheet/module.css');
+               $this->pageRenderer->addInlineLanguageLabelArray(array(
+                       'title' => $GLOBALS['LANG']->getLL('title'),
+                       'path' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.path'),
+                       'table' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.table'),
+                       'depth' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_perm.xml:Depth'),
+                       'depth_0' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_0'),
+                       'depth_1' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_1'),
+                       'depth_2' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_2'),
+                       'depth_3' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_3'),
+                       'depth_4' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_4'),
+                       'depth_infi' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_infi')
+               ));
+               $this->pageRenderer->addInlineLanguageLabelFile('EXT:workspaces/Resources/Private/Language/locallang.xml');
+       }
+
+       /**
+        * Processes a general request. The result can be returned by altering the given response.
+        *
+        * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request object
+        * @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response The response, modified by this handler
+        * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException if the controller doesn't support the current request type
+        * @return void
+        */
+       public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response) {
+               $this->template = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Template\\DocumentTemplate');
+               $this->pageRenderer = $this->template->getPageRenderer();
+               $GLOBALS['SOBE'] = new \stdClass();
+               $GLOBALS['SOBE']->doc = $this->template;
+               parent::processRequest($request, $response);
+               $pageHeader = $this->template->startpage($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:module.title'));
+               $pageEnd = $this->template->endPage();
+               $response->setContent($pageHeader . $response->getContent() . $pageEnd);
+       }
+
+       /**
+        * Gets the selected language.
+        *
+        * @return string
+        */
+       protected function getLanguageSelection() {
+               $language = 'all';
+               if (isset($GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['language'])) {
+                       $language = $GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['language'];
+               }
+               return $language;
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Controller/PreviewController.php b/typo3/sysext/workspaces/Classes/Controller/PreviewController.php
new file mode 100644 (file)
index 0000000..aaeadd2
--- /dev/null
@@ -0,0 +1,296 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Controller;
+use TYPO3\CMS\Core\Utility;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Implements the preview controller of the workspace module.
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class PreviewController extends \TYPO3\CMS\Workspaces\Controller\AbstractController {
+
+       /**
+        * @var \TYPO3\CMS\Workspaces\Service\StagesService
+        */
+       protected $stageService;
+
+       /**
+        * @var \TYPO3\CMS\Workspaces\Service\WorkspaceService
+        */
+       protected $workspaceService;
+
+       /**
+        * Initializes the controller before invoking an action method.
+        *
+        * @return void
+        */
+       protected function initializeAction() {
+               parent::initializeAction();
+               $this->stageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\StagesService');
+               $this->workspaceService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+               $this->template->setExtDirectStateProvider();
+               $resourcePath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('workspaces') . 'Resources/Public/StyleSheet/preview.css';
+               $GLOBALS['TBE_STYLES']['extJS']['theme'] = $resourcePath;
+               $this->pageRenderer->loadExtJS();
+               $this->pageRenderer->enableExtJSQuickTips();
+               // Load  JavaScript:
+               $this->pageRenderer->addExtDirectCode(array(
+                       'TYPO3.Workspaces',
+                       'TYPO3.ExtDirectStateProvider'
+               ));
+               $states = $GLOBALS['BE_USER']->uc['moduleData']['Workspaces']['States'];
+               $this->pageRenderer->addInlineSetting('Workspaces', 'States', $states);
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/notifications.js');
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/ux/flashmessages.js');
+               $this->pageRenderer->addJsFile($this->backPath . 'js/extjs/iframepanel.js');
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/notifications.js');
+               $resourcePathJavaScript = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('workspaces') . 'Resources/Public/JavaScript/';
+               $jsFiles = array(
+                       'Ext.ux.plugins.TabStripContainer.js',
+                       'Store/mainstore.js',
+                       'helpers.js',
+                       'actions.js'
+               );
+               foreach ($jsFiles as $jsFile) {
+                       $this->pageRenderer->addJsFile($resourcePathJavaScript . $jsFile);
+               }
+               // todo this part should be done with inlineLocallanglabels
+               $this->pageRenderer->addJsInlineCode('workspace-inline-code', $this->generateJavascript());
+       }
+
+       /**
+        * Basically makes sure that the workspace preview is rendered.
+        * The preview itself consists of three frames, so there are
+        * only the frames-urls we've to generate here
+        *
+        * @param integer $previewWS
+        * @return void
+        */
+       public function indexAction($previewWS = NULL) {
+               // @todo language doesn't always come throught the L parameter
+               // @todo Evaluate how the intval() call can be used with Extbase validators/filters
+               $language = intval(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('L'));
+               // fetch the next and previous stage
+               $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), ($filter = 1), ($stage = -99), $this->pageId, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
+               list(, $nextStage) = $this->stageService->getNextStageForElementCollection($workspaceItemsArray);
+               list(, $previousStage) = $this->stageService->getPreviousStageForElementCollection($workspaceItemsArray);
+               /** @var $wsService \TYPO3\CMS\Workspaces\Service\WorkspaceService */
+               $wsService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+               $wsList = $wsService->getAvailableWorkspaces();
+               $activeWorkspace = $GLOBALS['BE_USER']->workspace;
+               if (!is_null($previewWS)) {
+                       if (in_array($previewWS, array_keys($wsList)) && $activeWorkspace != $previewWS) {
+                               $activeWorkspace = $previewWS;
+                               $GLOBALS['BE_USER']->setWorkspace($activeWorkspace);
+                               \TYPO3\CMS\Backend\Utility\BackendUtility::setUpdateSignal('updatePageTree');
+                       }
+               }
+               /** @var $uriBuilder \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder */
+               $uriBuilder = $this->objectManager->create('TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder');
+               $wsSettingsPath = \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . 'typo3/';
+               $wsSettingsUri = $uriBuilder->uriFor('singleIndex', array(), 'TYPO3\\CMS\\Workspaces\\Controller\\ReviewController', 'workspaces', 'web_workspacesworkspaces');
+               $wsSettingsParams = '&tx_workspaces_web_workspacesworkspaces[controller]=Review';
+               $wsSettingsUrl = $wsSettingsPath . $wsSettingsUri . $wsSettingsParams;
+               $viewDomain = \TYPO3\CMS\Backend\Utility\BackendUtility::getViewDomain($this->pageId);
+               $wsBaseUrl = $viewDomain . '/index.php?id=' . $this->pageId . '&L=' . $language;
+               // @todo - handle new pages here
+               // branchpoints are not handled anymore because this feature is not supposed anymore
+               if (\TYPO3\CMS\Workspaces\Service\WorkspaceService::isNewPage($this->pageId)) {
+                       $wsNewPageUri = $uriBuilder->uriFor('newPage', array(), 'TYPO3\\CMS\\Workspaces\\Controller\\PreviewController', 'workspaces', 'web_workspacesworkspaces');
+                       $wsNewPageParams = '&tx_workspaces_web_workspacesworkspaces[controller]=Preview';
+                       $this->view->assign('liveUrl', $wsSettingsPath . $wsNewPageUri . $wsNewPageParams);
+               } else {
+                       $this->view->assign('liveUrl', $wsBaseUrl . '&ADMCMD_noBeUser=1');
+               }
+               $this->view->assign('wsUrl', $wsBaseUrl . '&ADMCMD_view=1&ADMCMD_editIcons=1&ADMCMD_previewWS=' . $GLOBALS['BE_USER']->workspace);
+               $this->view->assign('wsSettingsUrl', $wsSettingsUrl);
+               $this->view->assign('backendDomain', \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'));
+               $splitPreviewTsConfig = \TYPO3\CMS\Backend\Utility\BackendUtility::getModTSconfig($this->pageId, 'workspaces.splitPreviewModes');
+               $splitPreviewModes = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $splitPreviewTsConfig['value']);
+               $allPreviewModes = array('slider', 'vbox', 'hbox');
+               if (!array_intersect($splitPreviewModes, $allPreviewModes)) {
+                       $splitPreviewModes = $allPreviewModes;
+               }
+               $this->pageRenderer->addInlineSetting('Workspaces', 'SplitPreviewModes', $splitPreviewModes);
+               $GLOBALS['BE_USER']->setAndSaveSessionData('workspaces.backend_domain', \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'));
+               $this->pageRenderer->addInlineSetting('Workspaces', 'disableNextStageButton', $this->isInvalidStage($nextStage));
+               $this->pageRenderer->addInlineSetting('Workspaces', 'disablePreviousStageButton', $this->isInvalidStage($previousStage));
+               $this->pageRenderer->addInlineSetting('Workspaces', 'disableDiscardStageButton', $this->isInvalidStage($nextStage) && $this->isInvalidStage($previousStage));
+               $resourcePath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('lang') . 'res/js/be/';
+               $this->pageRenderer->addJsFile($resourcePath . 'typo3lang.js');
+               $this->pageRenderer->addJsInlineCode('workspaces.preview.lll', '
+               TYPO3.lang = {
+                       visualPreview: ' . Utility\GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.visualPreview', TRUE)) . ',
+                       listView: ' . Utility\GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.listView', TRUE)) . ',
+                       livePreview: ' . Utility\GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.livePreview', TRUE)) . ',
+                       livePreviewDetail: ' . Utility\GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.livePreviewDetail', TRUE)) . ',
+                       workspacePreview: ' . Utility\GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.workspacePreview', TRUE)) . ',
+                       workspacePreviewDetail: ' . Utility\GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.workspacePreviewDetail', TRUE)) . ',
+                       modeSlider: ' . Utility\GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.modeSlider', TRUE)) . ',
+                       modeVbox: ' . Utility\GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.modeVbox', TRUE)) . ',
+                       modeHbox: ' . Utility\GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.modeHbox', TRUE)) . ',
+                       discard: ' . Utility\GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:label_doaction_discard', TRUE)) . ',
+                       nextStage: ' . Utility\GeneralUtility::quoteJSvalue($nextStage['title']) . ',
+                       previousStage: ' . Utility\GeneralUtility::quoteJSvalue($previousStage['title']) . '
+               };TYPO3.l10n.initialize();
+');
+               $resourcePath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('workspaces') . 'Resources/Public/';
+               $this->pageRenderer->addJsFile($resourcePath . 'JavaScript/preview.js');
+       }
+
+       /**
+        * Evaluate the activate state based on given $stageArray.
+        *
+        * @param array $stageArray
+        * @return boolean
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       protected function isInvalidStage($stageArray) {
+               return !(is_array($stageArray) && count($stageArray) > 0);
+       }
+
+       /**
+        * @return void
+        */
+       public function newPageAction() {
+               $flashMessage = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessage', $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:info.newpage.detail'), $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:info.newpage'), \TYPO3\CMS\Core\Messaging\FlashMessage::INFO);
+               /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
+               $flashMessageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessageService');
+               /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
+               $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+               $defaultFlashMessageQueue->enqueue($flashMessage);
+       }
+
+       /**
+        * Generates the JavaScript code for the backend,
+        * and since we're loading a backend module outside of the actual backend
+        * this copies parts of the backend.php
+        *
+        * @return      string
+        */
+       protected function generateJavascript() {
+               $pathTYPO3 = \TYPO3\CMS\Core\Utility\GeneralUtility::dirname(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('SCRIPT_NAME')) . '/';
+               // If another page module was specified, replace the default Page module with the new one
+               $newPageModule = trim($GLOBALS['BE_USER']->getTSConfigVal('options.overridePageModule'));
+               $pageModule = \TYPO3\CMS\Backend\Utility\BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
+               if (!$GLOBALS['BE_USER']->check('modules', $pageModule)) {
+                       $pageModule = '';
+               }
+               $menuFrameName = 'menu';
+               if ($GLOBALS['BE_USER']->uc['noMenuMode'] === 'icons') {
+                       $menuFrameName = 'topmenuFrame';
+               }
+               // determine security level from conf vars and default to super challenged
+               if ($GLOBALS['TYPO3_CONF_VARS']['BE']['loginSecurityLevel']) {
+                       $loginSecurityLevel = $GLOBALS['TYPO3_CONF_VARS']['BE']['loginSecurityLevel'];
+               } else {
+                       $loginSecurityLevel = 'superchallenged';
+               }
+               $t3Configuration = array(
+                       'siteUrl' => \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL'),
+                       'PATH_typo3' => $pathTYPO3,
+                       'PATH_typo3_enc' => rawurlencode($pathTYPO3),
+                       'username' => htmlspecialchars($GLOBALS['BE_USER']->user['username']),
+                       'uniqueID' => \TYPO3\CMS\Core\Utility\GeneralUtility::shortMD5(uniqid('')),
+                       'securityLevel' => $this->loginSecurityLevel,
+                       'TYPO3_mainDir' => TYPO3_mainDir,
+                       'pageModule' => $pageModule,
+                       'condensedMode' => $GLOBALS['BE_USER']->uc['condensedMode'] ? 1 : 0,
+                       'inWorkspace' => $GLOBALS['BE_USER']->workspace !== 0 ? 1 : 0,
+                       'workspaceFrontendPreviewEnabled' => $GLOBALS['BE_USER']->user['workspace_preview'] ? 1 : 0,
+                       'veriCode' => $GLOBALS['BE_USER']->veriCode(),
+                       'denyFileTypes' => PHP_EXTENSIONS_DEFAULT,
+                       'moduleMenuWidth' => $this->menuWidth - 1,
+                       'topBarHeight' => isset($GLOBALS['TBE_STYLES']['dims']['topFrameH']) ? intval($GLOBALS['TBE_STYLES']['dims']['topFrameH']) : 30,
+                       'showRefreshLoginPopup' => isset($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup']) ? intval($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup']) : FALSE,
+                       'listModulePath' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('recordlist') ? \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('recordlist') . 'mod1/' : '',
+                       'debugInWindow' => $GLOBALS['BE_USER']->uc['debugInWindow'] ? 1 : 0,
+                       'ContextHelpWindows' => array(
+                               'width' => 600,
+                               'height' => 400
+                       )
+               );
+               $t3LLLcore = array(
+                       'waitTitle' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_logging_in'),
+                       'refresh_login_failed' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_failed'),
+                       'refresh_login_failed_message' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_failed_message'),
+                       'refresh_login_title' => sprintf($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_title'), htmlspecialchars($GLOBALS['BE_USER']->user['username'])),
+                       'login_expired' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.login_expired'),
+                       'refresh_login_username' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_username'),
+                       'refresh_login_password' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_password'),
+                       'refresh_login_emptyPassword' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_emptyPassword'),
+                       'refresh_login_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_button'),
+                       'refresh_logout_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_logout_button'),
+                       'please_wait' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.please_wait'),
+                       'loadingIndicator' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:loadingIndicator'),
+                       'be_locked' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.be_locked'),
+                       'refresh_login_countdown_singular' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_countdown_singular'),
+                       'refresh_login_countdown' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_countdown'),
+                       'login_about_to_expire' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.login_about_to_expire'),
+                       'login_about_to_expire_title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.login_about_to_expire_title'),
+                       'refresh_login_refresh_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_refresh_button'),
+                       'refresh_direct_logout_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_direct_logout_button'),
+                       'tabs_closeAll' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:tabs.closeAll'),
+                       'tabs_closeOther' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:tabs.closeOther'),
+                       'tabs_close' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:tabs.close'),
+                       'tabs_openInBrowserWindow' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:tabs.openInBrowserWindow'),
+                       'donateWindow_title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:donateWindow.title'),
+                       'donateWindow_message' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:donateWindow.message'),
+                       'donateWindow_button_donate' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:donateWindow.button_donate'),
+                       'donateWindow_button_disable' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:donateWindow.button_disable'),
+                       'donateWindow_button_postpone' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:donateWindow.button_postpone')
+               );
+               $js = '
+               TYPO3.configuration = ' . json_encode($t3Configuration) . ';
+               TYPO3.LLL = {
+                       core : ' . json_encode($t3LLLcore) . '
+               };
+
+               /**
+                * TypoSetup object.
+                */
+               function typoSetup()    {       //
+                       this.PATH_typo3 = TYPO3.configuration.PATH_typo3;
+                       this.PATH_typo3_enc = TYPO3.configuration.PATH_typo3_enc;
+                       this.username = TYPO3.configuration.username;
+                       this.uniqueID = TYPO3.configuration.uniqueID;
+                       this.navFrameWidth = 0;
+                       this.securityLevel = TYPO3.configuration.securityLevel;
+                       this.veriCode = TYPO3.configuration.veriCode;
+                       this.denyFileTypes = TYPO3.configuration.denyFileTypes;
+               }
+               var TS = new typoSetup();
+                       //backwards compatibility
+               ';
+               return $js;
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Controller/ReviewController.php b/typo3/sysext/workspaces/Classes/Controller/ReviewController.php
new file mode 100644 (file)
index 0000000..adbac30
--- /dev/null
@@ -0,0 +1,182 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Controller;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Steffen Ritter (steffen@typo3.org)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Review controller.
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class ReviewController extends \TYPO3\CMS\Workspaces\Controller\AbstractController {
+
+       /**
+        * Renders the review module user dependent with all workspaces.
+        * The module will show all records of one workspace.
+        *
+        * @return void
+        */
+       public function indexAction() {
+               $wsService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+               $this->view->assign('showGrid', !($GLOBALS['BE_USER']->workspace === 0 && !$GLOBALS['BE_USER']->isAdmin()));
+               $this->view->assign('showAllWorkspaceTab', $GLOBALS['BE_USER']->isAdmin());
+               $this->view->assign('pageUid', \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('id'));
+               $this->view->assign('showLegend', !($GLOBALS['BE_USER']->workspace === 0 && !$GLOBALS['BE_USER']->isAdmin()));
+               $wsList = $wsService->getAvailableWorkspaces();
+               $activeWorkspace = $GLOBALS['BE_USER']->workspace;
+               $performWorkspaceSwitch = FALSE;
+               // Only admins see multiple tabs, we decided to use it this
+               // way for usability reasons. Regular users might be confused
+               // by switching workspaces with the tabs in a module.
+               if (!$GLOBALS['BE_USER']->isAdmin()) {
+                       $wsCur = array($activeWorkspace => TRUE);
+                       $wsList = array_intersect_key($wsList, $wsCur);
+               } else {
+                       if (strlen(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('workspace'))) {
+                               $switchWs = (int) \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('workspace');
+                               if (in_array($switchWs, array_keys($wsList)) && $activeWorkspace != $switchWs) {
+                                       $activeWorkspace = $switchWs;
+                                       $GLOBALS['BE_USER']->setWorkspace($activeWorkspace);
+                                       $performWorkspaceSwitch = TRUE;
+                                       \TYPO3\CMS\Backend\Utility\BackendUtility::setUpdateSignal('updatePageTree');
+                               } elseif ($switchWs == \TYPO3\CMS\Workspaces\Service\WorkspaceService::SELECT_ALL_WORKSPACES) {
+                                       $this->redirect('fullIndex');
+                               }
+                       }
+               }
+               $this->pageRenderer->addInlineSetting('Workspaces', 'isLiveWorkspace', $GLOBALS['BE_USER']->workspace == 0 ? TRUE : FALSE);
+               $this->view->assign('performWorkspaceSwitch', $performWorkspaceSwitch);
+               $this->view->assign('workspaceList', $wsList);
+               $this->view->assign('activeWorkspaceUid', $activeWorkspace);
+               $this->view->assign('activeWorkspaceTitle', \TYPO3\CMS\Workspaces\Service\WorkspaceService::getWorkspaceTitle($activeWorkspace));
+               $this->view->assign('showPreviewLink', $wsService->canCreatePreviewLink(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('id'), $activeWorkspace));
+               $GLOBALS['BE_USER']->setAndSaveSessionData('tx_workspace_activeWorkspace', $activeWorkspace);
+       }
+
+       /**
+        * Renders the review module for admins.
+        * The module will show all records of all workspaces.
+        *
+        * @return void
+        */
+       public function fullIndexAction() {
+               if (!$GLOBALS['BE_USER']->isAdmin()) {
+                       $this->redirect('index');
+               } else {
+                       $wsService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+                       $this->view->assign('pageUid', \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('id'));
+                       $this->view->assign('showGrid', TRUE);
+                       $this->view->assign('showLegend', TRUE);
+                       $this->view->assign('showAllWorkspaceTab', $GLOBALS['BE_USER']->isAdmin());
+                       $this->view->assign('workspaceList', $wsService->getAvailableWorkspaces());
+                       $this->view->assign('activeWorkspaceUid', \TYPO3\CMS\Workspaces\Service\WorkspaceService::SELECT_ALL_WORKSPACES);
+                       $this->view->assign('showPreviewLink', FALSE);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('tx_workspace_activeWorkspace', \TYPO3\CMS\Workspaces\Service\WorkspaceService::SELECT_ALL_WORKSPACES);
+                       // set flag for javascript
+                       $this->pageRenderer->addInlineSetting('Workspaces', 'allView', '1');
+               }
+       }
+
+       /**
+        * Renders the review module for a single page. This is used within the
+        * workspace-preview frame.
+        *
+        * @return void
+        */
+       public function singleIndexAction() {
+               $wsService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+               $wsList = $wsService->getAvailableWorkspaces();
+               $activeWorkspace = $GLOBALS['BE_USER']->workspace;
+               $wsCur = array($activeWorkspace => TRUE);
+               $wsList = array_intersect_key($wsList, $wsCur);
+               $this->view->assign('pageUid', \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('id'));
+               $this->view->assign('showGrid', TRUE);
+               $this->view->assign('showAllWorkspaceTab', FALSE);
+               $this->view->assign('workspaceList', $wsList);
+               $this->view->assign('backendDomain', \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'));
+               $this->pageRenderer->addInlineSetting('Workspaces', 'singleView', '1');
+       }
+
+       /**
+        * Initializes the controller before invoking an action method.
+        *
+        * @return void
+        */
+       protected function initializeAction() {
+               parent::initializeAction();
+               $this->template->setExtDirectStateProvider();
+               if (\TYPO3\CMS\Workspaces\Service\WorkspaceService::isOldStyleWorkspaceUsed()) {
+                       $flashMessage = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessage', $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:warning.oldStyleWorkspaceInUser'), '', \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING);
+                       /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
+                       $flashMessageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessageService');
+                       /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
+                       $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+                       $defaultFlashMessageQueue->enqueue($flashMessage);
+               }
+               $this->pageRenderer->loadExtJS();
+               $this->pageRenderer->enableExtJSQuickTips();
+               $states = $GLOBALS['BE_USER']->uc['moduleData']['Workspaces']['States'];
+               $this->pageRenderer->addInlineSetting('Workspaces', 'States', $states);
+               // Load  JavaScript:
+               $this->pageRenderer->addExtDirectCode(array(
+                       'TYPO3.Workspaces'
+               ));
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/ux/flashmessages.js');
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/ux/Ext.grid.RowExpander.js');
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/ux/Ext.app.SearchField.js');
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/ux/Ext.ux.FitToParent.js');
+               $resourcePath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('workspaces') . 'Resources/Public/JavaScript/';
+               $this->pageRenderer->addCssFile($resourcePath . 'gridfilters/css/GridFilters.css');
+               $this->pageRenderer->addCssFile($resourcePath . 'gridfilters/css/RangeMenu.css');
+               $jsFiles = array(
+                       'gridfilters/menu/RangeMenu.js',
+                       'gridfilters/menu/ListMenu.js',
+                       'gridfilters/GridFilters.js',
+                       'gridfilters/filter/Filter.js',
+                       'gridfilters/filter/StringFilter.js',
+                       'gridfilters/filter/DateFilter.js',
+                       'gridfilters/filter/ListFilter.js',
+                       'gridfilters/filter/NumericFilter.js',
+                       'gridfilters/filter/BooleanFilter.js',
+                       'gridfilters/filter/BooleanFilter.js',
+                       'Store/mainstore.js',
+                       'configuration.js',
+                       'helpers.js',
+                       'actions.js',
+                       'component.js',
+                       'toolbar.js',
+                       'grid.js',
+                       'workspaces.js'
+               );
+               foreach ($jsFiles as $jsFile) {
+                       $this->pageRenderer->addJsFile($resourcePath . $jsFile);
+               }
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Domain/Model/CombinedRecord.php b/typo3/sysext/workspaces/Classes/Domain/Model/CombinedRecord.php
new file mode 100644 (file)
index 0000000..deea8d1
--- /dev/null
@@ -0,0 +1,170 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Domain\Model;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2012-2013 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Combined record class
+ *
+ * @author Oliver Hader <oliver.hader@typo3.org>
+ */
+class CombinedRecord {
+
+       /**
+        * @var string
+        */
+       protected $table;
+
+       /**
+        * @var \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord
+        */
+       protected $versionRecord;
+
+       /**
+        * @var \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord
+        */
+       protected $liveRecord;
+
+       /**
+        * Creates combined record object just by live-id and version-id of database record rows.
+        *
+        * @param string $table Name of the database table
+        * @param integer $liveId Id of the database live-record row
+        * @param integer $versionId Id of the datbase version-record row
+        * @return \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord
+        */
+       static public function create($table, $liveId, $versionId) {
+               $liveRecord = \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord::create($table, $liveId);
+               $versionRecord = \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord::create($table, $versionId);
+               return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Domain\\Model\\CombinedRecord', $table, $liveRecord, $versionRecord);
+       }
+
+       /**
+        * Creates combined record object by relevant database live-record and version-record rows.
+        *
+        * @param string $table Name of the database table
+        * @param array $liveRow The relevant datbase live-record row
+        * @param array $versionRow The relevant database version-record row
+        * @return \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord
+        */
+       static public function createFromArrays($table, array $liveRow, array $versionRow) {
+               $liveRecord = \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord::createFromArray($table, $liveRow);
+               $versionRecord = \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord::createFromArray($table, $versionRow);
+               return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Domain\\Model\\CombinedRecord', $table, $liveRecord, $versionRecord);
+       }
+
+       /**
+        * Creates this object.
+        *
+        * @param string $table
+        * @param \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord $liveRecord
+        * @param \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord $versionRecord
+        */
+       public function __construct($table, \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord $liveRecord, \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord $versionRecord) {
+               $this->setTable($table);
+               $this->setLiveRecord($liveRecord);
+               $this->setVersionRecord($versionRecord);
+       }
+
+       /**
+        * Gets the name of the database table.
+        *
+        * @return string
+        */
+       public function getTable() {
+               return $this->table;
+       }
+
+       /**
+        * Sets the name of the database table.
+        *
+        * @param string $table
+        * @return void
+        */
+       public function setTable($table) {
+               $this->table = $table;
+       }
+
+       /**
+        * Gets the live-record object.
+        *
+        * @return \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord
+        */
+       public function getLiveRecord() {
+               return $this->liveRecord;
+       }
+
+       /**
+        * Sets the live-record object.
+        *
+        * @param \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord $liveRecord
+        * @return void
+        */
+       public function setLiveRecord(\TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord $liveRecord) {
+               $this->liveRecord = $liveRecord;
+       }
+
+       /**
+        * Gets the version-record object.
+        *
+        * @return \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord
+        */
+       public function getVersionRecord() {
+               return $this->versionRecord;
+       }
+
+       /**
+        * Sets the version-record object.
+        *
+        * @param \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord $versionRecord
+        * @return void
+        */
+       public function setVersionRecord(\TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord $versionRecord) {
+               $this->versionRecord = $versionRecord;
+       }
+
+       /**
+        * Gets the id of the live-record.
+        *
+        * @return integer
+        */
+       public function getLiveId() {
+               return $this->getLiveRecord()->getUid();
+       }
+
+       /**
+        * Gets the id of version-record.
+        *
+        * @return integer
+        */
+       public function getVersiondId() {
+               return $this->getVersionRecord()->getUid();
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Domain/Model/DatabaseRecord.php b/typo3/sysext/workspaces/Classes/Domain/Model/DatabaseRecord.php
new file mode 100644 (file)
index 0000000..ae7227d
--- /dev/null
@@ -0,0 +1,167 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Domain\Model;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2012-2013 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Database record class
+ *
+ * @author Oliver Hader <oliver.hader@typo3.org>
+ */
+class DatabaseRecord {
+
+       /**
+        * @var string
+        */
+       protected $table;
+
+       /**
+        * @var integer
+        */
+       protected $uid;
+
+       /**
+        * @var array
+        */
+       protected $row;
+
+       /**
+        * Creates database record object just by id of database record.
+        *
+        * @param string $table Name of the database table
+        * @param integer $uid Id of the datbase record row
+        * @return \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord
+        */
+       static public function create($table, $uid) {
+               return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Domain\\Model\\DatabaseRecord', $table, $uid);
+       }
+
+       /**
+        * Creates datbase record object by relevant database record row.
+        *
+        * @param string $table Name of the database table
+        * @param array $row The relevant database record row
+        * @return \TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord
+        */
+       static public function createFromArray($table, array $row) {
+               return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Domain\\Model\\DatabaseRecord', $table, $row['uid'], $row);
+       }
+
+       /**
+        * @param string $table Name of the database table
+        * @param integer $uid Id of the datbase record row
+        * @param array|NULL $row The relevant database record row
+        */
+       public function __construct($table, $uid, array $row = NULL) {
+               $this->setTable($table);
+               $this->setUid($uid);
+               if ($row !== NULL) {
+                       $this->setRow($row);
+               }
+       }
+
+       /**
+        * Gets the name of the database table.
+        *
+        * @return string
+        */
+       public function getTable() {
+               return $this->table;
+       }
+
+       /**
+        * Sets the name of the database table.
+        *
+        * @param string $table
+        * @return void
+        */
+       public function setTable($table) {
+               $this->table = $table;
+       }
+
+       /**
+        * Gets the id of the database record row.
+        *
+        * @return integer
+        */
+       public function getUid() {
+               return $this->uid;
+       }
+
+       /**
+        * Sets the id of the database record row.
+        *
+        * @param integer $uid
+        * @return void
+        */
+       public function setUid($uid) {
+               $this->uid = $uid;
+       }
+
+       /**
+        * Gets the database record row.
+        *
+        * @return array
+        */
+       public function getRow() {
+               $this->loadRow();
+               return $this->row;
+       }
+
+       /**
+        * Sets the database record row.
+        *
+        * @param array $row
+        * @return void
+        */
+       public function setRow(array $row) {
+               $this->row = $row;
+       }
+
+       /**
+        * Gets the record identifier (table:id).
+        *
+        * @return string
+        */
+       public function getIdentifier() {
+               return implode(':', array($this->getTable(), $this->getUid()));
+       }
+
+       /**
+        * Loads the database record row (if not available yet).
+        *
+        * @return void
+        */
+       protected function loadRow() {
+               if ($this->row === NULL) {
+                       $this->row = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($this->getTable(), $this->getUid());
+               }
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/AbstractHandler.php b/typo3/sysext/workspaces/Classes/ExtDirect/AbstractHandler.php
new file mode 100644 (file)
index 0000000..4e9c122
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+namespace TYPO3\CMS\Workspaces\ExtDirect;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Abstract ExtDirect handler
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+abstract class AbstractHandler {
+
+       /**
+        * Gets the current workspace ID.
+        *
+        * @return integer The current workspace ID
+        */
+       protected function getCurrentWorkspace() {
+               return $this->getWorkspaceService()->getCurrentWorkspace();
+       }
+
+       /**
+        * Gets an error response to be shown in the grid component.
+        *
+        * @param string $errorLabel Name of the label in the locallang.xml file
+        * @param integer $errorCode The error code to be used
+        * @param boolean $successFlagValue Value of the success flag to be delivered back (might be FALSE in most cases)
+        * @return array
+        */
+       protected function getErrorResponse($errorLabel, $errorCode = 0, $successFlagValue = FALSE) {
+               $localLangFile = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xml';
+               $response = array(
+                       'error' => array(
+                               'code' => $errorCode,
+                               'message' => $GLOBALS['LANG']->sL($localLangFile . ':' . $errorLabel)
+                       ),
+                       'success' => $successFlagValue
+               );
+               return $response;
+       }
+
+       /**
+        * Gets an instance of the workspaces service.
+        *
+        * @return \TYPO3\CMS\Workspaces\Service\WorkspaceService
+        */
+       protected function getWorkspaceService() {
+               return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+       }
+
+       /**
+        * Validates whether the submitted language parameter can be
+        * interpreted as integer value.
+        *
+        * @param stdClass $parameters
+        * @return integer|NULL
+        */
+       protected function validateLanguageParameter(\stdClass $parameters) {
+               $language = NULL;
+               if (isset($parameters->language) && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($parameters->language)) {
+                       $language = $parameters->language;
+               }
+               return $language;
+       }
+
+       /**
+        * Gets affected elements on publishing/swapping actions.
+        * Affected elements have a dependency, e.g. translation overlay
+        * and the default origin record - thus, the default record would be
+        * affected if the translation overlay shall be published.
+        *
+        * @param stdClass $parameters
+        * @return array
+        */
+       protected function getAffectedElements(\stdClass $parameters) {
+               $affectedElements = array();
+               if ($parameters->type === 'selection') {
+                       foreach ((array) $parameters->selection as $element) {
+                               $affectedElements[] = \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord::create($element->table, $element->liveId, $element->versionId);
+                       }
+               } elseif ($parameters->type === 'all') {
+                       $versions = $this->getWorkspaceService()->selectVersionsInWorkspace($this->getCurrentWorkspace(), 0, -99, -1, 0, 'tables_select', $this->validateLanguageParameter($parameters));
+                       foreach ($versions as $table => $tableElements) {
+                               foreach ($tableElements as $element) {
+                                       $affectedElement = \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord::create($table, $element['t3ver_oid'], $element['uid']);
+                                       $affectedElement->getVersionRecord()->setRow($element);
+                                       $affectedElements[] = $affectedElement;
+                               }
+                       }
+               }
+               return $affectedElements;
+       }
+
+       /**
+        * Creates a new instance of the integrity service for the
+        * given set of affected elements.
+        *
+        * @param \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord[] $affectedElements
+        * @return \TYPO3\CMS\Workspaces\Service\IntegrityService
+        * @see getAffectedElements
+        */
+       protected function createIntegrityService(array $affectedElements) {
+               /** @var $integrityService \TYPO3\CMS\Workspaces\Service\IntegrityService */
+               $integrityService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\IntegrityService');
+               $integrityService->setAffectedElements($affectedElements);
+               return $integrityService;
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php b/typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php
new file mode 100644 (file)
index 0000000..49dc496
--- /dev/null
@@ -0,0 +1,701 @@
+<?php
+namespace TYPO3\CMS\Workspaces\ExtDirect;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * ExtDirect action handler
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class ActionHandler extends \TYPO3\CMS\Workspaces\ExtDirect\AbstractHandler {
+
+       /**
+        * @var \TYPO3\CMS\Workspaces\Service\StagesService
+        */
+       protected $stageService;
+
+       /**
+        * Creates this object.
+        */
+       public function __construct() {
+               $this->stageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\StagesService');
+       }
+
+       /**
+        * Generates a workspace preview link.
+        *
+        * @param integer $uid The ID of the record to be linked
+        * @return string the full domain including the protocol http:// or https://, but without the trailing '/'
+        */
+       public function generateWorkspacePreviewLink($uid) {
+               return $this->getWorkspaceService()->generateWorkspacePreviewLink($uid);
+       }
+
+       /**
+        * Swaps a single record.
+        *
+        * @param string $table
+        * @param integer $t3ver_oid
+        * @param integer $orig_uid
+        * @return void
+        * @todo What about reporting errors back to the ExtJS interface? /olly/
+        */
+       public function swapSingleRecord($table, $t3ver_oid, $orig_uid) {
+               $cmd[$table][$t3ver_oid]['version'] = array(
+                       'action' => 'swap',
+                       'swapWith' => $orig_uid,
+                       'swapIntoWS' => 1
+               );
+               $this->processTcaCmd($cmd);
+       }
+
+       /**
+        * Deletes a single record.
+        *
+        * @param string $table
+        * @param integer $uid
+        * @return void
+        * @todo What about reporting errors back to the ExtJS interface? /olly/
+        */
+       public function deleteSingleRecord($table, $uid) {
+               $cmd[$table][$uid]['version'] = array(
+                       'action' => 'clearWSID'
+               );
+               $this->processTcaCmd($cmd);
+       }
+
+       /**
+        * Generates a view link for a page.
+        *
+        * @param string $table
+        * @param string $uid
+        * @return string
+        */
+       public function viewSingleRecord($table, $uid) {
+               return \TYPO3\CMS\Workspaces\Service\WorkspaceService::viewSingleRecord($table, $uid);
+       }
+
+       /**
+        * Saves the selected columns to be shown to the preferences of the current backend user.
+        *
+        * @param object $model
+        * @return void
+        */
+       public function saveColumnModel($model) {
+               $data = array();
+               foreach ($model as $column) {
+                       $data[$column->column] = array(
+                               'position' => $column->position,
+                               'hidden' => $column->hidden
+                       );
+               }
+               $GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'] = $data;
+               $GLOBALS['BE_USER']->writeUC();
+       }
+
+       public function loadColumnModel() {
+               if (is_array($GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'])) {
+                       return $GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'];
+               } else {
+                       return array();
+               }
+       }
+
+       /**
+        * Saves the selected language.
+        *
+        * @param integer|string $language
+        * @return void
+        */
+       public function saveLanguageSelection($language) {
+               if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($language) === FALSE && $language !== 'all') {
+                       $language = 'all';
+               }
+               $GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['language'] = $language;
+               $GLOBALS['BE_USER']->writeUC();
+       }
+
+       /**
+        * Gets the dialog window to be displayed before a record can be sent to the next stage.
+        *
+        * @param integer $uid
+        * @param string $table
+        * @param integer $t3ver_oid
+        * @return array
+        */
+       public function sendToNextStageWindow($uid, $table, $t3ver_oid) {
+               $elementRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $uid);
+               if (is_array($elementRecord)) {
+                       $stageId = $elementRecord['t3ver_stage'];
+                       if ($this->getStageService()->isValid($stageId)) {
+                               $nextStage = $this->getStageService()->getNextStage($stageId);
+                               $result = $this->getSentToStageWindow($nextStage['uid']);
+                               $result['affects'] = array(
+                                       'table' => $table,
+                                       'nextStage' => $nextStage['uid'],
+                                       't3ver_oid' => $t3ver_oid,
+                                       'uid' => $uid
+                               );
+                       } else {
+                               $result = $this->getErrorResponse('error.stageId.invalid', 1291111644);
+                       }
+               } else {
+                       $result = $this->getErrorResponse('error.sendToNextStage.noRecordFound', 1287264776);
+               }
+               return $result;
+       }
+
+       /**
+        * Gets the dialog window to be displayed before a record can be sent to the previous stage.
+        *
+        * @param integer $uid
+        * @param string $table
+        * @return array
+        */
+       public function sendToPrevStageWindow($uid, $table) {
+               $elementRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $uid);
+               if (is_array($elementRecord)) {
+                       $stageId = $elementRecord['t3ver_stage'];
+                       if ($this->getStageService()->isValid($stageId)) {
+                               if ($stageId !== \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_EDIT_ID) {
+                                       $prevStage = $this->getStageService()->getPrevStage($stageId);
+                                       $result = $this->getSentToStageWindow($prevStage['uid']);
+                                       $result['affects'] = array(
+                                               'table' => $table,
+                                               'uid' => $uid,
+                                               'nextStage' => $prevStage['uid']
+                                       );
+                               } else {
+                                       // element is already in edit stage, there is no prev stage - return an error message
+                                       $result = $this->getErrorResponse('error.sendToPrevStage.noPreviousStage', 1287264746);
+                               }
+                       } else {
+                               $result = $this->getErrorResponse('error.stageId.invalid', 1291111644);
+                       }
+               } else {
+                       $result = $this->getErrorResponse('error.sendToNextStage.noRecordFound', 1287264765);
+               }
+               return $result;
+       }
+
+       /**
+        * Gets the dialog window to be displayed before a record can be sent to a specific stage.
+        *
+        * @param integer $nextStageId
+        * @return array
+        */
+       public function sendToSpecificStageWindow($nextStageId) {
+               $result = $this->getSentToStageWindow($nextStageId);
+               $result['affects'] = array(
+                       'nextStage' => $nextStageId
+               );
+               return $result;
+       }
+
+       /**
+        * Gets a merged variant of recipient defined by uid and custom ones.
+        *
+        * @param array list of recipients
+        * @param string given user string of additional recipients
+        * @param integer stage id
+        * @return array
+        */
+       public function getRecipientList(array $uidOfRecipients, $additionalRecipients, $stageId) {
+               $finalRecipients = array();
+               if (!$this->getStageService()->isValid($stageId)) {
+                       throw new \InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
+               } else {
+                       $stageId = (int) $stageId;
+               }
+               $recipients = array();
+               foreach ($uidOfRecipients as $userUid) {
+                       $beUserRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('be_users', intval($userUid));
+                       if (is_array($beUserRecord) && $beUserRecord['email'] !== '') {
+                               $uc = $beUserRecord['uc'] ? unserialize($beUserRecord['uc']) : array();
+                               $recipients[$beUserRecord['email']] = array(
+                                       'email' => $beUserRecord['email'],
+                                       'lang' => isset($uc['lang']) ? $uc['lang'] : $beUserRecord['lang']
+                               );
+                       }
+               }
+               // the notification mode can be configured in the workspace stage record
+               $notification_mode = $this->getStageService()->getNotificationMode($stageId);
+               if (intval($notification_mode) === \TYPO3\CMS\Workspaces\Service\StagesService::MODE_NOTIFY_ALL || intval($notification_mode) === \TYPO3\CMS\Workspaces\Service\StagesService::MODE_NOTIFY_ALL_STRICT) {
+                       // get the default recipients from the stage configuration
+                       // the default recipients needs to be added in some cases of the notification_mode
+                       $default_recipients = $this->getStageService()->getResponsibleBeUser($stageId, TRUE);
+                       foreach ($default_recipients as $default_recipient_uid => $default_recipient_record) {
+                               if (!isset($recipients[$default_recipient_record['email']])) {
+                                       $uc = $default_recipient_record['uc'] ? unserialize($default_recipient_record['uc']) : array();
+                                       $recipients[$default_recipient_record['email']] = array(
+                                               'email' => $default_recipient_record['email'],
+                                               'lang' => isset($uc['lang']) ? $uc['lang'] : $default_recipient_record['lang']
+                                       );
+                               }
+                       }
+               }
+               if ($additionalRecipients !== '') {
+                       $emails = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(LF, $additionalRecipients, TRUE);
+                       $additionalRecipients = array();
+                       foreach ($emails as $email) {
+                               $additionalRecipients[$email] = array('email' => $email);
+                       }
+               } else {
+                       $additionalRecipients = array();
+               }
+               // We merge $recipients on top of $additionalRecipients because $recipients
+               // possibly is more complete with a user language. Furthermore, the list of
+               // recipients is automatically unique since we indexed $additionalRecipients
+               // and $recipients with the email address
+               $allRecipients = array_merge($additionalRecipients, $recipients);
+               foreach ($allRecipients as $email => $recipientInformation) {
+                       if (\TYPO3\CMS\Core\Utility\GeneralUtility::validEmail($email)) {
+                               $finalRecipients[] = $recipientInformation;
+                       }
+               }
+               return $finalRecipients;
+       }
+
+       /**
+        * Discard all items from given page id.
+        *
+        * @param integer $pageId
+        * @return array
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function discardStagesFromPage($pageId) {
+               $cmdMapArray = array();
+               /** @var $workspaceService \TYPO3\CMS\Workspaces\Service\WorkspaceService */
+               $workspaceService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+               /** @var $stageService \TYPO3\CMS\Workspaces\Service\StagesService */
+               $stageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\StagesService');
+               $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($stageService->getWorkspaceId(), ($filter = 1), ($stage = -99), $pageId, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
+               foreach ($workspaceItemsArray as $tableName => $items) {
+                       foreach ($items as $item) {
+                               $cmdMapArray[$tableName][$item['uid']]['version']['action'] = 'clearWSID';
+                       }
+               }
+               $this->processTcaCmd($cmdMapArray);
+               return array(
+                       'success' => TRUE
+               );
+       }
+
+       /**
+        * Push the given element collection to the next workspace stage.
+        *
+        * <code>
+        * $parameters->additional = your@mail.com
+        * $parameters->affects->__TABLENAME__
+        * $parameters->comments
+        * $parameters->receipients
+        * $parameters->stageId
+        * </code>
+        *
+        * @param stdClass $parameters
+        * @return array
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function sentCollectionToStage(\stdClass $parameters) {
+               $cmdMapArray = array();
+               $comment = $parameters->comments;
+               $stageId = $parameters->stageId;
+               if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($stageId) === FALSE) {
+                       throw new \InvalidArgumentException('Missing "stageId" in $parameters array.', 1319488194);
+               }
+               if (!is_object($parameters->affects) || count($parameters->affects) == 0) {
+                       throw new \InvalidArgumentException('Missing "affected items" in $parameters array.', 1319488195);
+               }
+               $recipients = $this->getRecipientList($parameters->receipients, $parameters->additional, $stageId);
+               foreach ($parameters->affects as $tableName => $items) {
+                       foreach ($items as $item) {
+                               if ($stageId == \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_EXECUTE_ID) {
+                                       $cmdMapArray[$tableName][$item->t3ver_oid]['version']['action'] = 'swap';
+                                       $cmdMapArray[$tableName][$item->t3ver_oid]['version']['swapWith'] = $item->uid;
+                                       $cmdMapArray[$tableName][$item->t3ver_oid]['version']['comment'] = $comment;
+                                       $cmdMapArray[$tableName][$item->t3ver_oid]['version']['notificationAlternativeRecipients'] = $recipients;
+                               } else {
+                                       $cmdMapArray[$tableName][$item->uid]['version']['action'] = 'setStage';
+                                       $cmdMapArray[$tableName][$item->uid]['version']['stageId'] = $stageId;
+                                       $cmdMapArray[$tableName][$item->uid]['version']['comment'] = $comment;
+                                       $cmdMapArray[$tableName][$item->uid]['version']['notificationAlternativeRecipients'] = $recipients;
+                               }
+                       }
+               }
+               $this->processTcaCmd($cmdMapArray);
+               return array(
+                       'success' => TRUE,
+                       // force refresh after publishing changes
+                       'refreshLivePanel' => $parameters->stageId == -20 ? TRUE : FALSE
+               );
+       }
+
+       /**
+        * Process TCA command map array.
+        *
+        * @param array $cmdMapArray
+        * @return void
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       protected function processTcaCmd(array $cmdMapArray) {
+               $tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
+               $tce->start(array(), $cmdMapArray);
+               $tce->process_cmdmap();
+       }
+
+       /**
+        * Gets an object with this structure:
+        *
+        * affects: object
+        * table
+        * t3ver_oid
+        * nextStage
+        * uid
+        * receipients: array with uids
+        * additional: string
+        * comments: string
+        *
+        * @param stdClass $parameters
+        * @return array
+        */
+       public function sendToNextStageExecute(\stdClass $parameters) {
+               $cmdArray = array();
+               $setStageId = $parameters->affects->nextStage;
+               $comments = $parameters->comments;
+               $table = $parameters->affects->table;
+               $uid = $parameters->affects->uid;
+               $t3ver_oid = $parameters->affects->t3ver_oid;
+               $recipients = $this->getRecipientList($parameters->receipients, $parameters->additional, $setStageId);
+               if ($setStageId == \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_EXECUTE_ID) {
+                       $cmdArray[$table][$t3ver_oid]['version']['action'] = 'swap';
+                       $cmdArray[$table][$t3ver_oid]['version']['swapWith'] = $uid;
+                       $cmdArray[$table][$t3ver_oid]['version']['comment'] = $comments;
+                       $cmdArray[$table][$t3ver_oid]['version']['notificationAlternativeRecipients'] = $recipients;
+               } else {
+                       $cmdArray[$table][$uid]['version']['action'] = 'setStage';
+                       $cmdArray[$table][$uid]['version']['stageId'] = $setStageId;
+                       $cmdArray[$table][$uid]['version']['comment'] = $comments;
+                       $cmdArray[$table][$uid]['version']['notificationAlternativeRecipients'] = $recipients;
+               }
+               $this->processTcaCmd($cmdArray);
+               $result = array(
+                       'success' => TRUE
+               );
+               return $result;
+       }
+
+       /**
+        * Gets an object with this structure:
+        *
+        * affects: object
+        * table
+        * t3ver_oid
+        * nextStage
+        * receipients: array with uids
+        * additional: string
+        * comments: string
+        *
+        * @param stdClass $parameters
+        * @return array
+        */
+       public function sendToPrevStageExecute(\stdClass $parameters) {
+               $cmdArray = array();
+               $recipients = array();
+               $setStageId = $parameters->affects->nextStage;
+               $comments = $parameters->comments;
+               $table = $parameters->affects->table;
+               $uid = $parameters->affects->uid;
+               $recipients = $this->getRecipientList($parameters->receipients, $parameters->additional, $setStageId);
+               $cmdArray[$table][$uid]['version']['action'] = 'setStage';
+               $cmdArray[$table][$uid]['version']['stageId'] = $setStageId;
+               $cmdArray[$table][$uid]['version']['comment'] = $comments;
+               $cmdArray[$table][$uid]['version']['notificationAlternativeRecipients'] = $recipients;
+               $this->processTcaCmd($cmdArray);
+               $result = array(
+                       'success' => TRUE
+               );
+               return $result;
+       }
+
+       /**
+        * Gets an object with this structure:
+        *
+        * affects: object
+        * elements: array
+        * 0: object
+        * table
+        * t3ver_oid
+        * uid
+        * 1: object
+        * table
+        * t3ver_oid
+        * uid
+        * nextStage
+        * receipients: array with uids
+        * additional: string
+        * comments: string
+        *
+        * @param stdClass $parameters
+        * @return array
+        */
+       public function sendToSpecificStageExecute(\stdClass $parameters) {
+               $cmdArray = array();
+               $setStageId = $parameters->affects->nextStage;
+               $comments = $parameters->comments;
+               $elements = $parameters->affects->elements;
+               $recipients = $this->getRecipientList($parameters->receipients, $parameters->additional, $setStageId);
+               foreach ($elements as $key => $element) {
+                       if ($setStageId == \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_EXECUTE_ID) {
+                               $cmdArray[$element->table][$element->t3ver_oid]['version']['action'] = 'swap';
+                               $cmdArray[$element->table][$element->t3ver_oid]['version']['swapWith'] = $element->uid;
+                               $cmdArray[$element->table][$element->t3ver_oid]['version']['comment'] = $comments;
+                               $cmdArray[$element->table][$element->t3ver_oid]['version']['notificationAlternativeRecipients'] = $recipients;
+                       } else {
+                               $cmdArray[$element->table][$element->uid]['version']['action'] = 'setStage';
+                               $cmdArray[$element->table][$element->uid]['version']['stageId'] = $setStageId;
+                               $cmdArray[$element->table][$element->uid]['version']['comment'] = $comments;
+                               $cmdArray[$element->table][$element->uid]['version']['notificationAlternativeRecipients'] = $recipients;
+                       }
+               }
+               $this->processTcaCmd($cmdArray);
+               $result = array(
+                       'success' => TRUE
+               );
+               return $result;
+       }
+
+       /**
+        * Gets the dialog window to be displayed before a record can be sent to a stage.
+        *
+        * @param $nextStageId
+        * @return array
+        */
+       protected function getSentToStageWindow($nextStageId) {
+               $workspaceRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('sys_workspace', $this->getStageService()->getWorkspaceId());
+               $showNotificationFields = FALSE;
+               $stageTitle = $this->getStageService()->getStageTitle($nextStageId);
+               $result = array(
+                       'title' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:actionSendToStage'),
+                       'items' => array(
+                               array(
+                                       'xtype' => 'panel',
+                                       'bodyStyle' => 'margin-bottom: 7px; border: none;',
+                                       'html' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:window.sendToNextStageWindow.itemsWillBeSentTo') . ' ' . $stageTitle
+                               )
+                       )
+               );
+               switch ($nextStageId) {
+               case \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_EXECUTE_ID:
+
+               case \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_ID:
+                       if (!empty($workspaceRec['publish_allow_notificaton_settings'])) {
+                               $showNotificationFields = TRUE;
+                       }
+                       break;
+               case \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_EDIT_ID:
+                       if (!empty($workspaceRec['edit_allow_notificaton_settings'])) {
+                               $showNotificationFields = TRUE;
+                       }
+                       break;
+               default:
+                       $allow_notificaton_settings = $this->getStageService()->getPropertyOfCurrentWorkspaceStage($nextStageId, 'allow_notificaton_settings');
+                       if (!empty($allow_notificaton_settings)) {
+                               $showNotificationFields = TRUE;
+                       }
+                       break;
+               }
+               if ($showNotificationFields == TRUE) {
+                       $result['items'][] = array(
+                               'fieldLabel' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:window.sendToNextStageWindow.sendMailTo'),
+                               'xtype' => 'checkboxgroup',
+                               'itemCls' => 'x-check-group-alt',
+                               'columns' => 1,
+                               'style' => 'max-height: 200px',
+                               'autoScroll' => TRUE,
+                               'items' => array(
+                                       $this->getReceipientsOfStage($nextStageId)
+                               )
+                       );
+                       $result['items'][] = array(
+                               'fieldLabel' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:window.sendToNextStageWindow.additionalRecipients'),
+                               'name' => 'additional',
+                               'xtype' => 'textarea',
+                               'width' => 250
+                       );
+               }
+               $result['items'][] = array(
+                       'fieldLabel' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:window.sendToNextStageWindow.comments'),
+                       'name' => 'comments',
+                       'xtype' => 'textarea',
+                       'width' => 250,
+                       'value' => $this->getDefaultCommentOfStage($nextStageId)
+               );
+               return $result;
+       }
+
+       /**
+        * Gets all assigned recipients of a particular stage.
+        *
+        * @param integer $stage
+        * @return array
+        */
+       protected function getReceipientsOfStage($stage) {
+               $result = array();
+               $recipients = $this->getStageService()->getResponsibleBeUser($stage);
+               $default_recipients = $this->getStageService()->getResponsibleBeUser($stage, TRUE);
+               foreach ($recipients as $id => $user) {
+                       if (\TYPO3\CMS\Core\Utility\GeneralUtility::validEmail($user['email'])) {
+                               $checked = FALSE;
+                               $disabled = FALSE;
+                               $name = $user['realName'] ? $user['realName'] : $user['username'];
+                               // the notification mode can be configured in the workspace stage record
+                               $notification_mode = $this->getStageService()->getNotificationMode($stage);
+                               if (intval($notification_mode) === \TYPO3\CMS\Workspaces\Service\StagesService::MODE_NOTIFY_SOMEONE) {
+                                       // all responsible users are checked per default, as in versions before
+                                       $checked = TRUE;
+                               } elseif (intval($notification_mode) === \TYPO3\CMS\Workspaces\Service\StagesService::MODE_NOTIFY_ALL) {
+                                       // the default users are checked only
+                                       if (!empty($default_recipients[$id])) {
+                                               $checked = TRUE;
+                                               $disabled = TRUE;
+                                       } else {
+                                               $checked = FALSE;
+                                       }
+                               } elseif (intval($notification_mode) === \TYPO3\CMS\Workspaces\Service\StagesService::MODE_NOTIFY_ALL_STRICT) {
+                                       // all responsible users are checked
+                                       $checked = TRUE;
+                                       $disabled = TRUE;
+                               }
+                               $result[] = array(
+                                       'boxLabel' => sprintf('%s (%s)', $name, $user['email']),
+                                       'name' => 'receipients-' . $id,
+                                       'checked' => $checked,
+                                       'disabled' => $disabled
+                               );
+                       }
+               }
+               return $result;
+       }
+
+       /**
+        * Gets the default comment of a particular stage.
+        *
+        * @param integer $stage
+        * @return string
+        */
+       protected function getDefaultCommentOfStage($stage) {
+               $result = $this->getStageService()->getPropertyOfCurrentWorkspaceStage($stage, 'default_mailcomment');
+               return $result;
+       }
+
+       /**
+        * Gets an instance of the Stage service.
+        *
+        * @return \TYPO3\CMS\Workspaces\Service\StagesService
+        */
+       protected function getStageService() {
+               if (!isset($this->stageService)) {
+                       $this->stageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\StagesService');
+               }
+               return $this->stageService;
+       }
+
+       /**
+        * Send all available workspace records to the previous stage.
+        *
+        * @param integer $id Current page id to process items to previous stage.
+        * @return array
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function sendPageToPreviousStage($id) {
+               $workspaceService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+               $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), ($filter = 1), ($stage = -99), $id, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
+               list($currentStage, $previousStage) = $this->getStageService()->getPreviousStageForElementCollection($workspaceItemsArray);
+               // get only the relevant items for processing
+               $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), ($filter = 1), $currentStage['uid'], $id, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
+               return array(
+                       'title' => 'Status message: Page send to next stage - ID: ' . $id . ' - Next stage title: ' . $previousStage['title'],
+                       'items' => $this->getSentToStageWindow($previousStage['uid']),
+                       'affects' => $workspaceItemsArray,
+                       'stageId' => $previousStage['uid']
+               );
+       }
+
+       /**
+        * @param integer $id Current Page id to select Workspace items from.
+        * @return array
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function sendPageToNextStage($id) {
+               $workspaceService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+               $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), ($filter = 1), ($stage = -99), $id, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
+               list($currentStage, $nextStage) = $this->getStageService()->getNextStageForElementCollection($workspaceItemsArray);
+               // get only the relevant items for processing
+               $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), ($filter = 1), $currentStage['uid'], $id, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
+               return array(
+                       'title' => 'Status message: Page send to next stage - ID: ' . $id . ' - Next stage title: ' . $nextStage['title'],
+                       'items' => $this->getSentToStageWindow($nextStage['uid']),
+                       'affects' => $workspaceItemsArray,
+                       'stageId' => $nextStage['uid']
+               );
+       }
+
+       /**
+        * Fetch the current label and visible state of the buttons.
+        *
+        * @param integer $id
+        * @return array Contains the visibility state and label of the stage change buttons.
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function updateStageChangeButtons($id) {
+               $stageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\StagesService');
+               $workspaceService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+               // fetch the next and previous stage
+               $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($stageService->getWorkspaceId(), ($filter = 1), ($stage = -99), $id, ($recursionLevel = 0), ($selectionType = 'tables_modify'));
+               list(, $nextStage) = $stageService->getNextStageForElementCollection($workspaceItemsArray);
+               list(, $previousStage) = $stageService->getPreviousStageForElementCollection($workspaceItemsArray);
+               $toolbarButtons = array(
+                       'feToolbarButtonNextStage' => array(
+                               'visible' => is_array($nextStage) && count($nextStage) > 0,
+                               'text' => $nextStage['title']
+                       ),
+                       'feToolbarButtonPreviousStage' => array(
+                               'visible' => is_array($previousStage) && count($previousStage),
+                               'text' => $previousStage['title']
+                       ),
+                       'feToolbarButtonDiscardStage' => array(
+                               'visible' => is_array($nextStage) && count($nextStage) > 0 || is_array($previousStage) && count($previousStage) > 0,
+                               'text' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:label_doaction_discard', TRUE)
+                       )
+               );
+               return $toolbarButtons;
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/ExtDirectServer.php b/typo3/sysext/workspaces/Classes/ExtDirect/ExtDirectServer.php
new file mode 100644 (file)
index 0000000..523661d
--- /dev/null
@@ -0,0 +1,305 @@
+<?php
+namespace TYPO3\CMS\Workspaces\ExtDirect;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * ExtDirect server
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class ExtDirectServer extends \TYPO3\CMS\Workspaces\ExtDirect\AbstractHandler {
+
+       /**
+        * @var \TYPO3\CMS\Workspaces\Service\GridDataService
+        */
+       protected $gridDataService;
+
+       /**
+        * @var \TYPO3\CMS\Workspaces\Service\StagesService
+        */
+       protected $stagesService;
+
+       /**
+        * Checks integrity of elements before peforming actions on them.
+        *
+        * @param stdClass $parameters
+        * @return array
+        */
+       public function checkIntegrity(\stdClass $parameters) {
+               $integrity = $this->createIntegrityService($this->getAffectedElements($parameters));
+               $integrity->check();
+               $response = array(
+                       'result' => $integrity->getStatusRepresentation()
+               );
+               return $response;
+       }
+
+       /**
+        * Get List of workspace changes
+        *
+        * @param object $parameter
+        * @return array $data
+        */
+       public function getWorkspaceInfos($parameter) {
+               // To avoid too much work we use -1 to indicate that every page is relevant
+               $pageId = $parameter->id > 0 ? $parameter->id : -1;
+               if (!isset($parameter->language) || !\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($parameter->language)) {
+                       $parameter->language = NULL;
+               }
+               $versions = $this->getWorkspaceService()->selectVersionsInWorkspace($this->getCurrentWorkspace(), 0, -99, $pageId, $parameter->depth, 'tables_select', $parameter->language);
+               $data = $this->getGridDataService()->generateGridListFromVersions($versions, $parameter, $this->getCurrentWorkspace());
+               return $data;
+       }
+
+       /**
+        * Gets the editing history of a record.
+        *
+        * @param stdClass $parameters
+        * @return array
+        */
+       public function getHistory($parameters) {
+               /** @var $historyService \TYPO3\CMS\Workspaces\Service\HistoryService */
+               $historyService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\HistoryService');
+               $history = $historyService->getHistory($parameters->table, $parameters->liveId);
+               return array(
+                       'data' => $history,
+                       'total' => count($history)
+               );
+       }
+
+       /**
+        * Get List of available workspace actions
+        *
+        * @param object $parameter
+        * @return array $data
+        */
+       public function getStageActions($parameter) {
+               $currentWorkspace = $this->getCurrentWorkspace();
+               $stages = array();
+               if ($currentWorkspace != \TYPO3\CMS\Workspaces\Service\WorkspaceService::SELECT_ALL_WORKSPACES) {
+                       $stages = $this->getStagesService()->getStagesForWSUser();
+               }
+               $data = array(
+                       'total' => count($stages),
+                       'data' => $stages
+               );
+               return $data;
+       }
+
+       /**
+        * Fetch futher information to current selected worspace record.
+        *
+        * @param object $parameter
+        * @return array $data
+        */
+       public function getRowDetails($parameter) {
+               $diffReturnArray = array();
+               $liveReturnArray = array();
+               /** @var $t3lib_diff \TYPO3\CMS\Core\Utility\DiffUtility */
+               $t3lib_diff = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\DiffUtility');
+               /** @var $parseObj \TYPO3\CMS\Core\Html\RteHtmlParser */
+               $parseObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Html\\RteHtmlParser');
+               $liveRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($parameter->table, $parameter->t3ver_oid);
+               $versionRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($parameter->table, $parameter->uid);
+               $icon_Live = \TYPO3\CMS\Backend\Utility\IconUtility::mapRecordTypeToSpriteIconClass($parameter->table, $liveRecord);
+               $icon_Workspace = \TYPO3\CMS\Backend\Utility\IconUtility::mapRecordTypeToSpriteIconClass($parameter->table, $versionRecord);
+               $stagePosition = $this->getStagesService()->getPositionOfCurrentStage($parameter->stage);
+               $fieldsOfRecords = array_keys($liveRecord);
+               if ($GLOBALS['TCA'][$parameter->table]) {
+                       if ($GLOBALS['TCA'][$parameter->table]['interface']['showRecordFieldList']) {
+                               $fieldsOfRecords = $GLOBALS['TCA'][$parameter->table]['interface']['showRecordFieldList'];
+                               $fieldsOfRecords = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $fieldsOfRecords, 1);
+                       }
+               }
+               foreach ($fieldsOfRecords as $fieldName) {
+                       // check for exclude fields
+                       if ($GLOBALS['BE_USER']->isAdmin() || $GLOBALS['TCA'][$parameter->table]['columns'][$fieldName]['exclude'] == 0 || \TYPO3\CMS\Core\Utility\GeneralUtility::inList($GLOBALS['BE_USER']->groupData['non_exclude_fields'], $parameter->table . ':' . $fieldName)) {
+                               // call diff class only if there is a difference
+                               if (strcmp($liveRecord[$fieldName], $versionRecord[$fieldName]) !== 0) {
+                                       // Select the human readable values before diff
+                                       $liveRecord[$fieldName] = \TYPO3\CMS\Backend\Utility\BackendUtility::getProcessedValue(
+                                               $parameter->table,
+                                               $fieldName,
+                                               $liveRecord[$fieldName],
+                                               0,
+                                               1,
+                                               FALSE,
+                                               $liveRecord['uid']
+                                       );
+                                       $versionRecord[$fieldName] = \TYPO3\CMS\Backend\Utility\BackendUtility::getProcessedValue(
+                                               $parameter->table,
+                                               $fieldName,
+                                               $versionRecord[$fieldName],
+                                               0,
+                                               1,
+                                               FALSE,
+                                               $versionRecord['uid']
+                                       );
+                                       // Get the field's label. If not available, use the field name
+                                       $fieldTitle = $GLOBALS['LANG']->sL(\TYPO3\CMS\Backend\Utility\BackendUtility::getItemLabel($parameter->table, $fieldName));
+                                       if (empty($fieldTitle)) {
+                                               $fieldTitle = $fieldName;
+                                       }
+                                       if ($GLOBALS['TCA'][$parameter->table]['columns'][$fieldName]['config']['type'] == 'group' && $GLOBALS['TCA'][$parameter->table]['columns'][$fieldName]['config']['internal_type'] == 'file') {
+                                               $versionThumb = \TYPO3\CMS\Backend\Utility\BackendUtility::thumbCode($versionRecord, $parameter->table, $fieldName, '');
+                                               $liveThumb = \TYPO3\CMS\Backend\Utility\BackendUtility::thumbCode($liveRecord, $parameter->table, $fieldName, '');
+                                               $diffReturnArray[] = array(
+                                                       'field' => $fieldName,
+                                                       'label' => $fieldTitle,
+                                                       'content' => $versionThumb
+                                               );
+                                               $liveReturnArray[] = array(
+                                                       'field' => $fieldName,
+                                                       'label' => $fieldTitle,
+                                                       'content' => $liveThumb
+                                               );
+                                       } else {
+                                               $diffReturnArray[] = array(
+                                                       'field' => $fieldName,
+                                                       'label' => $fieldTitle,
+                                                       'content' => $t3lib_diff->makeDiffDisplay($liveRecord[$fieldName], $versionRecord[$fieldName])
+                                               );
+                                               $liveReturnArray[] = array(
+                                                       'field' => $fieldName,
+                                                       'label' => $fieldTitle,
+                                                       'content' => $parseObj->TS_images_rte($liveRecord[$fieldName])
+                                               );
+                                       }
+                               }
+                       }
+               }
+               // Hook for modifying the difference and live arrays
+               // (this may be used by custom or dynamically-defined fields)
+               if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['modifyDifferenceArray'])) {
+                       foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['modifyDifferenceArray'] as $className) {
+                               $hookObject =& \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($className);
+                               $hookObject->modifyDifferenceArray($parameter, $diffReturnArray, $liveReturnArray, $t3lib_diff);
+                       }
+               }
+               $commentsForRecord = $this->getCommentsForRecord($parameter->uid, $parameter->table);
+               return array(
+                       'total' => 1,
+                       'data' => array(
+                               array(
+                                       'diff' => $diffReturnArray,
+                                       'live_record' => $liveReturnArray,
+                                       'path_Live' => $parameter->path_Live,
+                                       'label_Stage' => $parameter->label_Stage,
+                                       'stage_position' => $stagePosition['position'],
+                                       'stage_count' => $stagePosition['count'],
+                                       'comments' => $commentsForRecord,
+                                       'icon_Live' => $icon_Live,
+                                       'icon_Workspace' => $icon_Workspace
+                               )
+                       )
+               );
+       }
+
+       /**
+        * Gets an array with all sys_log entries and their comments for the given record uid and table
+        *
+        * @param integer $uid uid of changed element to search for in log
+        * @param string $table Name of the record's table
+        * @return array
+        */
+       public function getCommentsForRecord($uid, $table) {
+               $sysLogReturnArray = array();
+               $sysLogRows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('log_data,tstamp,userid', 'sys_log', 'action=6 and details_nr=30
+                               AND tablename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($table, 'sys_log') . '
+                               AND recuid=' . intval($uid), '', 'tstamp DESC');
+               foreach ($sysLogRows as $sysLogRow) {
+                       $sysLogEntry = array();
+                       $data = unserialize($sysLogRow['log_data']);
+                       $beUserRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('be_users', $sysLogRow['userid']);
+                       $sysLogEntry['stage_title'] = $this->getStagesService()->getStageTitle($data['stage']);
+                       $sysLogEntry['user_uid'] = $sysLogRow['userid'];
+                       $sysLogEntry['user_username'] = is_array($beUserRecord) ? $beUserRecord['username'] : '';
+                       $sysLogEntry['tstamp'] = \TYPO3\CMS\Backend\Utility\BackendUtility::datetime($sysLogRow['tstamp']);
+                       $sysLogEntry['user_comment'] = $data['comment'];
+                       $sysLogReturnArray[] = $sysLogEntry;
+               }
+               return $sysLogReturnArray;
+       }
+
+       /**
+        * Gets all available system languages.
+        *
+        * @return array
+        */
+       public function getSystemLanguages() {
+               $systemLanguages = array(
+                       array(
+                               'uid' => 'all',
+                               'title' => \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('language.allLanguages', 'workspaces'),
+                               'cls' => \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconClasses('empty-empty')
+                       )
+               );
+               foreach ($this->getGridDataService()->getSystemLanguages() as $id => $systemLanguage) {
+                       if ($id < 0) {
+                               continue;
+                       }
+                       $systemLanguages[] = array(
+                               'uid' => $id,
+                               'title' => htmlspecialchars($systemLanguage['title']),
+                               'cls' => \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconClasses($systemLanguage['flagIcon'])
+                       );
+               }
+               $result = array(
+                       'total' => count($systemLanguages),
+                       'data' => $systemLanguages
+               );
+               return $result;
+       }
+
+       /**
+        * Gets the Grid Data Service.
+        *
+        * @return \TYPO3\CMS\Workspaces\Service\GridDataService
+        */
+       protected function getGridDataService() {
+               if (!isset($this->gridDataService)) {
+                       $this->gridDataService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\GridDataService');
+               }
+               return $this->gridDataService;
+       }
+
+       /**
+        * Gets the Stages Service.
+        *
+        * @return \TYPO3\CMS\Workspaces\Service\StagesService
+        */
+       protected function getStagesService() {
+               if (!isset($this->stagesService)) {
+                       $this->stagesService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\StagesService');
+               }
+               return $this->stagesService;
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/MassActionHandler.php b/typo3/sysext/workspaces/Classes/ExtDirect/MassActionHandler.php
new file mode 100644 (file)
index 0000000..0d2b403
--- /dev/null
@@ -0,0 +1,232 @@
+<?php
+namespace TYPO3\CMS\Workspaces\ExtDirect;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 1999-2013 Kasper Skårhøj (kasperYYYY@typo3.com)
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Class encapsulates all actions which are triggered for all elements within the current workspace.
+ *
+ * @author Kasper Skårhøj (kasperYYYY@typo3.com)
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class MassActionHandler extends \TYPO3\CMS\Workspaces\ExtDirect\AbstractHandler {
+
+       const MAX_RECORDS_TO_PROCESS = 30;
+       /**
+        * Path to the locallang file
+        *
+        * @var string
+        */
+       private $pathToLocallang = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xml';
+
+       /**
+        * Get list of available mass workspace actions.
+        *
+        * @param object $parameter
+        * @return array $data
+        */
+       public function getMassStageActions($parameter) {
+               $actions = array();
+               $currentWorkspace = $this->getCurrentWorkspace();
+               $massActionsEnabled = $GLOBALS['BE_USER']->getTSConfigVal('options.workspaces.enableMassActions') !== '0';
+               // in case we're working within "All Workspaces" we can't provide Mass Actions
+               if ($currentWorkspace != \TYPO3\CMS\Workspaces\Service\WorkspaceService::SELECT_ALL_WORKSPACES && $massActionsEnabled) {
+                       $publishAccess = $GLOBALS['BE_USER']->workspacePublishAccess($currentWorkspace);
+                       if ($publishAccess && !($GLOBALS['BE_USER']->workspaceRec['publish_access'] & 1)) {
+                               $actions[] = array('action' => 'publish', 'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':label_doaction_publish'));
+                               if ($GLOBALS['BE_USER']->workspaceSwapAccess()) {
+                                       $actions[] = array('action' => 'swap', 'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':label_doaction_swap'));
+                               }
+                       }
+                       if ($currentWorkspace !== \TYPO3\CMS\Workspaces\Service\WorkspaceService::LIVE_WORKSPACE_ID) {
+                               $actions[] = array('action' => 'discard', 'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':label_doaction_discard'));
+                       }
+               }
+               $result = array(
+                       'total' => count($actions),
+                       'data' => $actions
+               );
+               return $result;
+       }
+
+       /**
+        * Publishes the current workspace.
+        *
+        * @param stdclass $parameters
+        * @return array
+        */
+       public function publishWorkspace(\stdclass $parameters) {
+               $result = array(
+                       'init' => FALSE,
+                       'total' => 0,
+                       'processed' => 0,
+                       'error' => FALSE
+               );
+               try {
+                       if ($parameters->init) {
+                               $language = $this->validateLanguageParameter($parameters);
+                               $cnt = $this->initPublishData($this->getCurrentWorkspace(), $parameters->swap, $language);
+                               $result['total'] = $cnt;
+                       } else {
+                               $result['processed'] = $this->processData($this->getCurrentWorkspace());
+                               $result['total'] = $GLOBALS['BE_USER']->getSessionData('workspaceMassAction_total');
+                       }
+               } catch (\Exception $e) {
+                       $result['error'] = $e->getMessage();
+               }
+               return $result;
+       }
+
+       /**
+        * Flushes the current workspace.
+        *
+        * @param stdclass $parameters
+        * @return array
+        */
+       public function flushWorkspace(\stdclass $parameters) {
+               $result = array(
+                       'init' => FALSE,
+                       'total' => 0,
+                       'processed' => 0,
+                       'error' => FALSE
+               );
+               try {
+                       if ($parameters->init) {
+                               $language = $this->validateLanguageParameter($parameters);
+                               $cnt = $this->initFlushData($this->getCurrentWorkspace(), $language);
+                               $result['total'] = $cnt;
+                       } else {
+                               $result['processed'] = $this->processData($this->getCurrentWorkspace());
+                               $result['total'] = $GLOBALS['BE_USER']->getSessionData('workspaceMassAction_total');
+                       }
+               } catch (\Exception $e) {
+                       $result['error'] = $e->getMessage();
+               }
+               return $result;
+       }
+
+       /**
+        * Initializes the command map to be used for publishing.
+        *
+        * @param integer $workspace
+        * @param boolean $swap
+        * @param integer $language
+        * @return integer
+        */
+       protected function initPublishData($workspace, $swap, $language = NULL) {
+               // workspace might be -98 a.k.a "All Workspaces but that's save here
+               $publishData = $this->getWorkspaceService()->getCmdArrayForPublishWS($workspace, $swap, 0, $language);
+               $recordCount = 0;
+               foreach ($publishData as $table => $recs) {
+                       $recordCount += count($recs);
+               }
+               if ($recordCount > 0) {
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction', $publishData);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_total', $recordCount);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_processed', 0);
+               }
+               return $recordCount;
+       }
+
+       /**
+        * Initializes the command map to be used for flushing.
+        *
+        * @param integer $workspace
+        * @param integer $language
+        * @return integer
+        */
+       protected function initFlushData($workspace, $language = NULL) {
+               // workspace might be -98 a.k.a "All Workspaces but that's save here
+               $flushData = $this->getWorkspaceService()->getCmdArrayForFlushWS($workspace, TRUE, 0, $language);
+               $recordCount = 0;
+               foreach ($flushData as $table => $recs) {
+                       $recordCount += count($recs);
+               }
+               if ($recordCount > 0) {
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction', $flushData);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_total', $recordCount);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_processed', 0);
+               }
+               return $recordCount;
+       }
+
+       /**
+        * Processes the data.
+        *
+        * @param integer $workspace
+        * @return integer
+        */
+       protected function processData($workspace) {
+               $processData = $GLOBALS['BE_USER']->getSessionData('workspaceMassAction');
+               $recordsProcessed = $GLOBALS['BE_USER']->getSessionData('workspaceMassAction_processed');
+               $limitedCmd = array();
+               $numRecs = 0;
+               foreach ($processData as $table => $recs) {
+                       foreach ($recs as $key => $value) {
+                               $numRecs++;
+                               $limitedCmd[$table][$key] = $value;
+                               if ($numRecs == self::MAX_RECORDS_TO_PROCESS) {
+                                       break;
+                               }
+                       }
+                       if ($numRecs == self::MAX_RECORDS_TO_PROCESS) {
+                               break;
+                       }
+               }
+               if ($numRecs == 0) {
+                       // All done
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction', NULL);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_total', 0);
+               } else {
+                       /** @var $tce \TYPO3\CMS\Core\DataHandling\DataHandler */
+                       $tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
+                       $tce->stripslashes_values = 0;
+                       // Execute the commands:
+                       $tce->start(array(), $limitedCmd);
+                       $tce->process_cmdmap();
+                       $errors = $tce->errorLog;
+                       if (count($errors) > 0) {
+                               throw new \Exception(implode(', ', $errors));
+                       } else {
+                               // Unset processed records
+                               foreach ($limitedCmd as $table => $recs) {
+                                       foreach ($recs as $key => $value) {
+                                               $recordsProcessed++;
+                                               unset($processData[$table][$key]);
+                                       }
+                               }
+                               $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction', $processData);
+                               $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_processed', $recordsProcessed);
+                       }
+               }
+               return $recordsProcessed;
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/PagetreeCollectionsProcessor.php b/typo3/sysext/workspaces/Classes/ExtDirect/PagetreeCollectionsProcessor.php
new file mode 100644 (file)
index 0000000..523c252
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+namespace TYPO3\CMS\Workspaces\ExtDirect;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2011-2013 Tolleiv Nietsch <typo3@tolleiv.de>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Interface for classes which perform pre or post processing
+ *
+ * @author Tolleiv Nietsch <typo3@tolleiv.de>
+ */
+class PagetreeCollectionsProcessor implements \TYPO3\CMS\Backend\Tree\Pagetree\CollectionProcessorInterface {
+
+       /**
+        * @abstract
+        * @param \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode $node
+        * @param int $mountPoint
+        * @param int $level
+        * @param \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection $nodeCollection
+        * @return void
+        */
+       public function postProcessGetNodes($node, $mountPoint, $level, $nodeCollection) {
+               foreach ($nodeCollection as $node) {
+                       /** @var $node \TYPO3\CMS\Backend\Tree\TreeNode */
+                       $this->highlightVersionizedElements($node);
+               }
+       }
+
+       /**
+        * @abstract
+        * @param \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode $node
+        * @param string $searchFilter
+        * @param int $mountPoint
+        * @param \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection $nodeCollection
+        * @return void
+        */
+       public function postProcessFilteredNodes($node, $searchFilter, $mountPoint, $nodeCollection) {
+               foreach ($nodeCollection as $node) {
+                       /** @var $node \TYPO3\CMS\Backend\Tree\TreeNode */
+                       $this->highlightVersionizedElements($node);
+               }
+       }
+
+       /**
+        * @abstract
+        * @param string $searchFilter
+        * @param \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNodeCollection $nodeCollection
+        * @return void
+        */
+       public function postProcessGetTreeMounts($searchFilter, $nodeCollection) {
+               foreach ($nodeCollection as $node) {
+                       /** @var $node \TYPO3\CMS\Backend\Tree\TreeNode */
+                       $this->highlightVersionizedElements($node);
+               }
+       }
+
+       /**
+        * Sets the CSS Class on all pages which have versioned records
+        * in the current workspace
+        *
+        * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
+        * @return void
+        */
+       protected function highlightVersionizedElements(\TYPO3\CMS\Backend\Tree\TreeNode $node) {
+               if (!$node->getCls() && count(\TYPO3\CMS\Backend\Utility\BackendUtility::countVersionsOfRecordsOnPage($GLOBALS['BE_USER']->workspace, $node->getId(), TRUE))) {
+                       $node->setCls('ver-versions');
+               }
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/ToolbarMenu.php b/typo3/sysext/workspaces/Classes/ExtDirect/ToolbarMenu.php
new file mode 100644 (file)
index 0000000..abba483
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+namespace TYPO3\CMS\Workspaces\ExtDirect;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * ExtDirect toolbar menu
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class ToolbarMenu {
+
+       /**
+        * @param $parameter
+        * @return array
+        */
+       public function toggleWorkspacePreviewMode($parameter) {
+               $newState = $GLOBALS['BE_USER']->user['workspace_preview'] ? '0' : '1';
+               $GLOBALS['BE_USER']->setWorkspacePreview($newState);
+               return array('newWorkspacePreviewState' => $newState);
+       }
+
+       /**
+        * @param $parameter
+        * @return array
+        */
+       public function setWorkspace($parameter) {
+               $workspaceId = intval($parameter->workSpaceId);
+               $GLOBALS['BE_USER']->setWorkspace($workspaceId);
+               return array(
+                       'title' => \TYPO3\CMS\Workspaces\Service\WorkspaceService::getWorkspaceTitle($workspaceId),
+                       'id' => $workspaceId
+               );
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/WorkspaceSelectorToolbarItem.php b/typo3/sysext/workspaces/Classes/ExtDirect/WorkspaceSelectorToolbarItem.php
new file mode 100644 (file)
index 0000000..a8c3bad
--- /dev/null
@@ -0,0 +1,151 @@
+<?php
+namespace TYPO3\CMS\Workspaces\ExtDirect;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2007-2013 Ingo Renner <ingo@typo3.org>
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
+       require_once \TYPO3\CMS\Core\Extension\ExtensionManager::extPath('backend') . 'Classes/Toolbar/ToolbarItemHookInterface.php';
+}
+
+/**
+ * class to render the workspace selector
+ *
+ * @author     Ingo Renner <ingo@typo3.org>
+ */
+class WorkspaceSelectorToolbarItem implements \TYPO3\CMS\Backend\Toolbar\ToolbarItemHookInterface {
+
+       protected $changeWorkspace;
+
+       protected $changeWorkspacePreview;
+
+       /**
+        * reference back to the backend object
+        *
+        * @var \TYPO3\CMS\Backend\Controller\BackendController
+        */
+       protected $backendReference;
+
+       protected $checkAccess = NULL;
+
+       /**
+        * constructor
+        *
+        * @param \TYPO3\CMS\Backend\Controller\BackendController TYPO3 backend object reference
+        */
+       public function __construct(\TYPO3\CMS\Backend\Controller\BackendController &$backendReference = NULL) {
+               $this->backendReference = $backendReference;
+               $this->changeWorkspace = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('changeWorkspace');
+               $this->changeWorkspacePreview = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('changeWorkspacePreview');
+               $pageRenderer = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Page\\PageRenderer');
+               $this->backendReference->addJavaScript('TYPO3.Workspaces = { workspaceTitle : \'' . addslashes(\TYPO3\CMS\Workspaces\Service\WorkspaceService::getWorkspaceTitle($GLOBALS['BE_USER']->workspace)) . '\'};
+');
+       }
+
+       /**
+        * checks whether the user has access to this toolbar item
+        *
+        * @see                 typo3/alt_shortcut.php
+        * @return boolean  TRUE if user has access, FALSE if not
+        */
+       public function checkAccess() {
+               if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
+                       if ($this->checkAccess == NULL) {
+                               $availableWorkspaces = \TYPO3\CMS\Workspaces\Service\WorkspaceService::getAvailableWorkspaces();
+                               if (count($availableWorkspaces) > 0) {
+                                       $this->checkAccess = TRUE;
+                               } else {
+                                       $this->checkAccess = FALSE;
+                               }
+                       }
+                       return $this->checkAccess;
+               }
+               return FALSE;
+       }
+
+       /**
+        * Creates the selector for workspaces
+        *
+        * @return      string          workspace selector as HTML select
+        */
+       public function render() {
+               $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:toolbarItems.workspace', TRUE);
+               $this->addJavascriptToBackend();
+               $availableWorkspaces = \TYPO3\CMS\Workspaces\Service\WorkspaceService::getAvailableWorkspaces();
+               $workspaceMenu = array();
+               $stateCheckedIcon = \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIcon('status-status-checked');
+               $stateUncheckedIcon = \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIcon('empty-empty', array(
+                       'title' => $GLOBALS['LANG']->getLL('bookmark_inactive')
+               ));
+               $workspaceMenu[] = '<a href="#" class="toolbar-item">' . \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIcon('apps-toolbar-menu-workspace', array('title' => $title)) . '</a>';
+               $workspaceMenu[] = '<ul class="toolbar-item-menu" style="display: none;">';
+               if (count($availableWorkspaces)) {
+                       foreach ($availableWorkspaces as $workspaceId => $label) {
+                               $selected = '';
+                               $icon = $stateUncheckedIcon;
+                               if ((int) $GLOBALS['BE_USER']->workspace === $workspaceId) {
+                                       $selected = ' class="selected"';
+                                       $icon = $stateCheckedIcon;
+                               }
+                               $workspaceMenu[] = '<li' . $selected . '>' . '<a href="backend.php?changeWorkspace=' . intval($workspaceId) . '" id="ws-' . intval($workspaceId) . '" class="ws">' . $icon . ' ' . htmlspecialchars($label) . '</a></li>';
+                       }
+               } else {
+                       $workspaceMenu[] = '<li>' . $stateUncheckedIcon . ' ' . $GLOBALS['LANG']->getLL('bookmark_noWSfound', TRUE) . '</li>';
+               }
+               if ($GLOBALS['BE_USER']->check('modules', 'web_WorkspacesWorkspaces')) {
+                       // go to workspace module link
+                       $workspaceMenu[] = '<li class="divider">' . $stateUncheckedIcon . ' ' . '<a href="javascript:top.goToModule(\'web_WorkspacesWorkspaces\');" target="content" id="goToWsModule">' . ' ' . $GLOBALS['LANG']->getLL('bookmark_workspace', TRUE) . '</a></li>';
+               }
+               $workspaceMenu[] = '</ul>';
+               return implode(LF, $workspaceMenu);
+       }
+
+       /**
+        * adds the necessary JavaScript to the backend
+        *
+        * @return      void
+        */
+       protected function addJavascriptToBackend() {
+               $this->backendReference->addJavascriptFile(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('workspaces') . 'Resources/Public/JavaScript/workspacemenu.js');
+       }
+
+       /**
+        * returns additional attributes for the list item in the toolbar
+        *
+        * @return      string          list item HTML attibutes
+        */
+       public function getAdditionalAttributes() {
+               return ' id="workspace-selector-menu"';
+       }
+
+}
+
+
+if (!(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX)) {
+       $GLOBALS['TYPO3backend']->addToolbarItem('workSpaceSelector', 'TYPO3\\CMS\\Workspaces\\ExtDirect\\WorkspaceSelectorToolbarItem');
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Hook/BackendUtilityHook.php b/typo3/sysext/workspaces/Classes/Hook/BackendUtilityHook.php
new file mode 100644 (file)
index 0000000..b832454
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Hook;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Befunc service
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class BackendUtilityHook implements \TYPO3\CMS\Core\SingletonInterface {
+
+       /**
+        * Gets a singleton instance of this object.
+        *
+        * @return \TYPO3\CMS\Workspaces\Hook\BackendUtilityHook
+        */
+       static public function getInstance() {
+               return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Hook\\BackendUtilityHook');
+       }
+
+       /**
+        * Hooks into the \TYPO3\CMS\Backend\Utility\BackendUtility::viewOnClick and redirects to the workspace preview
+        * only if we're in a workspace and if the frontend-preview is disabled.
+        *
+        * @param $pageUid
+        * @param $backPath
+        * @param $rootLine
+        * @param $anchorSection
+        * @param $viewScript
+        * @param $additionalGetVars
+        * @param $switchFocus
+        * @return void
+        */
+       public function preProcess(&$pageUid, $backPath, $rootLine, $anchorSection, &$viewScript, $additionalGetVars, $switchFocus) {
+               if ($GLOBALS['BE_USER']->workspace !== 0) {
+                       $viewScript = $this->getWorkspaceService()->generateWorkspaceSplittedPreviewLink($pageUid);
+               }
+       }
+
+       /**
+        * Gets an instance of the workspaces service.
+        *
+        * @return \TYPO3\CMS\Workspaces\Service\WorkspaceService
+        */
+       protected function getWorkspaceService() {
+               return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+       }
+
+       /**
+        * Use that hook to show a info message in case someone starts editing
+        * a staged element
+        *
+        * @param $params
+        * @param $form
+        * @return boolean
+        */
+       public function makeEditForm_accessCheck($params, &$form) {
+               if ($GLOBALS['BE_USER']->workspace !== 0 && $GLOBALS['TCA'][$params['table']]['ctrl']['versioningWS']) {
+                       $record = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordWSOL($params['table'], $params['uid']);
+                       if (abs($record['t3ver_stage']) > \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_EDIT_ID) {
+                               $stages = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\StagesService');
+                               $stageName = $stages->getStageTitle($record['t3ver_stage']);
+                               $editingName = $stages->getStageTitle(\TYPO3\CMS\Workspaces\Service\StagesService::STAGE_EDIT_ID);
+                               $message = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:info.elementAlreadyModified');
+                               $flashMessage = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessage', sprintf($message, $stageName, $editingName), '', \TYPO3\CMS\Core\Messaging\FlashMessage::INFO, TRUE);
+                               /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
+                               $flashMessageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessageService');
+                               /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
+                               $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+                               $defaultFlashMessageQueue->enqueue($flashMessage);
+                       }
+               }
+               return $params['hasAccess'];
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php b/typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php
new file mode 100644 (file)
index 0000000..19b095b
--- /dev/null
@@ -0,0 +1,150 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Hook;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Tcemain service
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class DataHandlerHook {
+
+       /**
+        * In case a sys_workspace_stage record is deleted we do a hard reset
+        * for all existing records in that stage to avoid that any of these end up
+        * as orphan records.
+        *
+        * @param string $command
+        * @param string $table
+        * @param string $id
+        * @param string $value
+        * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemain
+        * @return void
+        */
+       public function processCmdmap_postProcess($command, $table, $id, $value, \TYPO3\CMS\Core\DataHandling\DataHandler $tcemain) {
+               if ($command === 'delete') {
+                       if ($table === \TYPO3\CMS\Workspaces\Service\StagesService::TABLE_STAGE) {
+                               $this->resetStageOfElements($id);
+                       } elseif ($table === \TYPO3\CMS\Workspaces\Service\WorkspaceService::TABLE_WORKSPACE) {
+                               $this->flushWorkspaceElements($id);
+                       }
+               }
+       }
+
+       /**
+        * hook that is called AFTER all commands of the commandmap was
+        * executed
+        *
+        * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj reference to the main tcemain object
+        * @return      void
+        */
+       public function processCmdmap_afterFinish(\TYPO3\CMS\Core\DataHandling\DataHandler $tcemainObj) {
+               $this->flushWorkspaceCacheEntriesByWorkspaceId($tcemainObj->BE_USER->workspace);
+       }
+
+       /**
+        * In case a sys_workspace_stage record is deleted we do a hard reset
+        * for all existing records in that stage to avoid that any of these end up
+        * as orphan records.
+        *
+        * @param integer $stageId Elements with this stage are resetted
+        * @return void
+        */
+       protected function resetStageOfElements($stageId) {
+               $fields = array('t3ver_stage' => \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_EDIT_ID);
+               foreach ($this->getTcaTables() as $tcaTable) {
+                       if (\TYPO3\CMS\Backend\Utility\BackendUtility::isTableWorkspaceEnabled($tcaTable)) {
+                               $where = 't3ver_stage = ' . intval($stageId);
+                               $where .= ' AND t3ver_wsid > 0 AND pid=-1';
+                               $where .= \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($tcaTable);
+                               $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tcaTable, $where, $fields);
+                       }
+               }
+       }
+
+       /**
+        * Flushes elements of a particular workspace to avoid orphan records.
+        *
+        * @param integer $workspaceId The workspace to be flushed
+        * @return void
+        */
+       protected function flushWorkspaceElements($workspaceId) {
+               $command = array();
+               foreach ($this->getTcaTables() as $tcaTable) {
+                       if (\TYPO3\CMS\Backend\Utility\BackendUtility::isTableWorkspaceEnabled($tcaTable)) {
+                               $where = '1=1';
+                               $where .= \TYPO3\CMS\Backend\Utility\BackendUtility::getWorkspaceWhereClause($tcaTable, $workspaceId);
+                               $where .= \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($tcaTable);
+                               $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $tcaTable, $where, '', '', '', 'uid');
+                               if (is_array($records)) {
+                                       foreach (array_keys($records) as $recordId) {
+                                               $command[$tcaTable][$recordId]['version']['action'] = 'flush';
+                                       }
+                               }
+                       }
+               }
+               if (count($command)) {
+                       $tceMain = $this->getTceMain();
+                       $tceMain->start(array(), $command);
+                       $tceMain->process_cmdmap();
+               }
+       }
+
+       /**
+        * Gets all defined TCA tables.
+        *
+        * @return array
+        */
+       protected function getTcaTables() {
+               return array_keys($GLOBALS['TCA']);
+       }
+
+       /**
+        * @return \TYPO3\CMS\Core\DataHandling\DataHandler
+        */
+       protected function getTceMain() {
+               $tceMain = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
+               $tceMain->stripslashes_values = 0;
+               return $tceMain;
+       }
+
+       /**
+        * Flushes the workspace cache for current workspace and for the virtual "all workspaces" too.
+        *
+        * @param integer $workspaceId The workspace to be flushed in cache
+        * @return void
+        */
+       protected function flushWorkspaceCacheEntriesByWorkspaceId($workspaceId) {
+               $workspacesCache = $GLOBALS['typo3CacheManager']->getCache('workspaces_cache');
+               $workspacesCache->flushByTag($workspaceId);
+               $workspacesCache->flushByTag(\TYPO3\CMS\Workspaces\Service\WorkspaceService::SELECT_ALL_WORKSPACES);
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Hook/TypoScriptFrontendControllerHook.php b/typo3/sysext/workspaces/Classes/Hook/TypoScriptFrontendControllerHook.php
new file mode 100644 (file)
index 0000000..25a5e31
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Hook;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Frontend hooks
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class TypoScriptFrontendControllerHook {
+
+       /**
+        * @param array $params
+        * @param \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $pObj
+        * @return mixed
+        */
+       public function hook_eofe($params, $pObj) {
+               if ($pObj->fePreview != 2) {
+                       return;
+               }
+               $previewParts = $GLOBALS['TSFE']->cObj->cObjGetSingle('FLUIDTEMPLATE', array(
+                       'file' => 'EXT:workspaces/Resources/Private/Templates/Preview/Preview.html',
+                       'variables.' => array(
+                               'backendDomain' => 'TEXT',
+                               'backendDomain.' => array('value' => $GLOBALS['BE_USER']->getSessionData('workspaces.backend_domain'))
+                       )
+               ));
+               $GLOBALS['TSFE']->content = str_ireplace('</body>', $previewParts . '</body>', $GLOBALS['TSFE']->content);
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/AutoPublishService.php b/typo3/sysext/workspaces/Classes/Service/AutoPublishService.php
new file mode 100644 (file)
index 0000000..c4f0664
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Service;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Automatic publishing of workspaces.
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class AutoPublishService {
+
+       /**
+        * This method is called by the Scheduler task that triggers
+        * the autopublication process
+        * It searches for workspaces whose publication date is in the past
+        * and publishes them
+        *
+        * @return      void
+        */
+       public function autoPublishWorkspaces() {
+               global $TYPO3_CONF_VARS;
+               // Temporarily set admin rights
+               // FIXME: once workspaces are cleaned up a better solution should be implemented
+               $currentAdminStatus = $GLOBALS['BE_USER']->user['admin'];
+               $GLOBALS['BE_USER']->user['admin'] = 1;
+               // Select all workspaces that needs to be published / unpublished:
+               $workspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,swap_modes,publish_time,unpublish_time', 'sys_workspace', 'pid=0
+                               AND
+                               ((publish_time!=0 AND publish_time<=' . intval($GLOBALS['EXEC_TIME']) . ')
+                               OR (publish_time=0 AND unpublish_time!=0 AND unpublish_time<=' . intval($GLOBALS['EXEC_TIME']) . '))' . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause('sys_workspace'));
+               $workspaceService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
+               foreach ($workspaces as $rec) {
+                       // First, clear start/end time so it doesn't get select once again:
+                       $fieldArray = $rec['publish_time'] != 0 ? array('publish_time' => 0) : array('unpublish_time' => 0);
+                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery('sys_workspace', 'uid=' . intval($rec['uid']), $fieldArray);
+                       // Get CMD array:
+                       $cmd = $workspaceService->getCmdArrayForPublishWS($rec['uid'], $rec['swap_modes'] == 1);
+                       // $rec['swap_modes']==1 means that auto-publishing will swap versions, not just publish and empty the workspace.
+                       // Execute CMD array:
+                       $tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
+                       $tce->stripslashes_values = 0;
+                       $tce->start(array(), $cmd);
+                       $tce->process_cmdmap();
+               }
+               // Restore admin status
+               $GLOBALS['BE_USER']->user['admin'] = $currentAdminStatus;
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/GridDataService.php b/typo3/sysext/workspaces/Classes/Service/GridDataService.php
new file mode 100644 (file)
index 0000000..8eb7a26
--- /dev/null
@@ -0,0 +1,564 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Service;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Grid data service
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class GridDataService {
+
+       const SIGNAL_GenerateDataArray_BeforeCaching = 'generateDataArray.beforeCaching';
+       const SIGNAL_GenerateDataArray_PostProcesss = 'generateDataArray.postProcess';
+       const SIGNAL_GetDataArray_PostProcesss = 'getDataArray.postProcess';
+       const SIGNAL_SortDataArray_PostProcesss = 'sortDataArray.postProcess';
+       /**
+        * Id of the current active workspace.
+        *
+        * @var integer
+        */
+       protected $currentWorkspace = NULL;
+
+       /**
+        * Version record information (filtered, sorted and limited)
+        *
+        * @var array
+        */
+       protected $dataArray = array();
+
+       /**
+        * Name of the field used for sorting.
+        *
+        * @var string
+        */
+       protected $sort = '';
+
+       /**
+        * Direction used for sorting (ASC, DESC).
+        *
+        * @var string
+        */
+       protected $sortDir = '';
+
+       /**
+        * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
+        */
+       protected $workspacesCache = NULL;
+
+       /**
+        * @var array
+        */
+       protected $systemLanguages;
+
+       /**
+        * @var \TYPO3\CMS\Workspaces\Service\IntegrityService
+        */
+       protected $integrityService;
+
+       /**
+        * Generates grid list array from given versions.
+        *
+        * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
+        * @param object $parameter Parameters as submitted by JavaScript component
+        * @param integer $currentWorkspace The current workspace
+        * @return array Version record information (filtered, sorted and limited)
+        * @throws \InvalidArgumentException
+        */
+       public function generateGridListFromVersions($versions, $parameter, $currentWorkspace) {
+               // Read the given parameters from grid. If the parameter is not set use default values.
+               $filterTxt = isset($parameter->filterTxt) ? $parameter->filterTxt : '';
+               $start = isset($parameter->start) ? intval($parameter->start) : 0;
+               $limit = isset($parameter->limit) ? intval($parameter->limit) : 30;
+               $this->sort = isset($parameter->sort) ? $parameter->sort : 't3ver_oid';
+               $this->sortDir = isset($parameter->dir) ? $parameter->dir : 'ASC';
+               if (is_int($currentWorkspace)) {
+                       $this->currentWorkspace = $currentWorkspace;
+               } else {
+                       throw new \InvalidArgumentException('No such workspace defined');
+               }
+               $data = array();
+               $data['data'] = array();
+               $this->generateDataArray($versions, $filterTxt);
+               $data['total'] = count($this->dataArray);
+               $data['data'] = $this->getDataArray($start, $limit);
+               return $data;
+       }
+
+       /**
+        * Generates grid list array from given versions.
+        *
+        * @param array $versions All available version records
+        * @param string $filterTxt Text to be used to filter record result
+        * @return void
+        */
+       protected function generateDataArray(array $versions, $filterTxt) {
+               $workspaceAccess = $GLOBALS['BE_USER']->checkWorkspace($GLOBALS['BE_USER']->workspace);
+               $swapStage = $workspaceAccess['publish_access'] & 1 ? \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_ID : 0;
+               $swapAccess = $GLOBALS['BE_USER']->workspacePublishAccess($GLOBALS['BE_USER']->workspace) && $GLOBALS['BE_USER']->workspaceSwapAccess();
+               $this->initializeWorkspacesCachingFramework();
+               // check for dataArray in cache
+               if ($this->getDataArrayFromCache($versions, $filterTxt) === FALSE) {
+                       /** @var $stagesObj \TYPO3\CMS\Workspaces\Service\StagesService */
+                       $stagesObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\StagesService');
+                       foreach ($versions as $table => $records) {
+                               $versionArray = array('table' => $table);
+                               $hiddenField = $this->getTcaEnableColumnsFieldName($table, 'disabled');
+                               $isRecordTypeAllowedToModify = $GLOBALS['BE_USER']->check('tables_modify', $table);
+
+                               foreach ($records as $record) {
+                                       $origRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $record['t3ver_oid']);
+                                       $versionRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $record['uid']);
+                                       $combinedRecord = \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord::createFromArrays($table, $origRecord, $versionRecord);
+                                       $this->getIntegrityService()->checkElement($combinedRecord);
+
+                                       if ($hiddenField !== NULL) {
+                                               $recordState = $this->workspaceState($versionRecord['t3ver_state'], $origRecord[$hiddenField], $versionRecord[$hiddenField]);
+                                       } else {
+                                               $recordState = $this->workspaceState($versionRecord['t3ver_state']);
+                                       }
+
+                                       $isDeletedPage = $table == 'pages' && $recordState == 'deleted';
+                                       $viewUrl = \TYPO3\CMS\Workspaces\Service\WorkspaceService::viewSingleRecord($table, $record['uid'], $origRecord, $versionRecord);
+                                       $versionArray['id'] = $table . ':' . $record['uid'];
+                                       $versionArray['uid'] = $record['uid'];
+                                       $versionArray['workspace'] = $versionRecord['t3ver_id'];
+                                       $versionArray['label_Workspace'] = htmlspecialchars(\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle($table, $versionRecord));
+                                       $versionArray['label_Live'] = htmlspecialchars(\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle($table, $origRecord));
+                                       $versionArray['label_Stage'] = htmlspecialchars($stagesObj->getStageTitle($versionRecord['t3ver_stage']));
+                                       $tempStage = $stagesObj->getNextStage($versionRecord['t3ver_stage']);
+                                       $versionArray['label_nextStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
+                                       $tempStage = $stagesObj->getPrevStage($versionRecord['t3ver_stage']);
+                                       $versionArray['label_prevStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
+                                       $versionArray['path_Live'] = htmlspecialchars(\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordPath($record['livepid'], '', 999));
+                                       $versionArray['path_Workspace'] = htmlspecialchars(\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordPath($record['wspid'], '', 999));
+                                       $versionArray['workspace_Title'] = htmlspecialchars(\TYPO3\CMS\Workspaces\Service\WorkspaceService::getWorkspaceTitle($versionRecord['t3ver_wsid']));
+                                       $versionArray['workspace_Tstamp'] = $versionRecord['tstamp'];
+                                       $versionArray['workspace_Formated_Tstamp'] = \TYPO3\CMS\Backend\Utility\BackendUtility::datetime($versionRecord['tstamp']);
+                                       $versionArray['t3ver_oid'] = $record['t3ver_oid'];
+                                       $versionArray['livepid'] = $record['livepid'];
+                                       $versionArray['stage'] = $versionRecord['t3ver_stage'];
+                                       $versionArray['icon_Live'] = \TYPO3\CMS\Backend\Utility\IconUtility::mapRecordTypeToSpriteIconClass($table, $origRecord);
+                                       $versionArray['icon_Workspace'] = \TYPO3\CMS\Backend\Utility\IconUtility::mapRecordTypeToSpriteIconClass($table, $versionRecord);
+                                       $languageValue = $this->getLanguageValue($table, $versionRecord);
+                                       $versionArray['languageValue'] = $languageValue;
+                                       $versionArray['language'] = array(
+                                               'cls' => \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconClasses($this->getSystemLanguageValue($languageValue, 'flagIcon')),
+                                               'title' => htmlspecialchars($this->getSystemLanguageValue($languageValue, 'title'))
+                                       );
+                                       $versionArray['allowedAction_nextStage'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($versionRecord['t3ver_stage']);
+                                       $versionArray['allowedAction_prevStage'] = $isRecordTypeAllowedToModify && $stagesObj->isPrevStageAllowedForUser($versionRecord['t3ver_stage']);
+                                       if ($swapAccess && $swapStage != 0 && $versionRecord['t3ver_stage'] == $swapStage) {
+                                               $versionArray['allowedAction_swap'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($swapStage);
+                                       } elseif ($swapAccess && $swapStage == 0) {
+                                               $versionArray['allowedAction_swap'] = $isRecordTypeAllowedToModify;
+                                       } else {
+                                               $versionArray['allowedAction_swap'] = FALSE;
+                                       }
+                                       $versionArray['allowedAction_delete'] = $isRecordTypeAllowedToModify;
+                                       // preview and editing of a deleted page won't work ;)
+                                       $versionArray['allowedAction_view'] = !$isDeletedPage && $viewUrl;
+                                       $versionArray['allowedAction_edit'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
+                                       $versionArray['allowedAction_editVersionedPage'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
+                                       $versionArray['state_Workspace'] = $recordState;
+                                       if ($filterTxt == '' || $this->isFilterTextInVisibleColumns($filterTxt, $versionArray)) {
+                                               $this->dataArray[] = $versionArray;
+                                       }
+                               }
+                       }
+                       // Suggested slot method:
+                       // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array &$dataArray, array $versions)
+                       $this->emitSignal(self::SIGNAL_GenerateDataArray_BeforeCaching, $this->dataArray, $versions);
+                       // Enrich elements after everything has been processed:
+                       foreach ($this->dataArray as &$element) {
+                               $identifier = $element['table'] . ':' . $element['t3ver_oid'];
+                               $element['integrity'] = array(
+                                       'status' => $this->getIntegrityService()->getStatusRepresentation($identifier),
+                                       'messages' => htmlspecialchars($this->getIntegrityService()->getIssueMessages($identifier, TRUE))
+                               );
+                       }
+                       $this->setDataArrayIntoCache($versions, $filterTxt);
+               }
+               // Suggested slot method:
+               // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array &$dataArray, array $versions)
+               $this->emitSignal(self::SIGNAL_GenerateDataArray_PostProcesss, $this->dataArray, $versions);
+               $this->sortDataArray();
+       }
+
+       /**
+        * Gets the data array by considering the page to be shown in the grid view.
+        *
+        * @param integer $start
+        * @param integer $limit
+        * @return array
+        */
+       protected function getDataArray($start, $limit) {
+               $dataArrayPart = array();
+               $end = $start + $limit < count($this->dataArray) ? $start + $limit : count($this->dataArray);
+               for ($i = $start; $i < $end; $i++) {
+                       $dataArrayPart[] = $this->dataArray[$i];
+               }
+               // Suggested slot method:
+               // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array &$dataArray, $start, $limit)
+               $this->emitSignal(self::SIGNAL_GetDataArray_PostProcesss, $this->dataArray, $start, $limit);
+               return $dataArrayPart;
+       }
+
+       /**
+        * Initializes the workspace cache
+        *
+        * @return void
+        */
+       protected function initializeWorkspacesCachingFramework() {
+               $this->workspacesCache = $GLOBALS['typo3CacheManager']->getCache('workspaces_cache');
+       }
+
+       /**
+        * Puts the generated dataArray into the workspace cache.
+        *
+        * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
+        * @param string $filterTxt The given filter text from the grid.
+        */
+       protected function setDataArrayIntoCache(array $versions, $filterTxt) {
+               $hash = $this->calculateHash($versions, $filterTxt);
+               $this->workspacesCache->set($hash, $this->dataArray, array($this->currentWorkspace, 'user_' . $GLOBALS['BE_USER']->user['uid']));
+       }
+
+       /**
+        * Checks if a cache entry is given for given versions and filter text and tries to load the data array from cache.
+        *
+        * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
+        * @param string $filterTxt The given filter text from the grid.
+        * @return boolean TRUE if cache entry was successfully fetched from cache and content put to $this->dataArray
+        */
+       protected function getDataArrayFromCache(array $versions, $filterTxt) {
+               $cacheEntry = FALSE;
+               $hash = $this->calculateHash($versions, $filterTxt);
+               $content = $this->workspacesCache->get($hash);
+               if ($content !== FALSE) {
+                       $this->dataArray = $content;
+                       $cacheEntry = TRUE;
+               }
+               return $cacheEntry;
+       }
+
+       /**
+        * Calculates the hash value of the used workspace, the user id, the versions array, the filter text, the sorting attribute, the workspace selected in grid and the sorting direction.
+        *
+        * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
+        * @param string $filterTxt The given filter text from the grid.
+        * @return string
+        */
+       protected function calculateHash(array $versions, $filterTxt) {
+               $hashArray = array(
+                       $GLOBALS['BE_USER']->workspace,
+                       $GLOBALS['BE_USER']->user['uid'],
+                       $versions,
+                       $filterTxt,
+                       $this->sort,
+                       $this->sortDir,
+                       $this->currentWorkspace
+               );
+               $hash = md5(serialize($hashArray));
+               return $hash;
+       }
+
+       /**
+        * Performs sorting on the data array accordant to the
+        * selected column in the grid view to be used for sorting.
+        *
+        * @return void
+        */
+       protected function sortDataArray() {
+               if (is_array($this->dataArray)) {
+                       switch ($this->sort) {
+                       case 'uid':
+
+                       case 'change':
+
+                       case 'workspace_Tstamp':
+
+                       case 't3ver_oid':
+
+                       case 'liveid':
+
+                       case 'livepid':
+
+                       case 'languageValue':
+                               usort($this->dataArray, array($this, 'intSort'));
+                               break;
+                       case 'label_Workspace':
+
+                       case 'label_Live':
+
+                       case 'label_Stage':
+
+                       case 'workspace_Title':
+
+                       case 'path_Live':
+                               // case 'path_Workspace': This is the first sorting attribute
+                               usort($this->dataArray, array($this, 'stringSort'));
+                               break;
+                       }
+               } else {
+                       \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog('Try to sort "' . $this->sort . '" in "TYPO3\\CMS\\Workspaces\\Service\\GridDataService::sortDataArray" but $this->dataArray is empty! This might be the Bug #26422 which could not reproduced yet.', 3);
+               }
+               // Suggested slot method:
+               // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array &$dataArray, $sortColumn, $sortDirection)
+               $this->emitSignal(self::SIGNAL_SortDataArray_PostProcesss, $this->dataArray, $this->sort, $this->sortDir);
+       }
+
+       /**
+        * Implements individual sorting for columns based on integer comparison.
+        *
+        * @param array $a First value
+        * @param array $b Second value
+        * @return integer
+        */
+       protected function intSort(array $a, array $b) {
+               // First sort by using the page-path in current workspace
+               $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
+               if ($path_cmp < 0) {
+                       return $path_cmp;
+               } elseif ($path_cmp == 0) {
+                       if ($a[$this->sort] == $b[$this->sort]) {
+                               return 0;
+                       }
+                       if ($this->sortDir == 'ASC') {
+                               return $a[$this->sort] < $b[$this->sort] ? -1 : 1;
+                       } elseif ($this->sortDir == 'DESC') {
+                               return $a[$this->sort] > $b[$this->sort] ? -1 : 1;
+                       }
+               } elseif ($path_cmp > 0) {
+                       return $path_cmp;
+               }
+               return 0;
+       }
+
+       /**
+        * Implements individual sorting for columns based on string comparison.
+        *
+        * @param string $a First value
+        * @param string $b Second value
+        * @return integer
+        */
+       protected function stringSort($a, $b) {
+               $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
+               if ($path_cmp < 0) {
+                       return $path_cmp;
+               } elseif ($path_cmp == 0) {
+                       if ($a[$this->sort] == $b[$this->sort]) {
+                               return 0;
+                       }
+                       if ($this->sortDir == 'ASC') {
+                               return strcasecmp($a[$this->sort], $b[$this->sort]);
+                       } elseif ($this->sortDir == 'DESC') {
+                               return strcasecmp($a[$this->sort], $b[$this->sort]) * -1;
+                       }
+               } elseif ($path_cmp > 0) {
+                       return $path_cmp;
+               }
+               return 0;
+       }
+
+       /**
+        * Determines whether the text used to filter the results is part of
+        * a column that is visible in the grid view.
+        *
+        * @param string $filterText
+        * @param array $versionArray
+        * @return boolean
+        */
+       protected function isFilterTextInVisibleColumns($filterText, array $versionArray) {
+               if (is_array($GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'])) {
+                       foreach ($GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'] as $column => $value) {
+                               if (isset($value['hidden']) && isset($column) && isset($versionArray[$column])) {
+                                       if ($value['hidden'] == 0) {
+                                               switch ($column) {
+                                               case 'workspace_Tstamp':
+                                                       if (stripos($versionArray['workspace_Formated_Tstamp'], $filterText) !== FALSE) {
+                                                               return TRUE;
+                                                       }
+                                                       break;
+                                               case 'change':
+                                                       if (stripos(strval($versionArray[$column]), str_replace('%', '', $filterText)) !== FALSE) {
+                                                               return TRUE;
+                                                       }
+                                                       break;
+                                               default:
+                                                       if (stripos(strval($versionArray[$column]), $filterText) !== FALSE) {
+                                                               return TRUE;
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+               return FALSE;
+       }
+
+       /**
+        * Gets the state of a given state value.
+        *
+        * @param integer $stateId stateId of offline record
+        * @param boolean $hiddenOnline hidden status of online record
+        * @param boolean $hiddenOffline hidden status of offline record
+        * @return string
+        */
+       protected function workspaceState($stateId, $hiddenOnline = FALSE, $hiddenOffline = FALSE) {
+               switch ($stateId) {
+               case -1:
+                       $state = 'new';
+                       break;
+               case 1:
+
+               case 2:
+                       $state = 'deleted';
+                       break;
+               case 4:
+                       $state = 'moved';
+                       break;
+               default:
+                       $state = 'modified';
+               }
+               if ($hiddenOnline == 0 && $hiddenOffline == 1) {
+                       $state = 'hidden';
+               } elseif ($hiddenOnline == 1 && $hiddenOffline == 0) {
+                       $state = 'unhidden';
+               }
+               return $state;
+       }
+
+       /**
+        * Gets the field name of the enable-columns as defined in $TCA.
+        *
+        * @param string $table Name of the table
+        * @param string $type Type to be fetches (e.g. 'disabled', 'starttime', 'endtime', 'fe_group)
+        * @return string|NULL The accordant field name or NULL if not defined
+        */
+       protected function getTcaEnableColumnsFieldName($table, $type) {
+               $fieldName = NULL;
+
+               if (!(empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type]))) {
+                       $fieldName = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type];
+               }
+
+               return $fieldName;
+       }
+
+       /**
+        * Gets the used language value (sys_language.uid) of
+        * a given database record.
+        *
+        * @param string $table Name of the table
+        * @param array $record Database record
+        * @return integer
+        */
+       protected function getLanguageValue($table, array $record) {
+               $languageValue = 0;
+               if (\TYPO3\CMS\Backend\Utility\BackendUtility::isTableLocalizable($table)) {
+                       $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
+                       if (!empty($record[$languageField])) {
+                               $languageValue = $record[$languageField];
+                       }
+               }
+               return $languageValue;
+       }
+
+       /**
+        * Gets a named value of the available sys_language elements.
+        *
+        * @param integer $id sys_language uid
+        * @param string $key Name of the value to be fetched (e.g. title)
+        * @return string|NULL
+        * @see getSystemLanguages
+        */
+       protected function getSystemLanguageValue($id, $key) {
+               $value = NULL;
+               $systemLanguages = $this->getSystemLanguages();
+               if (!empty($systemLanguages[$id][$key])) {
+                       $value = $systemLanguages[$id][$key];
+               }
+               return $value;
+       }
+
+       /**
+        * Gets all available system languages.
+        *
+        * @return array
+        */
+       public function getSystemLanguages() {
+               if (!isset($this->systemLanguages)) {
+                       /** @var $translateTools \TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider */
+                       $translateTools = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Configuration\\TranslationConfigurationProvider');
+                       $this->systemLanguages = $translateTools->getSystemLanguages();
+               }
+               return $this->systemLanguages;
+       }
+
+       /**
+        * Gets an instance of the integrity service.
+        *
+        * @return \TYPO3\CMS\Workspaces\Service\IntegrityService
+        */
+       protected function getIntegrityService() {
+               if (!isset($this->integrityService)) {
+                       $this->integrityService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\IntegrityService');
+               }
+               return $this->integrityService;
+       }
+
+       /**
+        * Emits a signal to be handled by any registered slots.
+        *
+        * @param string $signalName Name of the signal
+        * @return void
+        */
+       protected function emitSignal($signalName) {
+               // Arguments are always ($this, [method argument], [method argument], ...)
+               $signalArguments = array_merge(array($this), array_slice(func_get_args(), 1));
+               $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Workspaces\\Service\\GridDataService', $signalName, $signalArguments);
+       }
+
+       /**
+        * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
+        */
+       protected function getSignalSlotDispatcher() {
+               return $this->getObjectManager()->get('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
+       }
+
+       /**
+        * @return \TYPO3\CMS\Extbase\Object\ObjectManagerException
+        */
+       protected function getObjectManager() {
+               return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManagerException');
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/HistoryService.php b/typo3/sysext/workspaces/Classes/Service/HistoryService.php
new file mode 100644 (file)
index 0000000..9cd3ed6
--- /dev/null
@@ -0,0 +1,169 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Service;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2012-2013 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Service for history
+ *
+ * @author Oliver Hader <oliver.hader@typo3.org>
+ */
+class HistoryService implements \TYPO3\CMS\Core\SingletonInterface {
+
+       /**
+        * @var array
+        */
+       protected $backendUserNames;
+
+       /**
+        * @var array
+        */
+       protected $historyObjects = array();
+
+       /**
+        * @var \TYPO3\CMS\Core\Utility\DiffUtility
+        */
+       protected $differencesObject;
+
+       /**
+        * Creates this object.
+        */
+       public function __construct() {
+               require_once PATH_typo3 . 'class.show_rechis.inc';
+               $this->backendUserNames = \TYPO3\CMS\Backend\Utility\BackendUtility::getUserNames();
+       }
+
+       /**
+        * Gets the editing history of a record.
+        *
+        * @param string $table Name of the table
+        * @param integer $id Uid of the record
+        * @return array Record history entries
+        */
+       public function getHistory($table, $id) {
+               $history = array();
+               $i = 0;
+               foreach ($this->getHistoryObject($table, $id)->changeLog as $entry) {
+                       if ($i++ > 20) {
+                               break;
+                       }
+                       $history[] = $this->getHistoryEntry($entry);
+               }
+               return $history;
+       }
+
+       /**
+        * Gets the human readable representation of one
+        * record history entry.
+        *
+        * @param array $entry Record history entry
+        * @return array
+        * @see getHistory
+        */
+       protected function getHistoryEntry(array $entry) {
+               if (!empty($entry['action'])) {
+                       $differences = $entry['action'];
+               } else {
+                       $differences = implode('<br/>', $this->getDifferences($entry));
+               }
+               return array(
+                       'datetime' => htmlspecialchars(\TYPO3\CMS\Backend\Utility\BackendUtility::datetime($entry['tstamp'])),
+                       'user' => htmlspecialchars($this->getUserName($entry['user'])),
+                       'differences' => $differences
+               );
+       }
+
+       /**
+        * Gets the differences between two record versions out
+        * of one record history entry.
+        *
+        * @param array $entry Record history entry
+        * @return array
+        */
+       protected function getDifferences(array $entry) {
+               $differences = array();
+               $tableName = $entry['tablename'];
+               if (is_array($entry['newRecord'])) {
+                       $fields = array_keys($entry['newRecord']);
+                       foreach ($fields as $field) {
+                               if (!empty($GLOBALS['TCA'][$tableName]['columns'][$field]['config']['type']) && $GLOBALS['TCA'][$tableName]['columns'][$field]['config']['type'] !== 'passthrough') {
+                                       // Create diff-result:
+                                       $fieldDifferences = $this->getDifferencesObject()->makeDiffDisplay(\TYPO3\CMS\Backend\Utility\BackendUtility::getProcessedValue($tableName, $field, $entry['oldRecord'][$field], 0, TRUE), \TYPO3\CMS\Backend\Utility\BackendUtility::getProcessedValue($tableName, $field, $entry['newRecord'][$field], 0, TRUE));
+                                       $differences[] = nl2br($fieldDifferences);
+                               }
+                       }
+               }
+               return $differences;
+       }
+
+       /**
+        * Gets the username of a backend user.
+        *
+        * @param string $user
+        * @return string
+        */
+       protected function getUserName($user) {
+               $userName = 'unknown';
+               if (!empty($this->backendUserNames[$user]['username'])) {
+                       $userName = $this->backendUserNames[$user]['username'];
+               }
+               return $userName;
+       }
+
+       /**
+        * Gets an instance of the record history service.
+        *
+        * @param string $table Name of the table
+        * @param integer $id Uid of the record
+        * @return \TYPO3\CMS\Backend\History\RecordHistory
+        */
+       protected function getHistoryObject($table, $id) {
+               if (!isset($this->historyObjects[$table][$id])) {
+                       /** @var $historyObject \TYPO3\CMS\Backend\History\RecordHistory */
+                       $historyObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\History\\RecordHistory');
+                       $historyObject->element = $table . ':' . $id;
+                       $historyObject->createChangeLog();
+                       $this->historyObjects[$table][$id] = $historyObject;
+               }
+               return $this->historyObjects[$table][$id];
+       }
+
+       /**
+        * Gets an instance of the record differences utility.
+        *
+        * @return \TYPO3\CMS\Core\Utility\DiffUtility
+        */
+       protected function getDifferencesObject() {
+               if (!isset($this->differencesObject)) {
+                       $this->differencesObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\DiffUtility');
+               }
+               return $this->differencesObject;
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/IntegrityService.php b/typo3/sysext/workspaces/Classes/Service/IntegrityService.php
new file mode 100644 (file)
index 0000000..74d2c90
--- /dev/null
@@ -0,0 +1,253 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Service;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2012-2013 Oliver Hader <oliver.hader@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Service for integrity
+ *
+ * @author Oliver Hader <oliver.hader@typo3.org>
+ */
+class IntegrityService {
+
+       /**
+        * Succes status - everything is fine
+        *
+        * @var integer
+        */
+       const STATUS_Succes = 100;
+       /**
+        * Info status - nothing is wrong, but a notice is shown
+        *
+        * @var integer
+        */
+       const STATUS_Info = 101;
+       /**
+        * Warning status - user interaction might be required
+        *
+        * @var integer
+        */
+       const STATUS_Warning = 102;
+       /**
+        * Error status - user interaction is required
+        *
+        * @var integer
+        */
+       const STATUS_Error = 103;
+       /**
+        * @var array
+        */
+       protected $statusRepresentation = array(
+               self::STATUS_Succes => 'success',
+               self::STATUS_Info => 'info',
+               self::STATUS_Warning => 'warning',
+               self::STATUS_Error => 'error'
+       );
+
+       /**
+        * @var \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord[]
+        */
+       protected $affectedElements;
+
+       /**
+        * Array storing all issues that have been checked and
+        * found during runtime in this object. The array keys
+        * are identifiers of table and the version-id.
+        *
+        * 'tx_table:123' => array(
+        * array(
+        * 'status' => 'warning',
+        * 'message' => 'Element cannot be...',
+        * )
+        * )
+        *
+        * @var array
+        */
+       protected $issues = array();
+
+       /**
+        * Sets the affected elements.
+        *
+        * @param \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord[] $affectedElements
+        * @return void
+        */
+       public function setAffectedElements(array $affectedElements) {
+               $this->affectedElements = $affectedElements;
+       }
+
+       /**
+        * Checks integrity of affected records.
+        *
+        * @return void
+        */
+       public function check() {
+               foreach ($this->affectedElements as $affectedElement) {
+                       $this->checkElement($affectedElement);
+               }
+       }
+
+       /**
+        * Checks a single element.
+        *
+        * @param \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord $element
+        * @return void
+        */
+       public function checkElement(\TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord $element) {
+               $this->checkLocalization($element);
+       }
+
+       /**
+        * Checks workspace localization integrity of a single elements.
+        * If current record is a localization and its localization parent
+        * is new in this workspace (has only a placeholder record in live),
+        * then boths (localization and localization parent) should be published.
+        *
+        * @param \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord $element
+        * @return void
+        */
+       protected function checkLocalization(\TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord $element) {
+               $table = $element->getTable();
+               if (\TYPO3\CMS\Backend\Utility\BackendUtility::isTableLocalizable($table)) {
+                       $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
+                       $languageParentField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
+                       $versionRow = $element->getVersionRecord()->getRow();
+                       // If element is a localization:
+                       if ($versionRow[$languageField] > 0) {
+                               // Get localization parent from live workspace:
+                               $languageParentRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $versionRow[$languageParentField], 'uid,t3ver_state');
+                               // If localization parent is a "new placeholder" record:
+                               if ($languageParentRecord['t3ver_state'] == 1) {
+                                       $title = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle($table, $versionRow);
+                                       // Add warning for current versionized record:
+                                       $this->addIssue($element->getLiveRecord()->getIdentifier(), self::STATUS_Warning, sprintf(\TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('integrity.dependsOnDefaultLanguageRecord', 'workspaces'), $title));
+                                       // Add info for related localization parent record:
+                                       $this->addIssue($table . ':' . $languageParentRecord['uid'], self::STATUS_Info, sprintf(\TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate('integrity.isDefaultLanguageRecord', 'workspaces'), $title));
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Gets the status of the most important severity.
+        * (low << success, info, warning, error >> high)
+        *
+        * @param string $identifier Record identifier (table:id) for look-ups
+        * @return string
+        */
+       public function getStatus($identifier = NULL) {
+               $status = self::STATUS_Succes;
+               if ($identifier === NULL) {
+                       foreach ($this->issues as $idenfieriferIssues) {
+                               foreach ($idenfieriferIssues as $issue) {
+                                       if ($status < $issue['status']) {
+                                               $status = $issue['status'];
+                                       }
+                               }
+                       }
+               } else {
+                       foreach ($this->getIssues($identifier) as $issue) {
+                               if ($status < $issue['status']) {
+                                       $status = $issue['status'];
+                               }
+                       }
+               }
+               return $status;
+       }
+
+       /**
+        * Gets the (human readable) represetation of the status with the most
+        * important severity (wraps $this->getStatus() and translates the result).
+        *
+        * @param string $identifier Record identifier (table:id) for look-ups
+        * @return string One out of success, info, warning, error
+        */
+       public function getStatusRepresentation($identifier = NULL) {
+               return $this->statusRepresentation[$this->getStatus($identifier)];
+       }
+
+       /**
+        * Gets issues, all or specific for one identifier.
+        *
+        * @param string $identifier Record identifier (table:id) for look-ups
+        * @return array
+        */
+       public function getIssues($identifier = NULL) {
+               if ($identifier === NULL) {
+                       return $this->issues;
+               } elseif (isset($this->issues[$identifier])) {
+                       return $this->issues[$identifier];
+               }
+               return array();
+       }
+
+       /**
+        * Gets the message of all issues.
+        *
+        * @param string $identifier Record identifier (table:id) for look-ups
+        * @param boolean $asString Return results as string instead of array
+        * @return array|string
+        */
+       public function getIssueMessages($identifier = NULL, $asString = FALSE) {
+               $messages = array();
+               if ($identifier === NULL) {
+                       foreach ($this->issues as $idenfieriferIssues) {
+                               foreach ($idenfieriferIssues as $issue) {
+                                       $messages[] = $issue['message'];
+                               }
+                       }
+               } else {
+                       foreach ($this->getIssues($identifier) as $issue) {
+                               $messages[] = $issue['message'];
+                       }
+               }
+               if ($asString) {
+                       $messages = implode('<br/>', $messages);
+               }
+               return $messages;
+       }
+
+       /**
+        * Adds an issue.
+        *
+        * @param string $identifier Record identifier (table:id)
+        * @param integer $status Status code (see constants)
+        * @param string $message Message/description of the issue
+        * @return void
+        */
+       protected function addIssue($identifier, $status, $message) {
+               if (!isset($this->issues[$identifier])) {
+                       $this->issues[$identifier] = array();
+               }
+               $this->issues[$identifier][] = array(
+                       'status' => $status,
+                       'message' => $message
+               );
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/StagesService.php b/typo3/sysext/workspaces/Classes/Service/StagesService.php
new file mode 100644 (file)
index 0000000..966d318
--- /dev/null
@@ -0,0 +1,777 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Service;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Stages service
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class StagesService {
+
+       const TABLE_STAGE = 'sys_workspace_stage';
+       // if a record is in the "ready to publish" stage STAGE_PUBLISH_ID the nextStage is STAGE_PUBLISH_EXECUTE_ID, this id wont be saved at any time in db
+       const STAGE_PUBLISH_EXECUTE_ID = -20;
+       // ready to publish stage
+       const STAGE_PUBLISH_ID = -10;
+       const STAGE_EDIT_ID = 0;
+       const MODE_NOTIFY_SOMEONE = 0;
+       const MODE_NOTIFY_ALL = 1;
+       const MODE_NOTIFY_ALL_STRICT = 2;
+       /**
+        * Current workspace ID
+        *
+        * @var integer
+        */
+       private $workspaceId = NULL;
+
+       /**
+        * Path to the locallang file
+        *
+        * @var string
+        */
+       private $pathToLocallang = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xml';
+
+       /**
+        * Local cache to reduce number of database queries for stages, groups, etc.
+        *
+        * @var array
+        */
+       protected $workspaceStageCache = array();
+
+       /**
+        * @var array
+        */
+       protected $workspaceStageAllowedCache = array();
+
+       /**
+        * @var array
+        */
+       protected $fetchGroupsCache = array();
+
+       /**
+        * @var array
+        */
+       protected $userGroups = array();
+
+       /**
+        * Getter for current workspace id
+        *
+        * @return int current workspace id
+        */
+       public function getWorkspaceId() {
+               if ($this->workspaceId == NULL) {
+                       $this->setWorkspaceId($GLOBALS['BE_USER']->workspace);
+               }
+               return $this->workspaceId;
+       }
+
+       /**
+        * Setter for current workspace id
+        *
+        * @param int current workspace id
+        */
+       private function setWorkspaceId($wsid) {
+               $this->workspaceId = $wsid;
+       }
+
+       /**
+        * constructor for workspace library
+        */
+       public function __construct() {
+               $this->setWorkspaceId($GLOBALS['BE_USER']->workspace);
+       }
+
+       /**
+        * Find the highest possible "previous" stage for all $byTableName
+        *
+        * @param array $workspaceItems
+        * @param array $byTableName
+        * @return array Current and next highest possible stage
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function getPreviousStageForElementCollection(
+               $workspaceItems,
+               array $byTableName = array('tt_content', 'pages', 'pages_language_overlay')
+       ) {
+               $currentStage = array();
+               $previousStage = array();
+               $usedStages = array();
+               $found = FALSE;
+               $availableStagesForWS = array_reverse($this->getStagesForWS());
+               $availableStagesForWSUser = $this->getStagesForWSUser();
+               $byTableName = array_flip($byTableName);
+               foreach ($workspaceItems as $tableName => $items) {
+                       if (!array_key_exists($tableName, $byTableName)) {
+                               continue;
+                       }
+                       foreach ($items as $item) {
+                               $usedStages[$item['t3ver_stage']] = TRUE;
+                       }
+               }
+               foreach ($availableStagesForWS as $stage) {
+                       if (isset($usedStages[$stage['uid']])) {
+                               $currentStage = $stage;
+                               $previousStage = $this->getPrevStage($stage['uid']);
+                               break;
+                       }
+               }
+               foreach ($availableStagesForWSUser as $userWS) {
+                       if ($previousStage['uid'] == $userWS['uid']) {
+                               $found = TRUE;
+                               break;
+                       }
+               }
+               if ($found === FALSE) {
+                       $previousStage = array();
+               }
+               return array(
+                       $currentStage,
+                       $previousStage
+               );
+       }
+
+       /**
+        * Retrieve the next stage based on the lowest stage given in the $workspaceItems record array.
+        *
+        * @param array $workspaceItems
+        * @param array $byTableName
+        * @return array Current and next possible stage.
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function getNextStageForElementCollection(
+               $workspaceItems,
+               array $byTableName = array('tt_content', 'pages', 'pages_language_overlay')
+       ) {
+               $currentStage = array();
+               $usedStages = array();
+               $nextStage = array();
+               $availableStagesForWS = $this->getStagesForWS();
+               $availableStagesForWSUser = $this->getStagesForWSUser();
+               $byTableName = array_flip($byTableName);
+               $found = FALSE;
+               foreach ($workspaceItems as $tableName => $items) {
+                       if (!array_key_exists($tableName, $byTableName)) {
+                               continue;
+                       }
+                       foreach ($items as $item) {
+                               $usedStages[$item['t3ver_stage']] = TRUE;
+                       }
+               }
+               foreach ($availableStagesForWS as $stage) {
+                       if (isset($usedStages[$stage['uid']])) {
+                               $currentStage = $stage;
+                               $nextStage = $this->getNextStage($stage['uid']);
+                               break;
+                       }
+               }
+               foreach ($availableStagesForWSUser as $userWS) {
+                       if ($nextStage['uid'] == $userWS['uid']) {
+                               $found = TRUE;
+                               break;
+                       }
+               }
+               if ($found === FALSE) {
+                       $nextStage = array();
+               }
+               return array(
+                       $currentStage,
+                       $nextStage
+               );
+       }
+
+       /**
+        * Building an array with all stage ids and titles related to the given workspace
+        *
+        * @return array id and title of the stages
+        */
+       public function getStagesForWS() {
+               $stages = array();
+               if (isset($this->workspaceStageCache[$this->getWorkspaceId()])) {
+                       $stages = $this->workspaceStageCache[$this->getWorkspaceId()];
+               } else {
+                       $stages[] = array(
+                               'uid' => self::STAGE_EDIT_ID,
+                               'title' => $GLOBALS['LANG']->sL(($this->pathToLocallang . ':actionSendToStage')) . ' "' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_editing') . '"'
+                       );
+                       $workspaceRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('sys_workspace', $this->getWorkspaceId());
+                       if ($workspaceRec['custom_stages'] > 0) {
+                               // Get all stage records for this workspace
+                               $workspaceStageRecs = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', self::TABLE_STAGE, 'parentid=' . $this->getWorkspaceId() . ' AND parenttable=' . $GLOBALS['TYPO3_DB']->fullQuoteStr('sys_workspace', self::TABLE_STAGE) . ' AND deleted=0', '', 'sorting', '', 'uid');
+                               foreach ($workspaceStageRecs as $stage) {
+                                       $stage['title'] = $GLOBALS['LANG']->sL(($this->pathToLocallang . ':actionSendToStage')) . ' "' . $stage['title'] . '"';
+                                       $stages[] = $stage;
+                               }
+                       }
+                       $stages[] = array(
+                               'uid' => self::STAGE_PUBLISH_ID,
+                               'title' => $GLOBALS['LANG']->sL(($this->pathToLocallang . ':actionSendToStage')) . ' "' . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xml:stage_ready_to_publish') . '"'
+                       );
+                       $stages[] = array(
+                               'uid' => self::STAGE_PUBLISH_EXECUTE_ID,
+                               'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':publish_execute_action_option')
+                       );
+                       $this->workspaceStageCache[$this->getWorkspaceId()] = $stages;
+               }
+               return $stages;
+       }
+
+       /**
+        * Returns an array of stages, the user is allowed to send to
+        *
+        * @return array id and title of stages
+        */
+       public function getStagesForWSUser() {
+               $stagesForWSUserData = array();
+               $allowedStages = array();
+               $orderedAllowedStages = array();
+               $workspaceStageRecs = $this->getStagesForWS();
+               if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
+                       if ($GLOBALS['BE_USER']->isAdmin()) {
+                               $orderedAllowedStages = $workspaceStageRecs;
+                       } else {
+                               foreach ($workspaceStageRecs as $workspaceStageRec) {
+                                       if ($workspaceStageRec['uid'] === self::STAGE_EDIT_ID) {
+                                               $allowedStages[self::STAGE_EDIT_ID] = $workspaceStageRec;
+                                               $stagesForWSUserData[$workspaceStageRec['uid']] = $workspaceStageRec;
+                                       } elseif ($this->isStageAllowedForUser($workspaceStageRec['uid'])) {
+                                               $stagesForWSUserData[$workspaceStageRec['uid']] = $workspaceStageRec;
+                                       } elseif ($workspaceStageRec['uid'] == self::STAGE_PUBLISH_EXECUTE_ID && $GLOBALS['BE_USER']->workspacePublishAccess($this->getWorkspaceId())) {
+                                               $allowedStages[] = $workspaceStageRec;
+                                               $stagesForWSUserData[$workspaceStageRec['uid']] = $workspaceStageRec;
+                                       }
+                               }
+                               foreach ($stagesForWSUserData as $allowedStage) {
+                                       $nextStage = $this->getNextStage($allowedStage['uid']);
+                                       $prevStage = $this->getPrevStage($allowedStage['uid']);
+                                       if (isset($nextStage['uid'])) {
+                                               $allowedStages[$nextStage['uid']] = $nextStage;
+                                       }
+                                       if (isset($prevStage['uid'])) {
+                                               $allowedStages[$prevStage['uid']] = $prevStage;
+                                       }
+                               }
+                               $orderedAllowedStages = array_values($allowedStages);
+                       }
+               }
+               return $orderedAllowedStages;
+       }
+
+       /**
+        * Check if given workspace has custom staging activated
+        *
+        * @return bool TRUE or FALSE
+        */
+       public function checkCustomStagingForWS() {
+               $workspaceRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('sys_workspace', $this->getWorkspaceId());
+               return $workspaceRec['custom_stages'] > 0;
+       }
+
+       /**
+        * Gets the title of a stage.
+        *
+        * @param integer $ver_stage
+        * @return string
+        */
+       public function getStageTitle($ver_stage) {
+               global $LANG;
+               $stageTitle = '';
+               switch ($ver_stage) {
+               case self::STAGE_PUBLISH_EXECUTE_ID:
+                       $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_publish');
+                       break;
+               case self::STAGE_PUBLISH_ID:
+                       $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xml:stage_ready_to_publish');
+                       break;
+               case self::STAGE_EDIT_ID:
+                       $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_editing');
+                       break;
+               default:
+                       $stageTitle = $this->getPropertyOfCurrentWorkspaceStage($ver_stage, 'title');
+                       if ($stageTitle == NULL) {
+                               $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.getStageTitle.stageNotFound');
+                       }
+                       break;
+               }
+               return $stageTitle;
+       }
+
+       /**
+        * Gets a particular stage record.
+        *
+        * @param integer $stageid
+        * @return array
+        */
+       public function getStageRecord($stageid) {
+               return \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('sys_workspace_stage', $stageid);
+       }
+
+       /**
+        * Gets next stage in process for given stage id
+        *
+        * @param integer $stageid Id of the stage to fetch the next one for
+        * @return integer The next stage Id
+        */
+       public function getNextStage($stageId) {
+               if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($stageId)) {
+                       throw new \InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'), 1291109987);
+               }
+               $nextStage = FALSE;
+               $workspaceStageRecs = $this->getStagesForWS();
+               if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
+                       reset($workspaceStageRecs);
+                       while (!is_null(($workspaceStageRecKey = key($workspaceStageRecs)))) {
+                               $workspaceStageRec = current($workspaceStageRecs);
+                               if ($workspaceStageRec['uid'] == $stageId) {
+                                       $nextStage = next($workspaceStageRecs);
+                                       break;
+                               }
+                               next($workspaceStageRecs);
+                       }
+               } else {
+
+               }
+               if ($nextStage === FALSE) {
+                       $nextStage[] = array(
+                               'uid' => self::STAGE_EDIT_ID,
+                               'title' => $GLOBALS['LANG']->sL(($this->pathToLocallang . ':actionSendToStage')) . ' "' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_editing') . '"'
+                       );
+               }
+               return $nextStage;
+       }
+
+       /**
+        * Recursive function to get all next stages for a record depending on user permissions
+        *
+        * @param       array   next stages
+        * @param       int             current stage id of the record
+        * @return      array   next stages
+        */
+       public function getNextStages(array &$nextStageArray, $stageId) {
+               // Current stage is "Ready to publish" - there is no next stage
+               if ($stageId == self::STAGE_PUBLISH_ID) {
+                       return $nextStageArray;
+               } else {
+                       $nextStageRecord = $this->getNextStage($stageId);
+                       if (empty($nextStageRecord) || !is_array($nextStageRecord)) {
+                               // There is no next stage
+                               return $nextStageArray;
+                       } else {
+                               // Check if the user has the permission to for the current stage
+                               // If this next stage record is the first next stage after the current the user
+                               // has always the needed permission
+                               if ($this->isStageAllowedForUser($stageId)) {
+                                       $nextStageArray[] = $nextStageRecord;
+                                       return $this->getNextStages($nextStageArray, $nextStageRecord['uid']);
+                               } else {
+                                       // He hasn't - return given next stage array
+                                       return $nextStageArray;
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Get next stage in process for given stage id
+        *
+        * @param int                   stageid
+        * @return int                  id
+        */
+       public function getPrevStage($stageid) {
+               if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($stageid)) {
+                       throw new \InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
+               }
+               $prevStage = FALSE;
+               $workspaceStageRecs = $this->getStagesForWS();
+               if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
+                       end($workspaceStageRecs);
+                       while (!is_null(($workspaceStageRecKey = key($workspaceStageRecs)))) {
+                               $workspaceStageRec = current($workspaceStageRecs);
+                               if ($workspaceStageRec['uid'] == $stageid) {
+                                       $prevStage = prev($workspaceStageRecs);
+                                       break;
+                               }
+                               prev($workspaceStageRecs);
+                       }
+               } else {
+
+               }
+               return $prevStage;
+       }
+
+       /**
+        * Recursive function to get all prev stages for a record depending on user permissions
+        *
+        * @param       array   prev stages
+        * @param       int             current stage id of the record
+        * @return      array   prev stages
+        */
+       public function getPrevStages(array &$prevStageArray, $stageId) {
+               // Current stage is "Editing" - there is no prev stage
+               if ($stageId != self::STAGE_EDIT_ID) {
+                       $prevStageRecord = $this->getPrevStage($stageId);
+                       if (!empty($prevStageRecord) && is_array($prevStageRecord)) {
+                               // Check if the user has the permission to switch to that stage
+                               // If this prev stage record is the first previous stage before the current
+                               // the user has always the needed permission
+                               if ($this->isStageAllowedForUser($stageId)) {
+                                       $prevStageArray[] = $prevStageRecord;
+                                       $prevStageArray = $this->getPrevStages($prevStageArray, $prevStageRecord['uid']);
+                               }
+                       }
+               }
+               return $prevStageArray;
+       }
+
+       /**
+        * Get array of all responsilbe be_users for a stage
+        *
+        * @param       int     stage id
+        * @param       boolean if field notification_defaults should be selected instead of responsible users
+        * @return      array be_users with e-mail and name
+        */
+       public function getResponsibleBeUser($stageId, $selectDefaultUserField = FALSE) {
+               $workspaceRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('sys_workspace', $this->getWorkspaceId());
+               $recipientArray = array();
+               switch ($stageId) {
+               case self::STAGE_PUBLISH_EXECUTE_ID:
+
+               case self::STAGE_PUBLISH_ID:
+                       if ($selectDefaultUserField == FALSE) {
+                               $userList = $this->getResponsibleUser($workspaceRec['adminusers']);
+                       } else {
+                               $notification_default_user = $workspaceRec['publish_notification_defaults'];
+                               $userList = $this->getResponsibleUser($notification_default_user);
+                       }
+                       break;
+               case self::STAGE_EDIT_ID:
+                       if ($selectDefaultUserField == FALSE) {
+                               $userList = $this->getResponsibleUser($workspaceRec['members']);
+                       } else {
+                               $notification_default_user = $workspaceRec['edit_notification_defaults'];
+                               $userList = $this->getResponsibleUser($notification_default_user);
+                       }
+                       break;
+               default:
+                       if ($selectDefaultUserField == FALSE) {
+                               $responsible_persons = $this->getPropertyOfCurrentWorkspaceStage($stageId, 'responsible_persons');
+                               $userList = $this->getResponsibleUser($responsible_persons);
+                       } else {
+                               $notification_default_user = $this->getPropertyOfCurrentWorkspaceStage($stageId, 'notification_defaults');
+                               $userList = $this->getResponsibleUser($notification_default_user);
+                       }
+                       break;
+               }
+               if (!empty($userList)) {
+                       $userRecords = \TYPO3\CMS\Backend\Utility\BackendUtility::getUserNames('username, uid, email, realName', 'AND uid IN (' . $userList . ')');
+               }
+               if (!empty($userRecords) && is_array($userRecords)) {
+                       foreach ($userRecords as $userUid => $userRecord) {
+                               $recipientArray[$userUid] = $userRecord;
+                       }
+               }
+               return $recipientArray;
+       }
+
+       /**
+        * Get uids of all responsilbe persons for a stage
+        *
+        * @param       string  responsible_persion value from stage record
+        * @return      string  uid list of responsible be_users
+        */
+       public function getResponsibleUser($stageRespValue) {
+               $stageValuesArray = array();
+               $stageValuesArray = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $stageRespValue);
+               $beuserUidArray = array();
+               $begroupUidArray = array();
+               $allBeUserArray = array();
+               $begroupUidList = array();
+               foreach ($stageValuesArray as $key => $uidvalue) {
+                       if (strstr($uidvalue, 'be_users') !== FALSE) {
+                               // Current value is a uid of a be_user record
+                               $beuserUidArray[] = str_replace('be_users_', '', $uidvalue);
+                       } elseif (strstr($uidvalue, 'be_groups') !== FALSE) {
+                               $begroupUidArray[] = str_replace('be_groups_', '', $uidvalue);
+                       } else {
+                               $beuserUidArray[] = $uidvalue;
+                       }
+               }
+               if (!empty($begroupUidArray)) {
+                       $allBeUserArray = \TYPO3\CMS\Backend\Utility\BackendUtility::getUserNames();
+                       $begroupUidList = implode(',', $begroupUidArray);
+                       $this->userGroups = array();
+                       $begroupUidArray = $this->fetchGroups($begroupUidList);
+                       foreach ($begroupUidArray as $groupkey => $groupData) {
+                               foreach ($allBeUserArray as $useruid => $userdata) {
+                                       if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList($userdata['usergroup_cached_list'], $groupData['uid'])) {
+                                               $beuserUidArray[] = $useruid;
+                                       }
+                               }
+                       }
+               }
+               array_unique($beuserUidArray);
+               return implode(',', $beuserUidArray);
+       }
+
+       /**
+        * @param $grList
+        * @param string $idList
+        * @return array
+        */
+       private function fetchGroups($grList, $idList = '') {
+               $cacheKey = md5($grList . $idList);
+               $groupList = array();
+               if (isset($this->fetchGroupsCache[$cacheKey])) {
+                       $groupList = $this->fetchGroupsCache[$cacheKey];
+               } else {
+                       if ($idList === '') {
+                               // we're at the beginning of the recursion and therefore we need to reset the userGroups member
+                               $this->userGroups = array();
+                       }
+                       $groupList = $this->fetchGroupsRecursive($grList);
+                       $this->fetchGroupsCache[$cacheKey] = $groupList;
+               }
+               return $groupList;
+       }
+
+       /**
+        * @param array         $groups
+        * @return void
+        */
+       private function fetchGroupsFromDB(array $groups) {
+               $whereSQL = 'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . implode(',', $groups) . ') ';
+               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'be_groups', $whereSQL);
+               // The userGroups array is filled
+               while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
+                       $this->userGroups[$row['uid']] = $row;
+               }
+       }
+
+       /**
+        * Fetches particular groups recursively.
+        *
+        * @param $grList
+        * @param string $idList
+        * @return array
+        */
+       private function fetchGroupsRecursive($grList, $idList = '') {
+               $requiredGroups = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $grList, TRUE);
+               $existingGroups = array_keys($this->userGroups);
+               $missingGroups = array_diff($requiredGroups, $existingGroups);
+               if (count($missingGroups) > 0) {
+                       $this->fetchGroupsFromDB($missingGroups);
+               }
+               // Traversing records in the correct order
+               foreach ($requiredGroups as $uid) {
+                       // traversing list
+                       // Get row:
+                       $row = $this->userGroups[$uid];
+                       if (is_array($row) && !\TYPO3\CMS\Core\Utility\GeneralUtility::inList($idList, $uid)) {
+                               // Must be an array and $uid should not be in the idList, because then it is somewhere previously in the grouplist
+                               // If the localconf.php option isset the user of the sub- sub- groups will also be used
+                               if ($GLOBALS['TYPO3_CONF_VARS']['BE']['customStageShowRecipientRecursive'] == 1) {
+                                       // Include sub groups
+                                       if (trim($row['subgroup'])) {
+                                               // Make integer list
+                                               $theList = implode(',', \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $row['subgroup']));
+                                               // Get the subarray
+                                               $subbarray = $this->fetchGroups($theList, $idList . ',' . $uid);
+                                               list($subUid, $subArray) = each($subbarray);
+                                               // Merge the subarray to the already existing userGroups array
+                                               $this->userGroups[$subUid] = $subArray;
+                                       }
+                               }
+                       }
+               }
+               return $this->userGroups;
+       }
+
+       /**
+        * Gets a property of a workspaces stage.
+        *
+        * @param integer $stageId
+        * @param string $property
+        * @return string
+        */
+       public function getPropertyOfCurrentWorkspaceStage($stageId, $property) {
+               $result = NULL;
+               if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($stageId)) {
+                       throw new \InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
+               }
+               $workspaceStage = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord(self::TABLE_STAGE, $stageId);
+               if (is_array($workspaceStage) && isset($workspaceStage[$property])) {
+                       $result = $workspaceStage[$property];
+               }
+               return $result;
+       }
+
+       /**
+        * Gets the position of the given workspace in the hole process f.e. 3 means step 3 of 20, by which 1 is edit and 20 is ready to publish
+        *
+        * @param integer $stageId
+        * @return array position => 3, count => 20
+        */
+       public function getPositionOfCurrentStage($stageId) {
+               $stagesOfWS = $this->getStagesForWS();
+               $countOfStages = count($stagesOfWS);
+               switch ($stageId) {
+               case self::STAGE_PUBLISH_ID:
+                       $position = $countOfStages;
+                       break;
+               case self::STAGE_EDIT_ID:
+                       $position = 1;
+                       break;
+               default:
+                       $position = 1;
+                       foreach ($stagesOfWS as $key => $stageInfoArray) {
+                               $position++;
+                               if ($stageId == $stageInfoArray['uid']) {
+                                       break;
+                               }
+                       }
+                       break;
+               }
+               return array('position' => $position, 'count' => $countOfStages);
+       }
+
+       /**
+        * Check if the user has access to the previous stage, relative to the given stage
+        *
+        * @param integer $stageId
+        * @return bool
+        */
+       public function isPrevStageAllowedForUser($stageId) {
+               $isAllowed = FALSE;
+               try {
+                       $prevStage = $this->getPrevStage($stageId);
+                       // if there's no prev-stage the stageIds match,
+                       // otherwise we've to check if the user is permitted to use the stage
+                       if (!empty($prevStage) && $prevStage['uid'] != $stageId) {
+                               // if the current stage is allowed for the user, the user is also allowed to send to prev
+                               $isAllowed = $this->isStageAllowedForUser($stageId);
+                       }
+               } catch (\Exception $e) {
+
+               }
+               return $isAllowed;
+       }
+
+       /**
+        * Check if the user has access to the next stage, relative to the given stage
+        *
+        * @param integer $stageId
+        * @return bool
+        */
+       public function isNextStageAllowedForUser($stageId) {
+               $isAllowed = FALSE;
+               try {
+                       $nextStage = $this->getNextStage($stageId);
+                       // if there's no next-stage the stageIds match,
+                       // otherwise we've to check if the user is permitted to use the stage
+                       if (!empty($nextStage) && $nextStage['uid'] != $stageId) {
+                               // if the current stage is allowed for the user, the user is also allowed to send to next
+                               $isAllowed = $this->isStageAllowedForUser($stageId);
+                       }
+               } catch (\Exception $e) {
+
+               }
+               return $isAllowed;
+       }
+
+       /**
+        * @param $stageId
+        * @return bool
+        */
+       protected function isStageAllowedForUser($stageId) {
+               $cacheKey = $this->getWorkspaceId() . '_' . $stageId;
+               $isAllowed = FALSE;
+               if (isset($this->workspaceStageAllowedCache[$cacheKey])) {
+                       $isAllowed = $this->workspaceStageAllowedCache[$cacheKey];
+               } else {
+                       $isAllowed = $GLOBALS['BE_USER']->workspaceCheckStageForCurrent($stageId);
+                       $this->workspaceStageAllowedCache[$cacheKey] = $isAllowed;
+               }
+               return $isAllowed;
+       }
+
+       /**
+        * Determines whether a stage Id is valid.
+        *
+        * @param integer $stageId The stage Id to be checked
+        * @return boolean
+        */
+       public function isValid($stageId) {
+               $isValid = FALSE;
+               $stages = $this->getStagesForWS();
+               foreach ($stages as $stage) {
+                       if ($stage['uid'] == $stageId) {
+                               $isValid = TRUE;
+                               break;
+                       }
+               }
+               return $isValid;
+       }
+
+       /**
+        * Returns the notification mode from stage configuration
+        *
+        * Return values:
+        * 0 = notify someone / old way / default setting
+        * 1 = notify all responsible users (some users checked per default and you're not allowed to uncheck them)
+        * 2 = notify all responsible users (all users are checked and nothing can be changed during stage change)
+        *
+        * @param integer stage id to return the notification mode for
+        * @return integer
+        */
+       public function getNotificationMode($stageId) {
+               if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($stageId)) {
+                       throw new \InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
+               }
+               switch ($stageId) {
+               case self::STAGE_PUBLISH_EXECUTE_ID:
+
+               case self::STAGE_PUBLISH_ID:
+                       $workspaceRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('sys_workspace', $this->getWorkspaceId());
+                       return $workspaceRecord['publish_notification_mode'];
+                       break;
+               case self::STAGE_EDIT_ID:
+                       $workspaceRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('sys_workspace', $this->getWorkspaceId());
+                       return $workspaceRecord['edit_notification_mode'];
+                       break;
+               default:
+                       $workspaceStage = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord(self::TABLE_STAGE, $stageId);
+                       if (is_array($workspaceStage) && isset($workspaceStage['notification_mode'])) {
+                               return $workspaceStage['notification_mode'];
+                       }
+                       break;
+               }
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/WorkspaceService.php b/typo3/sysext/workspaces/Classes/Service/WorkspaceService.php
new file mode 100644 (file)
index 0000000..2c8784a
--- /dev/null
@@ -0,0 +1,631 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Service;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Workspace service
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class WorkspaceService implements \TYPO3\CMS\Core\SingletonInterface {
+
+       /**
+        * @var array
+        */
+       protected $pageCache = array();
+
+       const TABLE_WORKSPACE = 'sys_workspace';
+       const SELECT_ALL_WORKSPACES = -98;
+       const LIVE_WORKSPACE_ID = 0;
+       /**
+        * retrieves the available workspaces from the database and checks whether
+        * they're available to the current BE user
+        *
+        * @return      array   array of worspaces available to the current user
+        */
+       public function getAvailableWorkspaces() {
+               $availableWorkspaces = array();
+               // add default workspaces
+               if ($GLOBALS['BE_USER']->checkWorkspace(array('uid' => (string) self::LIVE_WORKSPACE_ID))) {
+                       $availableWorkspaces[self::LIVE_WORKSPACE_ID] = self::getWorkspaceTitle(self::LIVE_WORKSPACE_ID);
+               }
+               // add custom workspaces (selecting all, filtering by BE_USER check):
+               $customWorkspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, title, adminusers, members', 'sys_workspace', 'pid = 0' . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause('sys_workspace'), '', 'title');
+               if (count($customWorkspaces)) {
+                       foreach ($customWorkspaces as $workspace) {
+                               if ($GLOBALS['BE_USER']->checkWorkspace($workspace)) {
+                                       $availableWorkspaces[$workspace['uid']] = $workspace['title'];
+                               }
+                       }
+               }
+               return $availableWorkspaces;
+       }
+
+       /**
+        * Gets the current workspace ID.
+        *
+        * @return integer The current workspace ID
+        */
+       public function getCurrentWorkspace() {
+               $workspaceId = $GLOBALS['BE_USER']->workspace;
+               if ($GLOBALS['BE_USER']->isAdmin()) {
+                       $activeId = $GLOBALS['BE_USER']->getSessionData('tx_workspace_activeWorkspace');
+                       $workspaceId = $activeId !== NULL ? $activeId : $workspaceId;
+               }
+               return $workspaceId;
+       }
+
+       /**
+        * Find the title for the requested workspace.
+        *
+        * @param integer $wsId
+        * @return string
+        */
+       static public function getWorkspaceTitle($wsId) {
+               $title = FALSE;
+               switch ($wsId) {
+               case self::LIVE_WORKSPACE_ID:
+                       $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:shortcut_onlineWS');
+                       break;
+               default:
+                       $labelField = $GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
+                       $wsRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
+                       if (is_array($wsRecord)) {
+                               $title = $wsRecord[$labelField];
+                       }
+               }
+               if ($title === FALSE) {
+                       throw new \InvalidArgumentException('No such workspace defined');
+               }
+               return $title;
+       }
+
+       /**
+        * Building tcemain CMD-array for swapping all versions in a workspace.
+        *
+        * @param       integer         Real workspace ID, cannot be ONLINE (zero).
+        * @param       boolean         If set, then the currently online versions are swapped into the workspace in exchange for the offline versions. Otherwise the workspace is emptied.
+        * @param       integer         $pageId: ...
+        * @param       integer         $language Select specific language only
+        * @return      array           Command array for tcemain
+        */
+       public function getCmdArrayForPublishWS($wsid, $doSwap, $pageId = 0, $language = NULL) {
+               $wsid = intval($wsid);
+               $cmd = array();
+               if ($wsid >= -1 && $wsid !== 0) {
+                       // Define stage to select:
+                       $stage = -99;
+                       if ($wsid > 0) {
+                               $workspaceRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('sys_workspace', $wsid);
+                               if ($workspaceRec['publish_access'] & 1) {
+                                       $stage = \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_ID;
+                               }
+                       }
+                       // Select all versions to swap:
+                       $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, $pageId ? $pageId : -1, 0, 'tables_modify', $language);
+                       // Traverse the selection to build CMD array:
+                       foreach ($versions as $table => $records) {
+                               foreach ($records as $rec) {
+                                       // Build the cmd Array:
+                                       $cmd[$table][$rec['t3ver_oid']]['version'] = array('action' => 'swap', 'swapWith' => $rec['uid'], 'swapIntoWS' => $doSwap ? 1 : 0);
+                               }
+                       }
+               }
+               return $cmd;
+       }
+
+       /**
+        * Building tcemain CMD-array for releasing all versions in a workspace.
+        *
+        * @param       integer         Real workspace ID, cannot be ONLINE (zero).
+        * @param       boolean         Run Flush (TRUE) or ClearWSID (FALSE) command
+        * @param       integer         $pageId: ...
+        * @param       integer         $language Select specific language only
+        * @return      array           Command array for tcemain
+        */
+       public function getCmdArrayForFlushWS($wsid, $flush = TRUE, $pageId = 0, $language = NULL) {
+               $wsid = intval($wsid);
+               $cmd = array();
+               if ($wsid >= -1 && $wsid !== 0) {
+                       // Define stage to select:
+                       $stage = -99;
+                       // Select all versions to swap:
+                       $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, $pageId ? $pageId : -1, 0, 'tables_modify', $language);
+                       // Traverse the selection to build CMD array:
+                       foreach ($versions as $table => $records) {
+                               foreach ($records as $rec) {
+                                       // Build the cmd Array:
+                                       $cmd[$table][$rec['uid']]['version'] = array('action' => $flush ? 'flush' : 'clearWSID');
+                               }
+                       }
+               }
+               return $cmd;
+       }
+
+       /**
+        * Select all records from workspace pending for publishing
+        * Used from backend to display workspace overview
+        * User for auto-publishing for selecting versions for publication
+        *
+        * @param       integer         Workspace ID. If -99, will select ALL versions from ANY workspace. If -98 will select all but ONLINE. >=-1 will select from the actual workspace
+        * @param       integer         Lifecycle filter: 1 = select all drafts (never-published), 2 = select all published one or more times (archive/multiple), anything else selects all.
+        * @param       integer         Stage filter: -99 means no filtering, otherwise it will be used to select only elements with that stage. For publishing, that would be "10
+        * @param       integer         Page id: Live page for which to find versions in workspace!
+        * @param       integer         Recursion Level - select versions recursive - parameter is only relevant if $pageId != -1
+        * @param       string          How to collect records for "listing" or "modify" these tables. Support the permissions of each type of record, see \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::check.
+        * @param       integer         $language Select specific language only
+        * @return      array           Array of all records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oidfields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
+        */
+       public function selectVersionsInWorkspace($wsid, $filter = 0, $stage = -99, $pageId = -1, $recursionLevel = 0, $selectionType = 'tables_select', $language = NULL) {
+               $wsid = intval($wsid);
+               $filter = intval($filter);
+               $output = array();
+               // Contains either nothing or a list with live-uids
+               if ($pageId != -1 && $recursionLevel > 0) {
+                       $pageList = $this->getTreeUids($pageId, $wsid, $recursionLevel);
+               } elseif ($pageId != -1) {
+                       $pageList = $pageId;
+               } else {
+                       $pageList = '';
+                       // check if person may only see a "virtual" page-root
+                       $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
+                       $mountPoints = array_unique($mountPoints);
+                       if (!in_array(0, $mountPoints)) {
+                               $tempPageIds = array();
+                               foreach ($mountPoints as $mountPoint) {
+                                       $tempPageIds[] = $this->getTreeUids($mountPoint, $wsid, $recursionLevel);
+                               }
+                               $pageList = implode(',', $tempPageIds);
+                       }
+               }
+               // Traversing all tables supporting versioning:
+               foreach ($GLOBALS['TCA'] as $table => $cfg) {
+                       // we do not collect records from tables without permissions on them.
+                       if (!$GLOBALS['BE_USER']->check($selectionType, $table)) {
+                               continue;
+                       }
+                       if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+                               $recs = $this->selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language);
+                               if (intval($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) === 2) {
+                                       $moveRecs = $this->getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage);
+                                       $recs = array_merge($recs, $moveRecs);
+                               }
+                               $recs = $this->filterPermittedElements($recs, $table);
+                               if (count($recs)) {
+                                       $output[$table] = $recs;
+                               }
+                       }
+               }
+               return $output;
+       }
+
+       /**
+        * Find all versionized elements except moved records.
+        *
+        * @param string $table
+        * @param string $pageList
+        * @param integer $wsid
+        * @param integer $filter
+        * @param integer $stage
+        * @param integer $language
+        * @return array
+        */
+       protected function selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language = NULL) {
+               $isTableLocalizable = \TYPO3\CMS\Backend\Utility\BackendUtility::isTableLocalizable($table);
+               $languageParentField = '';
+               // If table is not localizable, but localized reocrds shall
+               // be collected, an empty result array needs to be returned:
+               if ($isTableLocalizable === FALSE && $language > 0) {
+                       return array();
+               } elseif ($isTableLocalizable) {
+                       $languageParentField = 'A.' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . ', ';
+               }
+               $fields = 'A.uid, A.t3ver_oid, A.t3ver_stage, ' . $languageParentField . 'B.pid AS wspid, B.pid AS livepid';
+               if ($isTableLocalizable) {
+                       $fields .= ', A.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
+               }
+               $from = $table . ' A,' . $table . ' B';
+               // Table A is the offline version and pid=-1 defines offline
+               $where = 'A.pid=-1 AND A.t3ver_state!=4';
+               if ($pageList) {
+                       $pidField = $table === 'pages' ? 'uid' : 'pid';
+                       $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
+                       $where .= ' AND B.' . $pidField . $pidConstraint;
+               }
+               if ($isTableLocalizable && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($language)) {
+                       $where .= ' AND A.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . $language;
+               }
+               // For "real" workspace numbers, select by that.
+               // If = -98, select all that are NOT online (zero).
+               // Anything else below -1 will not select on the wsid and therefore select all!
+               if ($wsid > self::SELECT_ALL_WORKSPACES) {
+                       $where .= ' AND A.t3ver_wsid=' . $wsid;
+               } elseif ($wsid === self::SELECT_ALL_WORKSPACES) {
+                       $where .= ' AND A.t3ver_wsid!=0';
+               }
+               // lifecycle filter:
+               // 1 = select all drafts (never-published),
+               // 2 = select all published one or more times (archive/multiple)
+               if ($filter === 1 || $filter === 2) {
+                       $where .= ' AND A.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
+               }
+               if ($stage != -99) {
+                       $where .= ' AND A.t3ver_stage=' . intval($stage);
+               }
+               // Table B (online) must have PID >= 0 to signify being online.
+               $where .= ' AND B.pid>=0';
+               // ... and finally the join between the two tables.
+               $where .= ' AND A.t3ver_oid=B.uid';
+               $where .= \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table, 'A');
+               $where .= \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table, 'B');
+               // Select all records from this table in the database from the workspace
+               // This joins the online version with the offline version as tables A and B
+               // Order by UID, mostly to have a sorting in the backend overview module which doesn't "jump around" when swapping.
+               $res = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'B.uid');
+               return is_array($res) ? $res : array();
+       }
+
+       /**
+        * Find all moved records at their new position.
+        *
+        * @param string $table
+        * @param string $pageList
+        * @param integer $wsid
+        * @param integer $filter
+        * @param integer $stage
+        * @return array
+        */
+       protected function getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage) {
+               // Aliases:
+               // A - moveTo placeholder
+               // B - online record
+               // C - moveFrom placeholder
+               $fields = 'A.pid AS wspid, B.uid AS t3ver_oid, C.uid AS uid, B.pid AS livepid';
+               $from = $table . ' A, ' . $table . ' B,' . $table . ' C';
+               $where = 'A.t3ver_state=3 AND B.pid>0 AND B.t3ver_state=0 AND B.t3ver_wsid=0 AND C.pid=-1 AND C.t3ver_state=4';
+               if ($wsid > self::SELECT_ALL_WORKSPACES) {
+                       $where .= ' AND A.t3ver_wsid=' . $wsid . ' AND C.t3ver_wsid=' . $wsid;
+               } elseif ($wsid === self::SELECT_ALL_WORKSPACES) {
+                       $where .= ' AND A.t3ver_wsid!=0 AND C.t3ver_wsid!=0 ';
+               }
+               // lifecycle filter:
+               // 1 = select all drafts (never-published),
+               // 2 = select all published one or more times (archive/multiple)
+               if ($filter === 1 || $filter === 2) {
+                       $where .= ' AND C.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
+               }
+               if ($stage != -99) {
+                       $where .= ' AND C.t3ver_stage=' . intval($stage);
+               }
+               if ($pageList) {
+                       $pidField = $table === 'pages' ? 'B.uid' : 'A.pid';
+                       $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
+                       $where .= ' AND ' . $pidField . $pidConstraint;
+               }
+               $where .= ' AND A.t3ver_move_id = B.uid AND B.uid = C.t3ver_oid';
+               $where .= \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table, 'A');
+               $where .= \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table, 'B');
+               $where .= \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table, 'C');
+               $res = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'A.uid');
+               return is_array($res) ? $res : array();
+       }
+
+       /**
+        * Find all page uids recursive starting from a specific page
+        *
+        * @param        integer        $pageId
+        * @param        integer        $wsid
+        * @param        integer        $recursionLevel
+        * @return      string  Comma sep. uid list
+        */
+       protected function getTreeUids($pageId, $wsid, $recursionLevel) {
+               // Reusing existing functionality with the drawback that
+               // mount points are not covered yet
+               $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
+               /** @var $searchObj \TYPO3\CMS\Core\Database\QueryView */
+               $searchObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\QueryView');
+               if ($pageId > 0) {
+                       $pageList = $searchObj->getTreeList($pageId, $recursionLevel, 0, $perms_clause);
+               } else {
+                       $mountPoints = $GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint'];
+                       if (!is_array($mountPoints) || empty($mountPoints)) {
+                               $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
+                               $mountPoints = array_unique($mountPoints);
+                       }
+                       $newList = array();
+                       foreach ($mountPoints as $mountPoint) {
+                               $newList[] = $searchObj->getTreeList($mountPoint, $recursionLevel, 0, $perms_clause);
+                       }
+                       $pageList = implode(',', $newList);
+               }
+               unset($searchObj);
+               if (intval($GLOBALS['TCA']['pages']['ctrl']['versioningWS']) === 2 && $pageList) {
+                       // Remove the "subbranch" if a page was moved away
+                       $movedAwayPages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, pid, t3ver_move_id', 'pages', 't3ver_move_id IN (' . $pageList . ') AND t3ver_wsid=' . intval($wsid) . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause('pages'), '', 'uid', '', 't3ver_move_id');
+                       $pageIds = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $pageList, TRUE);
+                       // move all pages away
+                       $newList = array_diff($pageIds, array_keys($movedAwayPages));
+                       // keep current page in the list
+                       $newList[] = $pageId;
+                       // move back in if still connected to the "remaining" pages
+                       do {
+                               $changed = FALSE;
+                               foreach ($movedAwayPages as $uid => $rec) {
+                                       if (in_array($rec['pid'], $newList) && !in_array($uid, $newList)) {
+                                               $newList[] = $uid;
+                                               $changed = TRUE;
+                                       }
+                               }
+                       } while ($changed);
+                       $pageList = implode(',', $newList);
+                       // In case moving pages is enabled we need to replace all move-to pointer with their origin
+                       $pages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, t3ver_move_id', 'pages', 'uid IN (' . $pageList . ')' . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause('pages'), '', 'uid', '', 'uid');
+                       $newList = array();
+                       $pageIds = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $pageList, TRUE);
+                       if (!in_array($pageId, $pageIds)) {
+                               $pageIds[] = $pageId;
+                       }
+                       foreach ($pageIds as $pageId) {
+                               if (intval($pages[$pageId]['t3ver_move_id']) > 0) {
+                                       $newList[] = intval($pages[$pageId]['t3ver_move_id']);
+                               } else {
+                                       $newList[] = $pageId;
+                               }
+                       }
+                       $pageList = implode(',', $newList);
+               }
+               return $pageList;
+       }
+
+       /**
+        * Remove all records which are not permitted for the user
+        *
+        * @param array $recs
+        * @param string $table
+        * @return array
+        */
+       protected function filterPermittedElements($recs, $table) {
+               $checkField = $table == 'pages' ? 'uid' : 'wspid';
+               $permittedElements = array();
+               if (is_array($recs)) {
+                       foreach ($recs as $rec) {
+                               $page = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('pages', $rec[$checkField], 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
+                               if ($GLOBALS['BE_USER']->doesUserHaveAccess($page, 1) && $this->isLanguageAccessibleForCurrentUser($table, $rec)) {
+                                       $permittedElements[] = $rec;
+                               }
+                       }
+               }
+               return $permittedElements;
+       }
+
+       /**
+        * Check current be users language access on given record.
+        *
+        * @param string $table Name of the table
+        * @param array $record Record row to be checked
+        * @return boolean
+        */
+       protected function isLanguageAccessibleForCurrentUser($table, array $record) {
+               $languageUid = 0;
+               if (\TYPO3\CMS\Backend\Utility\BackendUtility::isTableLocalizable($table)) {
+                       $languageUid = $record[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
+               } else {
+                       return TRUE;
+               }
+               return $GLOBALS['BE_USER']->checkLanguageAccess($languageUid);
+       }
+
+       /**
+        * Trivial check to see if the user already migrated his workspaces
+        * to the new style (either manually or with the migrator scripts)
+        *
+        * @return bool
+        */
+       static public function isOldStyleWorkspaceUsed() {
+               $oldStyleWorkspaceIsUsed = FALSE;
+               $cacheKey = 'workspace-oldstyleworkspace-notused';
+               $cacheResult = $GLOBALS['BE_USER']->getSessionData($cacheKey);
+               if (!$cacheResult) {
+                       $where = 'adminusers != "" AND adminusers NOT LIKE "%be_users%" AND adminusers NOT LIKE "%be_groups%" AND deleted=0';
+                       $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'sys_workspace', $where);
+                       $oldStyleWorkspaceIsUsed = $count > 0;
+                       $GLOBALS['BE_USER']->setAndSaveSessionData($cacheKey, !$oldStyleWorkspaceIsUsed);
+               } else {
+                       $oldStyleWorkspaceIsUsed = !$cacheResult;
+               }
+               return $oldStyleWorkspaceIsUsed;
+       }
+
+       /**
+        * Determine whether a specific page is new and not yet available in the LIVE workspace
+        *
+        * @static
+        * @param integer $id Primary key of the page to check
+        * @param integer $language Language for which to check the page
+        * @return boolean
+        */
+       static public function isNewPage($id, $language = 0) {
+               $isNewPage = FALSE;
+               // If the language is not default, check state of overlay
+               if ($language > 0) {
+                       $whereClause = 'pid = ' . intval($id);
+                       $whereClause .= ' AND ' . $GLOBALS['TCA']['pages_language_overlay']['ctrl']['languageField'] . ' = ' . intval($language);
+                       $whereClause .= ' AND t3ver_wsid = ' . intval($GLOBALS['BE_USER']->workspace);
+                       $whereClause .= \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause('pages_language_overlay');
+                       $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('t3ver_state', 'pages_language_overlay', $whereClause);
+                       if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
+                               $isNewPage = (int) $row['t3ver_state'] === 1;
+                       }
+               } else {
+                       $rec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('pages', $id, 't3ver_state');
+                       if (is_array($rec)) {
+                               $isNewPage = (int) $rec['t3ver_state'] === 1;
+                       }
+               }
+               return $isNewPage;
+       }
+
+       /**
+        * Generates a view link for a page.
+        *
+        * @static
+        * @param string $table Table to be used
+        * @param integer $uid Uid of the version(!) record
+        * @param array $liveRecord Optional live record data
+        * @param array $versionRecord Optional version record data
+        * @return string
+        */
+       static public function viewSingleRecord($table, $uid, array $liveRecord = NULL, array $versionRecord = NULL) {
+               $viewUrl = '';
+
+               if ($table == 'pages') {
+                       $viewUrl = \TYPO3\CMS\Backend\Utility\BackendUtility::viewOnClick(\TYPO3\CMS\Backend\Utility\BackendUtility::getLiveVersionIdOfRecord('pages', $uid));
+               } elseif ($table === 'pages_language_overlay' || $table === 'tt_content') {
+                       if ($liveRecord === NULL) {
+                               $liveRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getLiveVersionOfRecord($table, $uid);
+                       }
+                       if ($versionRecord === NULL) {
+                               $versionRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $uid);
+                       }
+
+                       $additionalParameters = '';
+                       $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
+                       if ($versionRecord[$languageField] > 0) {
+                               $additionalParameters .= '&L=' . $versionRecord[$languageField];
+                       }
+
+                       $viewUrl = \TYPO3\CMS\Backend\Utility\BackendUtility::viewOnClick($liveRecord['pid'], '', '', '', '', $additionalParameters);
+               } else {
+                       if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'])) {
+                               $_params = array(
+                                       'table' => $table,
+                                       'uid' => $uid,
+                                       'record' => $liveRecord,
+                                       'liveRecord' => $liveRecord,
+                                       'versionRecord' => $versionRecord,
+                               );
+                               $_funcRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'];
+                               $null = NULL;
+                               $viewUrl = \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($_funcRef, $_params, $null);
+                       }
+               }
+
+               return $viewUrl;
+       }
+
+       /**
+        * Determine whether this page for the current
+        *
+        * @param integer $pageUid
+        * @param integer $workspaceUid
+        * @return boolean
+        */
+       public function canCreatePreviewLink($pageUid, $workspaceUid) {
+               $result = TRUE;
+               if ($pageUid > 0 && $workspaceUid > 0) {
+                       $pageRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('pages', $pageUid);
+                       \TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL('pages', $pageRecord, $workspaceUid);
+                       if (!\TYPO3\CMS\Core\Utility\GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['FE']['content_doktypes'], $pageRecord['doktype'])) {
+                               $result = FALSE;
+                       }
+               } else {
+                       $result = FALSE;
+               }
+               return $result;
+       }
+
+       /**
+        * Generates a workspace preview link.
+        *
+        * @param integer $uid The ID of the record to be linked
+        * @return string the full domain including the protocol http:// or https://, but without the trailing '/'
+        */
+       public function generateWorkspacePreviewLink($uid) {
+               $previewObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Version\\Hook\\PreviewHook');
+               $timeToLiveHours = $previewObject->getPreviewLinkLifetime();
+               $previewKeyword = $previewObject->compilePreviewKeyword('', $GLOBALS['BE_USER']->user['uid'], $timeToLiveHours * 3600, $this->getCurrentWorkspace());
+               $linkParams = array(
+                       'ADMCMD_prev' => $previewKeyword,
+                       'id' => $uid
+               );
+               return \TYPO3\CMS\Backend\Utility\BackendUtility::getViewDomain($uid) . '/index.php?' . \TYPO3\CMS\Core\Utility\GeneralUtility::implodeArrayForUrl('', $linkParams);
+       }
+
+       /**
+        * Generates a workspace splitted preview link.
+        *
+        * @param integer $uid The ID of the record to be linked
+        * @param boolean $addDomain Parameter to decide if domain should be added to the generated link, FALSE per default
+        * @return string the preview link without the trailing '/'
+        */
+       public function generateWorkspaceSplittedPreviewLink($uid, $addDomain = FALSE) {
+               // In case a $pageUid is submitted we need to make sure it points to a live-page
+               if ($uid > 0) {
+                       $uid = $this->getLivePageUid($uid);
+               }
+               $objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManagerException');
+               /** @var $uriBuilder \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder */
+               $uriBuilder = $objectManager->create('TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder');
+               // This seems to be very harsh to set this directly to "/typo3 but the viewOnClick also
+               // has /index.php as fixed value here and dealing with the backPath is very error-prone
+               // @todo make sure this would work in local extension installation too
+               $backPath = '/' . TYPO3_mainDir;
+               $redirect = $backPath . 'index.php?redirect_url=';
+               // @todo why do we need these additional params? the URIBuilder should add the controller, but he doesn't :(
+               $additionalParams = '&tx_workspaces_web_workspacesworkspaces%5Bcontroller%5D=Preview&M=web_WorkspacesWorkspaces&id=';
+               $viewScript = $backPath . $uriBuilder->setArguments(array('tx_workspaces_web_workspacesworkspaces' => array('previewWS' => $GLOBALS['BE_USER']->workspace)))->uriFor('index', array(), 'TYPO3\\CMS\\Workspaces\\Controller\\PreviewController', 'workspaces', 'web_workspacesworkspaces') . $additionalParams;
+               if ($addDomain === TRUE) {
+                       return \TYPO3\CMS\Backend\Utility\BackendUtility::getViewDomain($uid) . $redirect . urlencode($viewScript) . $uid;
+               } else {
+                       return $viewScript;
+               }
+       }
+
+       /**
+        * Find the Live-Uid for a given page,
+        * the results are cached at run-time to avoid too many database-queries
+        *
+        * @throws \InvalidArgumentException
+        * @param integer $uid
+        * @return integer
+        */
+       public function getLivePageUid($uid) {
+               if (!isset($this->pageCache[$uid])) {
+                       $pageRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('pages', $uid);
+                       if (is_array($pageRecord)) {
+                               $this->pageCache[$uid] = $pageRecord['t3ver_oid'] ? $pageRecord['t3ver_oid'] : $uid;
+                       } else {
+                               throw new \InvalidArgumentException('uid is supposed to point to an existing page - given value was:' . $uid, 1290628113);
+                       }
+               }
+               return $this->pageCache[$uid];
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Task/AutoPublishTask.php b/typo3/sysext/workspaces/Classes/Task/AutoPublishTask.php
new file mode 100644 (file)
index 0000000..1f8deb1
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Task;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * This class provides a wrapper around the autopublication
+ * mechanism of workspaces, as a Scheduler task
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ */
+class AutoPublishTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask {
+
+       /**
+        * Method executed from the Scheduler.
+        * Call on the workspace logic to publish workspaces whose publication date
+        * is in the past
+        *
+        * @return      boolean
+        */
+       public function execute() {
+               $autopubObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\AutoPublishService');
+               // Publish the workspaces that need to be
+               $autopubObj->autoPublishWorkspaces();
+               // There's no feedback from the publishing process,
+               // so there can't be any failure.
+               // TODO: This could certainly be improved.
+               return TRUE;
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Task/CleanupPreviewLinkTask.php b/typo3/sysext/workspaces/Classes/Task/CleanupPreviewLinkTask.php
new file mode 100644 (file)
index 0000000..540788c
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Task;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2013 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * This class provides a task to cleanup ol preview links.
+ *
+ * @author Timo Webler <timo.webler@dkd.de>
+ */
+class CleanupPreviewLinkTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask {
+
+       /**
+        * Cleanup old preview links.
+        * endtime < $GLOBALS['EXEC_TIME']
+        *
+        * @return      boolean
+        */
+       public function execute() {
+               $GLOBALS['TYPO3_DB']->exec_DELETEquery('sys_preview', 'endtime < ' . intval($GLOBALS['EXEC_TIME']));
+               return TRUE;
+       }
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Configuration/TCA/sys_workspace.php b/typo3/sysext/workspaces/Configuration/TCA/sys_workspace.php
new file mode 100644 (file)
index 0000000..5aa6df3
--- /dev/null
@@ -0,0 +1,278 @@
+<?php
+return array(
+       'ctrl' => array(
+               'label' => 'title',
+               'tstamp' => 'tstamp',
+               'title' => 'LLL:EXT:lang/locallang_tca.xml:sys_workspace',
+               'adminOnly' => 1,
+               'rootLevel' => 1,
+               'delete' => 'deleted',
+               'iconfile' => 'sys_workspace.png',
+               'typeicon_classes' => array(
+                       'default' => 'mimetypes-x-sys_workspace'
+               ),
+               'versioningWS_alwaysAllowLiveEdit' => TRUE,
+               'dividers2tabs' => TRUE
+       ),
+       'columns' => array(
+               'title' => array(
+                       'label' => 'LLL:EXT:lang/locallang_general.xml:LGL.title',
+                       'config' => array(
+                               'type' => 'input',
+                               'size' => '20',
+                               'max' => '30',
+                               'eval' => 'required,trim,unique'
+                       )
+               ),
+               'description' => array(
+                       'label' => 'LLL:EXT:lang/locallang_general.xml:LGL.description',
+                       'config' => array(
+                               'type' => 'text',
+                               'rows' => 5,
+                               'cols' => 30
+                       )
+               ),
+               'adminusers' => array(
+                       'label' => 'LLL:EXT:lang/locallang_tca.xml:sys_workspace.adminusers',
+                       'config' => array(
+                               'type' => 'group',
+                               'internal_type' => 'db',
+                               'allowed' => 'be_users,be_groups',
+                               'prepend_tname' => 1,
+                               'size' => '3',
+                               'maxitems' => '10',
+                               'autoSizeMax' => 10,
+                               'show_thumbs' => '1',
+                               'wizards' => array(
+                                       'suggest' => array(
+                                               'type' => 'suggest'
+                                       )
+                               )
+                       )
+               ),
+               'members' => array(
+                       'label' => 'LLL:EXT:lang/locallang_tca.xml:sys_workspace.members',
+                       'config' => array(
+                               'type' => 'group',
+                               'internal_type' => 'db',
+                               'allowed' => 'be_users,be_groups',
+                               'prepend_tname' => 1,
+                               'size' => '3',
+                               'maxitems' => '100',
+                               'autoSizeMax' => 10,
+                               'show_thumbs' => '1',
+                               'wizards' => array(
+                                       'suggest' => array(
+                                               'type' => 'suggest'
+                                       )
+                               )
+                       )
+               ),
+               'db_mountpoints' => array(
+                       'label' => 'LLL:EXT:lang/locallang_tca.xml:db_mountpoints',
+                       'config' => array(
+                               'type' => 'group',
+                               'internal_type' => 'db',
+                               'allowed' => 'pages',
+                               'size' => '3',
+                               'maxitems' => 25,
+                               'autoSizeMax' => 10,
+                               'show_thumbs' => '1',
+                               'wizards' => array(
+                                       'suggest' => array(
+                                               'type' => 'suggest'
+                                       )
+                               )
+                       )
+               ),
+               'file_mountpoints' => array(
+                       'label' => 'LLL:EXT:lang/locallang_tca.xml:file_mountpoints',
+                       'config' => array(
+                               'type' => 'select',
+                               'foreign_table' => 'sys_filemounts',
+                               'foreign_table_where' => ' AND sys_filemounts.pid=0 ORDER BY sys_filemounts.title',
+                               'size' => '3',
+                               'maxitems' => 25,
+                               'autoSizeMax' => 10,
+                               'renderMode' => $GLOBALS['TYPO3_CONF_VARS']['BE']['accessListRenderMode'],
+                               'iconsInOptionTags' => 1
+                       )
+               ),
+               'publish_time' => array(
+                       'label' => 'LLL:EXT:lang/locallang_tca.xml:sys_workspace.publish_time',
+                       'config' => array(
+                               'type' => 'input',
+                               'size' => '8',
+                               'max' => '20',
+                               'eval' => 'datetime',
+                               'default' => '0',
+                               'checkbox' => '0'
+                       )
+               ),
+               'unpublish_time' => array(
+                       'label' => 'LLL:EXT:lang/locallang_tca.xml:sys_workspace.unpublish_time',
+                       'config' => array(
+                               'type' => 'input',
+                               'size' => '8',
+                               'max' => '20',
+                               'eval' => 'datetime',
+                               'checkbox' => '0',
+                               'default' => '0',
+                               'range' => array(
+                                       'upper' => mktime(0, 0, 0, 12, 31, 2020)
+                               )
+                       ),
+                       'displayCond' => 'FALSE'
+               ),
+               'freeze' => array(
+                       'label' => 'LLL:EXT:lang/locallang_tca.xml:sys_workspace.freeze',
+                       'config' => array(
+                               'type' => 'check',
+                               'default' => '0'
+                       )
+               ),
+               'live_edit' => array(
+                       'label' => 'LLL:EXT:lang/locallang_tca.xml:sys_workspace.live_edit',
+                       'config' => array(
+                               'type' => 'check',
+                               'default' => '0'
+                       )
+               ),
+               'disable_autocreate' => array(
+                       'label' => 'LLL:EXT:lang/locallang_tca.xml:sys_workspace.disable_autocreate',
+                       'config' => array(
+