diff --git a/.gitmodules b/.gitmodules index 160fa161229ba825ed3e88a57b57c2951d8b1e55..685b1474df54233493add00bff0e9e4b21d1cc3d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 index 5c17d038410a779493d2f5cf05b79dd62d616393..0000000000000000000000000000000000000000 --- a/typo3/sysext/workspaces +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5c17d038410a779493d2f5cf05b79dd62d616393 diff --git a/typo3/sysext/workspaces/ChangeLog b/typo3/sysext/workspaces/ChangeLog new file mode 100644 index 0000000000000000000000000000000000000000..2eed64b76553e84035a97fd533503a9e9ce32a3c --- /dev/null +++ b/typo3/sysext/workspaces/ChangeLog @@ -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 index 0000000000000000000000000000000000000000..6e5ac75ac81bde9ca38f68d59633bbc82f4a2a44 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Controller/AbstractController.php @@ -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 index 0000000000000000000000000000000000000000..aaeadd2641a5bfaea0a94771b905c7c1f3558c18 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Controller/PreviewController.php @@ -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 index 0000000000000000000000000000000000000000..adbac300d9b331c87e80b887be6c632a4c4f8255 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Controller/ReviewController.php @@ -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 index 0000000000000000000000000000000000000000..deea8d1e9dc2d308952f8ed29a7604d83384ac4b --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Domain/Model/CombinedRecord.php @@ -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 index 0000000000000000000000000000000000000000..ae7227d266893fa76037eabcae0a45d7202de460 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Domain/Model/DatabaseRecord.php @@ -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 index 0000000000000000000000000000000000000000..4e9c122b9f2508c6fd93928b2e89b4ed8f4abbf9 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/ExtDirect/AbstractHandler.php @@ -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 index 0000000000000000000000000000000000000000..49dc49675412023e425c95a254cb29017d986201 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php @@ -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 index 0000000000000000000000000000000000000000..523661ded81c97e75082781299422f572b2b6c68 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/ExtDirect/ExtDirectServer.php @@ -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 index 0000000000000000000000000000000000000000..0d2b40348de512579207f0b4b53948d6bfb6026a --- /dev/null +++ b/typo3/sysext/workspaces/Classes/ExtDirect/MassActionHandler.php @@ -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 index 0000000000000000000000000000000000000000..523c2527dfd84dcbb6a9199ca6e8a474fb493c3c --- /dev/null +++ b/typo3/sysext/workspaces/Classes/ExtDirect/PagetreeCollectionsProcessor.php @@ -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 index 0000000000000000000000000000000000000000..abba483616d877a0da3ccc252a45a88ec0c97d83 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/ExtDirect/ToolbarMenu.php @@ -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 index 0000000000000000000000000000000000000000..a8c3badf10b95653223356a1b15076953698ce5e --- /dev/null +++ b/typo3/sysext/workspaces/Classes/ExtDirect/WorkspaceSelectorToolbarItem.php @@ -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 index 0000000000000000000000000000000000000000..b832454a022388dcdd6a1eb96a07a041630bdf93 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Hook/BackendUtilityHook.php @@ -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 index 0000000000000000000000000000000000000000..19b095b30e9d038cf2d5cfdf31ba686995e2cc25 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php @@ -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 index 0000000000000000000000000000000000000000..25a5e31766c5081bd7d20a9f82f9ebbc53510fc1 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Hook/TypoScriptFrontendControllerHook.php @@ -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 index 0000000000000000000000000000000000000000..c4f0664ba52eba82a9b878d07c3e15a27746be38 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Service/AutoPublishService.php @@ -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 index 0000000000000000000000000000000000000000..8eb7a26a1260d3ea4dd720569916868a45f5fda1 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Service/GridDataService.php @@ -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 index 0000000000000000000000000000000000000000..9cd3ed69bc5de9d0eedf1789d1daff2b14ee8b93 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Service/HistoryService.php @@ -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 index 0000000000000000000000000000000000000000..74d2c903349e93081c482879292e10fb2bfe7a7c --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Service/IntegrityService.php @@ -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 index 0000000000000000000000000000000000000000..966d31805a23aa6f078bdd54e1fe24b1849cfe59 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Service/StagesService.php @@ -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 index 0000000000000000000000000000000000000000..2c8784a377ba0602e4d26426b814254601527ccc --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Service/WorkspaceService.php @@ -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 index 0000000000000000000000000000000000000000..1f8deb1293da15ac8e39f92596f829e90775463b --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Task/AutoPublishTask.php @@ -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 index 0000000000000000000000000000000000000000..540788c64bf43c63ef9fc6d878a5d744c69ea741 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Task/CleanupPreviewLinkTask.php @@ -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 index 0000000000000000000000000000000000000000..5aa6df3a76526a48ec33e759664e22c7fce56717 --- /dev/null +++ b/typo3/sysext/workspaces/Configuration/TCA/sys_workspace.php @@ -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( + 'type' => 'check', + 'default' => '0' + ) + ), + 'swap_modes' => array( + 'label' => 'LLL:EXT:lang/locallang_tca.xml:sys_workspace.swap_modes', + 'config' => array( + 'type' => 'select', + 'items' => array( + array('', 0), + array('Swap-Into-Workspace on Auto-publish', 1), + array('Disable Swap-Into-Workspace', 2) + ) + ) + ), + 'publish_access' => array( + 'label' => 'LLL:EXT:lang/locallang_tca.xml:sys_workspace.publish_access', + 'config' => array( + 'type' => 'check', + 'items' => array( + array('Publish only content in publish stage', 0), + array('Only workspace owner can publish', 0) + ) + ) + ), + 'stagechg_notification' => array( + 'label' => 'LLL:EXT:lang/locallang_tca.xml:sys_workspace.stagechg_notification', + 'config' => array( + 'type' => 'select', + 'items' => array( + array('', 0), + array('Notify users on next stage only', 1), + array('Notify all users on any change', 10) + ) + ) + ), + 'custom_stages' => array( + 'exclude' => 1, + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.custom_stages', + 'config' => array( + 'type' => 'inline', + 'foreign_table' => 'sys_workspace_stage', + 'appearance' => 'useSortable,expandSingle', + 'foreign_field' => 'parentid', + 'foreign_table_field' => 'parenttable', + 'minitems' => 0 + ), + 'default' => 0 + ), + 'edit_notification_mode' => array( + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.edit_notification_mode', + 'config' => array( + 'type' => 'select', + 'items' => array( + array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.notification_mode.0', 0), + array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.notification_mode.1', 1), + array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.notification_mode.2', 2) + ) + ) + ), + 'edit_notification_defaults' => array( + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.edit_notification_defaults', + 'displayCond' => 'FIELD:edit_notification_mode:IN:0,1', + 'config' => array( + 'type' => 'group', + 'internal_type' => 'db', + 'allowed' => 'be_users,be_groups', + 'prepend_tname' => 1, + 'size' => '3', + 'maxitems' => '100', + 'autoSizeMax' => 20, + 'show_thumbs' => '1', + 'wizards' => array( + 'suggest' => array( + 'type' => 'suggest' + ) + ) + ) + ), + 'edit_allow_notificaton_settings' => array( + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.edit_allow_notificaton_settings', + 'config' => array( + 'type' => 'check', + 'default' => 1 + ) + ), + 'publish_notification_mode' => array( + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.publish_notification_mode', + 'config' => array( + 'type' => 'select', + 'items' => array( + array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.notification_mode.0', 0), + array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.notification_mode.1', 1), + array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.notification_mode.2', 2) + ) + ) + ), + 'publish_notification_defaults' => array( + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.publish_notification_defaults', + 'displayCond' => 'FIELD:publish_notification_mode:IN:0,1', + 'config' => array( + 'type' => 'group', + 'internal_type' => 'db', + 'allowed' => 'be_users,be_groups', + 'prepend_tname' => 1, + 'size' => '3', + 'maxitems' => '100', + 'autoSizeMax' => 20, + 'show_thumbs' => '1', + 'wizards' => array( + 'suggest' => array( + 'type' => 'suggest' + ) + ) + ) + ), + 'publish_allow_notificaton_settings' => array( + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.publish_allow_notificaton_settings', + 'config' => array( + 'type' => 'check', + 'default' => 1 + ) + ) + ), + 'types' => array( + '0' => array('showitem' => 'title,description, + --div--;LLL:EXT:lang/locallang_tca.xml:sys_filemounts.tabs.users,adminusers,members, + --div--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:tabs.notification_settings,stagechg_notification,edit_notification_mode,edit_notification_defaults,edit_allow_notificaton_settings,publish_notification_mode,publish_notification_defaults,publish_allow_notificaton_settings, + --div--;LLL:EXT:lang/locallang_tca.xml:sys_filemounts.tabs.mountpoints,db_mountpoints,file_mountpoints, + --div--;LLL:EXT:lang/locallang_tca.xml:sys_filemounts.tabs.publishing,publish_time,unpublish_time, + --div--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_filemounts.tabs.staging,custom_stages, + --div--;LLL:EXT:lang/locallang_tca.xml:sys_filemounts.tabs.other,freeze,live_edit,disable_autocreate,swap_modes,publish_access') + ) +); +?> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Configuration/TCA/sys_workspace_stage.php b/typo3/sysext/workspaces/Configuration/TCA/sys_workspace_stage.php new file mode 100644 index 0000000000000000000000000000000000000000..eec955383c79d2144d031f8c33baca63544189c0 --- /dev/null +++ b/typo3/sysext/workspaces/Configuration/TCA/sys_workspace_stage.php @@ -0,0 +1,113 @@ +<?php +return array( + 'ctrl' => array( + 'label' => 'title', + 'tstamp' => 'tstamp', + 'sortby' => 'sorting', + 'title' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace_stage', + 'adminOnly' => 1, + 'rootLevel' => 1, + 'hideTable' => TRUE, + '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' + ) + ), + 'responsible_persons' => array( + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace_stage.responsible_persons', + 'config' => array( + 'type' => 'group', + 'internal_type' => 'db', + 'allowed' => 'be_users,be_groups', + 'prepend_tname' => 1, + 'size' => '3', + 'maxitems' => '100', + 'autoSizeMax' => 20, + 'show_thumbs' => '1', + 'wizards' => array( + 'suggest' => array( + 'type' => 'suggest' + ) + ) + ) + ), + 'default_mailcomment' => array( + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace_stage.default_mailcomment', + 'config' => array( + 'type' => 'text', + 'rows' => 5, + 'cols' => 30 + ) + ), + 'parentid' => array( + 'exclude' => 0, + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace_stage.parentid', + 'config' => array( + 'type' => 'passthrough' + ) + ), + 'parenttable' => array( + 'exclude' => 0, + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace_stage.parenttable', + 'config' => array( + 'type' => 'passthrough' + ) + ), + 'notification_mode' => array( + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace_stage.notification_mode', + 'config' => array( + 'type' => 'select', + 'items' => array( + array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.notification_mode.0', 0), + array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.notification_mode.1', 1), + array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.notification_mode.2', 2) + ) + ) + ), + 'notification_defaults' => array( + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace_stage.notification_defaults', + 'displayCond' => 'FIELD:notification_mode:IN:0,1', + 'config' => array( + 'type' => 'group', + 'internal_type' => 'db', + 'allowed' => 'be_users,be_groups', + 'prepend_tname' => 1, + 'size' => '3', + 'maxitems' => '100', + 'autoSizeMax' => 20, + 'show_thumbs' => '1', + 'wizards' => array( + 'suggest' => array( + 'type' => 'suggest' + ) + ) + ) + ), + 'allow_notificaton_settings' => array( + 'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace_stage.allow_notificaton_settings', + 'config' => array( + 'type' => 'check', + 'default' => 1 + ) + ) + ), + 'types' => array( + '0' => array('showitem' => ' + --div--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:tabs.general,title,responsible_persons, + --div--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:tabs.notification_settings,notification_mode,notification_defaults,allow_notificaton_settings,default_mailcomment') + ) +); +?> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Documentation/manual.odt b/typo3/sysext/workspaces/Documentation/manual.odt new file mode 100644 index 0000000000000000000000000000000000000000..d1e9251c4dafa8c7e398dca3944608674ece1994 Binary files /dev/null and b/typo3/sysext/workspaces/Documentation/manual.odt differ diff --git a/typo3/sysext/workspaces/Documentation/manual.pdf b/typo3/sysext/workspaces/Documentation/manual.pdf new file mode 100644 index 0000000000000000000000000000000000000000..af7c378cfebc7d0dbf5070363f283d9e46244125 Binary files /dev/null and b/typo3/sysext/workspaces/Documentation/manual.pdf differ diff --git a/typo3/sysext/workspaces/Documentation/manual.sxw b/typo3/sysext/workspaces/Documentation/manual.sxw new file mode 100644 index 0000000000000000000000000000000000000000..8cb88e19aad4ebbe3b21a93803a9a9b4b6b4bd15 Binary files /dev/null and b/typo3/sysext/workspaces/Documentation/manual.sxw differ diff --git a/typo3/sysext/workspaces/Migrations/Code/ClassAliasMap.php b/typo3/sysext/workspaces/Migrations/Code/ClassAliasMap.php new file mode 100644 index 0000000000000000000000000000000000000000..d2f3e7cebd8b8fb6491609704c50c656f59aa350 --- /dev/null +++ b/typo3/sysext/workspaces/Migrations/Code/ClassAliasMap.php @@ -0,0 +1,27 @@ +<?php +return array( + 'Tx_Workspaces_Controller_AbstractController' => 'TYPO3\\CMS\\Workspaces\\Controller\\AbstractController', + 'Tx_Workspaces_Controller_PreviewController' => 'TYPO3\\CMS\\Workspaces\\Controller\\PreviewController', + 'Tx_Workspaces_Controller_ReviewController' => 'TYPO3\\CMS\\Workspaces\\Controller\\ReviewController', + 'Tx_Workspaces_Domain_Model_CombinedRecord' => 'TYPO3\\CMS\\Workspaces\\Domain\\Model\\CombinedRecord', + 'Tx_Workspaces_Domain_Model_DatabaseRecord' => 'TYPO3\\CMS\\Workspaces\\Domain\\Model\\DatabaseRecord', + 'Tx_Workspaces_ExtDirect_AbstractHandler' => 'TYPO3\\CMS\\Workspaces\\ExtDirect\\AbstractHandler', + 'Tx_Workspaces_ExtDirect_ActionHandler' => 'TYPO3\\CMS\\Workspaces\\ExtDirect\\ActionHandler', + 'Tx_Workspaces_ExtDirect_Server' => 'TYPO3\\CMS\\Workspaces\\ExtDirect\\ExtDirectServer', + 'Tx_Workspaces_ExtDirect_MassActionHandler' => 'TYPO3\\CMS\\Workspaces\\ExtDirect\\MassActionHandler', + 'Tx_Workspaces_ExtDirect_PagetreeCollectionsProcessor' => 'TYPO3\\CMS\\Workspaces\\ExtDirect\\PagetreeCollectionsProcessor', + 'Tx_Workspaces_ExtDirect_ToolbarMenu' => 'TYPO3\\CMS\\Workspaces\\ExtDirect\\ToolbarMenu', + 'Tx_Workspaces_ExtDirect_WorkspaceSelectorToolbarItem' => 'TYPO3\\CMS\\Workspaces\\ExtDirect\\WorkspaceSelectorToolbarItem', + 'Tx_Workspaces_Service_Befunc' => 'TYPO3\\CMS\\Workspaces\\Hook\\BackendUtilityHook', + 'Tx_Workspaces_Service_Tcemain' => 'TYPO3\\CMS\\Workspaces\\Hook\\DataHandlerHook', + 'Tx_Workspaces_Service_Fehooks' => 'TYPO3\\CMS\\Workspaces\\Hook\\TypoScriptFrontendControllerHook', + 'Tx_Workspaces_Service_AutoPublish' => 'TYPO3\\CMS\\Workspaces\\Service\\AutoPublishService', + 'Tx_Workspaces_Service_GridData' => 'TYPO3\\CMS\\Workspaces\\Service\\GridDataService', + 'Tx_Workspaces_Service_History' => 'TYPO3\\CMS\\Workspaces\\Service\\HistoryService', + 'Tx_Workspaces_Service_Integrity' => 'TYPO3\\CMS\\Workspaces\\Service\\IntegrityService', + 'Tx_Workspaces_Service_Stages' => 'TYPO3\\CMS\\Workspaces\\Service\\StagesService', + 'Tx_Workspaces_Service_Workspaces' => 'TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService', + 'Tx_Workspaces_Service_AutoPublishTask' => 'TYPO3\\CMS\\Workspaces\\Task\\AutoPublishTask', + 'Tx_Workspaces_Service_CleanupPreviewLinkTask' => 'TYPO3\\CMS\\Workspaces\\Task\\CleanupPreviewLinkTask', +); +?> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Private/Language/locallang.xlf b/typo3/sysext/workspaces/Resources/Private/Language/locallang.xlf new file mode 100644 index 0000000000000000000000000000000000000000..48f3fd86250152a40ee288bb91217ced44c5cace --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Language/locallang.xlf @@ -0,0 +1,311 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xliff version="1.0"> + <file source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:37Z" product-name="workspaces"> + <header/> + <body> + <trans-unit id="legend.label" xml:space="preserve"> + <source>Legend:</source> + </trans-unit> + <trans-unit id="legend.edited" xml:space="preserve"> + <source>edited</source> + </trans-unit> + <trans-unit id="legend.moved" xml:space="preserve"> + <source>moved</source> + </trans-unit> + <trans-unit id="legend.deleted" xml:space="preserve"> + <source>deleted</source> + </trans-unit> + <trans-unit id="legend.new" xml:space="preserve"> + <source>created</source> + </trans-unit> + <trans-unit id="legend.hidden" xml:space="preserve"> + <source>hidden</source> + </trans-unit> + <trans-unit id="title" xml:space="preserve"> + <source>Workspaces</source> + </trans-unit> + <trans-unit id="ok" xml:space="preserve"> + <source>ok</source> + </trans-unit> + <trans-unit id="cancel" xml:space="preserve"> + <source>cancel</source> + </trans-unit> + <trans-unit id="chooseMassAction" xml:space="preserve"> + <source>choose Mass Action</source> + </trans-unit> + <trans-unit id="chooseAction" xml:space="preserve"> + <source>choose Action</source> + </trans-unit> + <trans-unit id="item" xml:space="preserve"> + <source>item</source> + </trans-unit> + <trans-unit id="items" xml:space="preserve"> + <source>items</source> + </trans-unit> + <trans-unit id="recordsToDisplay" xml:space="preserve"> + <source>Records to display</source> + </trans-unit> + <trans-unit id="reviewandpublish" xml:space="preserve"> + <source>review and publish</source> + </trans-unit> + <trans-unit id="workspacelist" xml:space="preserve"> + <source>Workspace List</source> + </trans-unit> + <trans-unit id="editorInLive" xml:space="preserve"> + <source>You are in LIVE workspace. You have to select a draft workspace before you can review and + publish changes. + </source> + </trans-unit> + <trans-unit id="actionSendToStage" xml:space="preserve"> + <source>Send to stage</source> + </trans-unit> + <trans-unit id="publish_execute_action_option" xml:space="preserve"> + <source>Publish to LIVE</source> + </trans-unit> + <trans-unit id="close" xml:space="preserve"> + <source>Close</source> + </trans-unit> + <trans-unit id="runMassAction.done" xml:space="preserve"> + <source>Done processing %d elements</source> + </trans-unit> + <trans-unit id="runMassAction.init" xml:space="preserve"> + <source>init</source> + </trans-unit> + <trans-unit id="tooltip.publishAll" xml:space="preserve"> + <source>Really publish entire workspace?</source> + </trans-unit> + <trans-unit id="tooltip.swapAll" xml:space="preserve"> + <source>Really swap entire workspace?</source> + </trans-unit> + <trans-unit id="tooltip.discardAll" xml:space="preserve"> + <source>Do you really want to discard changes from the whole workspace?</source> + </trans-unit> + <trans-unit id="tooltip.affectWholeWorkspace" xml:space="preserve"> + <source>Please note that this will affect all changes in the current workspace, which may be more than + you are currently seeing on your screen. + </source> + </trans-unit> + <trans-unit id="tooltip.generatePagePreview" xml:space="preserve"> + <source>Generate page preview</source> + </trans-unit> + <trans-unit id="previewLink" xml:space="preserve"> + <source>Preview Link</source> + </trans-unit> + <trans-unit id="error.noResponse" xml:space="preserve"> + <source>The server did not send any response whether the action was successful.</source> + </trans-unit> + <trans-unit id="rowDetails" xml:space="preserve"> + <source>Row details...</source> + </trans-unit> + <trans-unit id="column.difference" xml:space="preserve"> + <source>Difference</source> + </trans-unit> + <trans-unit id="column.changeDate" xml:space="preserve"> + <source>Modification</source> + </trans-unit> + <trans-unit id="column.stage" xml:space="preserve"> + <source>Current Stage</source> + </trans-unit> + <trans-unit id="column.actions" xml:space="preserve"> + <source>Actions</source> + </trans-unit> + <trans-unit id="column.wsPath" xml:space="preserve"> + <source>Path</source> + </trans-unit> + <trans-unit id="column.wsTitle" xml:space="preserve"> + <source>Changed</source> + </trans-unit> + <trans-unit id="column.uid" xml:space="preserve"> + <source>WS-Id</source> + </trans-unit> + <trans-unit id="column.oid" xml:space="preserve"> + <source>Live-Id</source> + </trans-unit> + <trans-unit id="column.workspaceName" xml:space="preserve"> + <source>Workspace</source> + </trans-unit> + <trans-unit id="column.livePath" xml:space="preserve"> + <source>Live-Path</source> + </trans-unit> + <trans-unit id="column.liveTitle" xml:space="preserve"> + <source>Live-Title</source> + </trans-unit> + <trans-unit id="column.wsSwapColumn" xml:space="preserve"> + <source>Swap workspace</source> + </trans-unit> + <trans-unit id="tooltip.viewElementAction" xml:space="preserve"> + <source>Preview element</source> + </trans-unit> + <trans-unit id="tooltip.editElementAction" xml:space="preserve"> + <source>Edit element</source> + </trans-unit> + <trans-unit id="tooltip.openPage" xml:space="preserve"> + <source>Open version of page</source> + </trans-unit> + <trans-unit id="tooltip.sendToPrevStage" xml:space="preserve"> + <source>Send record to previous Stage</source> + </trans-unit> + <trans-unit id="tooltip.sendToNextStage" xml:space="preserve"> + <source>Send record to next Stage</source> + </trans-unit> + <trans-unit id="tooltip.discardVersion" xml:space="preserve"> + <source>Discard workspace version of record.</source> + </trans-unit> + <trans-unit id="tooltip.swap" xml:space="preserve"> + <source>Swap live and workspace versions of record</source> + </trans-unit> + <trans-unit id="tooltip.showHistory" xml:space="preserve"> + <source>Show history of workspace version</source> + </trans-unit> + <trans-unit id="window.discard.title" xml:space="preserve"> + <source>Discard workspace version of record.</source> + </trans-unit> + <trans-unit id="window.discardAll.title" xml:space="preserve"> + <source>Discard all workspace version of current page.</source> + </trans-unit> + <trans-unit id="window.discard.message" xml:space="preserve"> + <source>Do you really want to discard this version from workspace?</source> + </trans-unit> + <trans-unit id="window.discardAll.message" xml:space="preserve"> + <source>Do you really want to discard all versions from page?</source> + </trans-unit> + <trans-unit id="window.swap.title" xml:space="preserve"> + <source>Swap version</source> + </trans-unit> + <trans-unit id="window.swap.message" xml:space="preserve"> + <source>Do you really want to swap this version?</source> + </trans-unit> + <trans-unit id="window.massAction.title" xml:space="preserve"> + <source>Prepare to start mass action</source> + </trans-unit> + <trans-unit id="error.stageId.integer" xml:space="preserve"> + <source>StageId is supposed to be an integer</source> + </trans-unit> + <trans-unit id="error.stageId.invalid" xml:space="preserve"> + <source>The stage to be used seems to be invalid and cannot be used. Please consider to use the update + wizard in the install tool to fix this. + </source> + </trans-unit> + <trans-unit id="error.sendToNextStage.noRecordFound" xml:space="preserve"> + <source>The record element could not be found.</source> + </trans-unit> + <trans-unit id="error.sendToPrevStage.noPreviousStage" xml:space="preserve"> + <source>The record element is already in editing stage, there is no previous stage.</source> + </trans-unit> + <trans-unit id="window.sendToNextStageWindow.itemsWillBeSentTo" xml:space="preserve"> + <source>The selected element(s) will be sent to</source> + </trans-unit> + <trans-unit id="window.sendToNextStageWindow.sendMailTo" xml:space="preserve"> + <source>Send mail to</source> + </trans-unit> + <trans-unit id="window.sendToNextStageWindow.additionalRecipients" xml:space="preserve"> + <source>Additional recipients</source> + </trans-unit> + <trans-unit id="window.sendToNextStageWindow.comments" xml:space="preserve"> + <source>Comments</source> + </trans-unit> + <trans-unit id="error.getStageTitle.stageNotFound" xml:space="preserve"> + <source>Stage not found</source> + </trans-unit> + <trans-unit id="warning.oldStyleWorkspaceInUser" xml:space="preserve"> + <source>It seems that you're still using old-style workspace. Please use the update scripts in the TYPO3 + Install Tool to run the necessary updates. If you continue using this module without migrating your + workspace you might loose data. + </source> + </trans-unit> + <trans-unit id="info.newpage" xml:space="preserve"> + <source>New Page!</source> + </trans-unit> + <trans-unit id="info.newpage.detail" xml:space="preserve"> + <source>The previewed page has been created in a workspace and has no live counterpart.</source> + </trans-unit> + <trans-unit id="preview.visualPreview" xml:space="preserve"> + <source>Visual preview</source> + </trans-unit> + <trans-unit id="preview.listView" xml:space="preserve"> + <source>List view</source> + </trans-unit> + <trans-unit id="preview.livePreview" xml:space="preserve"> + <source>Live</source> + </trans-unit> + <trans-unit id="preview.livePreviewDetail" xml:space="preserve"> + <source>Click this element to hide the workspace version and see the live version of the page.</source> + </trans-unit> + <trans-unit id="preview.workspacePreview" xml:space="preserve"> + <source>Workspace</source> + </trans-unit> + <trans-unit id="preview.workspacePreviewDetail" xml:space="preserve"> + <source>Click this element to hide the live version and see the workspace version of the page.</source> + </trans-unit> + <trans-unit id="preview.modeSlider" xml:space="preserve"> + <source>Slider</source> + </trans-unit> + <trans-unit id="preview.modeVbox" xml:space="preserve"> + <source>Vertical</source> + </trans-unit> + <trans-unit id="preview.modeHbox" xml:space="preserve"> + <source>Horizontal</source> + </trans-unit> + <trans-unit id="label_doaction_publish" xml:space="preserve"> + <source>Publish</source> + </trans-unit> + <trans-unit id="label_doaction_swap" xml:space="preserve"> + <source>Swap</source> + </trans-unit> + <trans-unit id="label_doaction_discard" xml:space="preserve"> + <source>Discard</source> + </trans-unit> + <trans-unit id="label_doaction_generatePreviewLink" xml:space="preserve"> + <source>Generate Workspace Preview Link</source> + </trans-unit> + <trans-unit id="info.elementAlreadyModified" xml:space="preserve"> + <source>Element is in workspace stage "%s", modifications will send it back to "%s".</source> + </trans-unit> + <trans-unit id="live_workspace" xml:space="preserve"> + <source>Live Workspace</source> + </trans-unit> + <trans-unit id="workspace_version" xml:space="preserve"> + <source>Workspace version</source> + </trans-unit> + <trans-unit id="current_step" xml:space="preserve"> + <source><![CDATA[<b>Current stage step</b>: {0} (<b>{1}</b>/{2})]]></source> + </trans-unit> + <trans-unit id="path" xml:space="preserve"> + <source><![CDATA[<b>Path</b>: {0}]]></source> + </trans-unit> + <trans-unit id="stage" xml:space="preserve"> + <source>Stage {0}</source> + </trans-unit> + <trans-unit id="comments" xml:space="preserve"> + <source><![CDATA[User comments for <b>step {0} of stage</b> "{1}"]]></source> + </trans-unit> + <trans-unit id="language.allLanguages" xml:space="preserve"> + <source>all languages</source> + </trans-unit> + <trans-unit id="language.selectLanguage" xml:space="preserve"> + <source>all languages</source> + </trans-unit> + <trans-unit id="integrity.dependsOnDefaultLanguageRecord" xml:space="preserve"> + <source><![CDATA[Record "%s" depends on default language record]]></source> + </trans-unit> + <trans-unit id="integrity.isDefaultLanguageRecord" xml:space="preserve"> + <source><![CDATA[Default language record for record "%s"]]></source> + </trans-unit> + <trans-unit id="integrity.hasIssuesDescription" xml:space="preserve"> + <source>The integrity check discovered several issues, see status icons infront of each record for further details.</source> + </trans-unit> + <trans-unit id="integrity.hasIssuesQuestion" xml:space="preserve"> + <source>Do you want to continue?</source> + </trans-unit> + <trans-unit id="status.info" xml:space="preserve"> + <source>Information</source> + </trans-unit> + <trans-unit id="status.warning" xml:space="preserve"> + <source>Warning</source> + </trans-unit> + <trans-unit id="status.error" xml:space="preserve"> + <source>Error</source> + </trans-unit> + </body> + </file> +</xliff> diff --git a/typo3/sysext/workspaces/Resources/Private/Language/locallang_csh_sysws_stage.xlf b/typo3/sysext/workspaces/Resources/Private/Language/locallang_csh_sysws_stage.xlf new file mode 100644 index 0000000000000000000000000000000000000000..607656c97b76120e00dac318f125404a03817822 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Language/locallang_csh_sysws_stage.xlf @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xliff version="1.0"> + <file source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:37Z" product-name="workspaces"> + <header/> + <body> + <trans-unit id=".description" xml:space="preserve"> + <source>Defines custom workspace stages in TYPO3 which allows for groups of people to work together in a defined process. More information about workspaces can be found in the document "Inside TYPO3".</source> + </trans-unit> + <trans-unit id="title.description" xml:space="preserve"> + <source>Enter the name of the workspace stage.</source> + </trans-unit> + <trans-unit id="responsible_persons.description" xml:space="preserve"> + <source>Define which BE users are responsible for this workspace stage. This selected BE users are possible recipients for the stage change emails.</source> + </trans-unit> + <trans-unit id="default_mailcomment.description" xml:space="preserve"> + <source>Here its possible to define a standard mail comment which will be inserted into the stage change mail. When no standard mail comment was defined here its possible to write a comment for the mail on every stage change.</source> + </trans-unit> + <trans-unit id="notification_mode.description" xml:space="preserve"> + <source>You can choose one of three settings for the recipient suggestion list on stage change. In the most configurations the default option is a good choice. For exact explanation about the single options please refer to the documentation.</source> + </trans-unit> + <trans-unit id="notification_defaults.description" xml:space="preserve"> + <source>Select these backend users or backend groups that should be selected per default to receive a notification mail. Select only users/groups that are also selected as responsible for this stage.</source> + </trans-unit> + <trans-unit id="allow_notificaton_settings.description" xml:space="preserve"> + <source>Disable this checkbox to hide the recipient and additional recipient fields from the stage change popup window. Notifications are sent in the background nevertheless but based on configuration in workspace stage setting.</source> + </trans-unit> + </body> + </file> +</xliff> diff --git a/typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xlf b/typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xlf new file mode 100644 index 0000000000000000000000000000000000000000..1c19118af038d516d77f396c55e38cc5ce43099c --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xlf @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xliff version="1.0"> + <file source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:37Z" product-name="workspaces"> + <header/> + <body> + <trans-unit id="sys_filemounts.tabs.staging" xml:space="preserve"> + <source>Staging</source> + </trans-unit> + <trans-unit id="sys_workspace.custom_stages" xml:space="preserve"> + <source>Custom stages:</source> + </trans-unit> + <trans-unit id="sys_workspace_stage.responsible_persons" xml:space="preserve"> + <source>Responsible persons:</source> + </trans-unit> + <trans-unit id="sys_workspace_stage.default_mailcomment" xml:space="preserve"> + <source>Default mail comment:</source> + </trans-unit> + <trans-unit id="sys_workspace_stage.parentid" xml:space="preserve"> + <source>UID of parent record:</source> + </trans-unit> + <trans-unit id="sys_workspace_stage.parenttable" xml:space="preserve"> + <source>Parent table:</source> + </trans-unit> + <trans-unit id="sys_workspace_stage" xml:space="preserve"> + <source>Workspace Stage</source> + </trans-unit> + <trans-unit id="tabs.notification_settings" xml:space="preserve"> + <source>Notification settings</source> + </trans-unit> + <trans-unit id="sys_workspace.edit_notification_mode" xml:space="preserve"> + <source>Edit stage: Recipient suggestion checkboxes</source> + </trans-unit> + <trans-unit id="sys_workspace.notification_mode.0" xml:space="preserve"> + <source>All are selected per default (can be changed)</source> + </trans-unit> + <trans-unit id="sys_workspace.notification_mode.1" xml:space="preserve"> + <source>Some are selected per default (defaults cannot be unchecked)</source> + </trans-unit> + <trans-unit id="sys_workspace.notification_mode.2" xml:space="preserve"> + <source>All are selected per default (cannot be changed)</source> + </trans-unit> + <trans-unit id="sys_workspace.edit_notification_defaults" xml:space="preserve"> + <source>Edit stage: default notification mail recipients</source> + </trans-unit> + <trans-unit id="sys_workspace.edit_allow_notificaton_settings" xml:space="preserve"> + <source>Allow notification settings during stage change back to edit stage</source> + </trans-unit> + <trans-unit id="sys_workspace.publish_notification_mode" xml:space="preserve"> + <source>Ready to publish stage: Recipient suggestion checkboxes</source> + </trans-unit> + <trans-unit id="sys_workspace.publish_notification_defaults" xml:space="preserve"> + <source>Ready to publish stage: default notification mail recipients</source> + </trans-unit> + <trans-unit id="sys_workspace.publish_allow_notificaton_settings" xml:space="preserve"> + <source>Allow notification settings during stage change to ready to publish</source> + </trans-unit> + <trans-unit id="tabs.general" xml:space="preserve"> + <source>General</source> + </trans-unit> + <trans-unit id="sys_workspace_stage.notification_mode" xml:space="preserve"> + <source>Recipient suggestion checkboxes</source> + </trans-unit> + <trans-unit id="sys_workspace_stage.notification_defaults" xml:space="preserve"> + <source>Default notification mail recipients</source> + </trans-unit> + <trans-unit id="sys_workspace_stage.allow_notificaton_settings" xml:space="preserve"> + <source>Allow notification settings during stage change</source> + </trans-unit> + </body> + </file> +</xliff> diff --git a/typo3/sysext/workspaces/Resources/Private/Language/locallang_mod.xlf b/typo3/sysext/workspaces/Resources/Private/Language/locallang_mod.xlf new file mode 100644 index 0000000000000000000000000000000000000000..a4f1425130676052943d62383935f855b8ceee37 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Language/locallang_mod.xlf @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xliff version="1.0"> + <file source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:38Z" product-name="workspaces"> + <header/> + <body> + <trans-unit id="mlang_tabs_tab" xml:space="preserve"> + <source>Workspaces</source> + </trans-unit> + <trans-unit id="mlang_labels_tabdescr" xml:space="preserve"> + <source>This module contains the overview of all elements within the current workspace and it enables to continue the review and publishing workflow for them.</source> + </trans-unit> + <trans-unit id="mlang_labels_tablabel" xml:space="preserve"> + <source>Create and handle versioning workflows</source> + </trans-unit> + <trans-unit id="autopublishTask.name" xml:space="preserve"> + <source>Workspaces auto-publication</source> + </trans-unit> + <trans-unit id="autopublishTask.description" xml:space="preserve"> + <source>This tasks checks any workspace that has a publication date set in the past and automatically publishes it.</source> + </trans-unit> + <trans-unit id="cleanupPreviewLinkTask.name" xml:space="preserve"> + <source>Workspaces cleanup preview links</source> + </trans-unit> + <trans-unit id="cleanupPreviewLinkTask.description" xml:space="preserve"> + <source>This task delete old preview links</source> + </trans-unit> + <trans-unit id="stage_ready_to_publish" xml:space="preserve"> + <source>Ready to publish</source> + </trans-unit> + </body> + </file> +</xliff> diff --git a/typo3/sysext/workspaces/Resources/Private/Layouts/Module.html b/typo3/sysext/workspaces/Resources/Private/Layouts/Module.html new file mode 100644 index 0000000000000000000000000000000000000000..4d5aeb4b9575118b006d6c13e6fcb88fc8243efc --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Layouts/Module.html @@ -0,0 +1,35 @@ +<!-- ###FULLDOC### begin --> +<div class="typo3-fullDoc"> + <div id="typo3-docheader"> + <div class="typo3-docheader-functions"> + <div class="left"></div> + <div class="right"> + <f:be.pagePath /><f:be.pageInfo /> + </div> + </div> + <div class="typo3-docheader-buttons"> + <div class="left"> + <f:if condition="{showPreviewLink}"> + <a href="#" onclick="TYPO3.Workspaces.Actions.generateWorkspacePreviewLink();return false;" title="{f:translate(key:'tooltip.generatePagePreview')}" id="goPreviewLinkButton"> + <span class="t3-icon t3-icon-extensions t3-icon-extensions-workspaces t3-icon-workspaces-generatepreviewlink"> </span> + <f:translate key="label_doaction_generatePreviewLink">Generate Workspace Preview Link</f:translate> + </a> + </f:if> + </div> + <div class="right"> + <f:be.buttons.shortcut /> + </div> + </div> + </div> +</div> + +<div id="typo3-docbody"> + <div id="typo3-inner-docbody"> + + <f:flashMessages renderMode="div" /> + + <f:render partial="navigation" arguments="{workspaceList: workspaceList, activeWorkspaceUid: activeWorkspaceUid, showAllWorkspaceTab:showAllWorkspaceTab}" /> + <div class="typo3-dyntabmenu-divs"><f:render section="main" /></div> + <f:if condition="{showLegend}"><f:render partial="legend" /></f:if> + </div> +</div> diff --git a/typo3/sysext/workspaces/Resources/Private/Layouts/Nodoc.html b/typo3/sysext/workspaces/Resources/Private/Layouts/Nodoc.html new file mode 100644 index 0000000000000000000000000000000000000000..d3ebb5069b453c7d3c566d05a667e4ba1f929729 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Layouts/Nodoc.html @@ -0,0 +1,11 @@ +<div class="typo3-noDoc"> + <!-- Content of module, for instance listing, info or editing --> + <div id="typo3-docbody"> + <div id="typo3-inner-docbody"> + + <f:flashMessages renderMode="div" /> + + <f:render section="main"/> + </div> + </div> +</div> diff --git a/typo3/sysext/workspaces/Resources/Private/Layouts/Popup.html b/typo3/sysext/workspaces/Resources/Private/Layouts/Popup.html new file mode 100644 index 0000000000000000000000000000000000000000..f935b702f8c576f4607ac2b9f301348d0c7fe282 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Layouts/Popup.html @@ -0,0 +1,21 @@ +<!-- ###FULLDOC### begin --> +<f:render section="main" /> +<script type="text/javascript"> + var liveUrl = '{liveUrl}'; + var wsUrl = '{wsUrl}'; + var wsSettingsUrl = '{wsSettingsUrl}'; + document.domain = '{backendDomain}'; + + function resize(height) { + // poor way to avoid that we require any scrollbars within the frames + if (Ext.getCmp('wsContainer')) { + var currentHeight = isNaN(Ext.getCmp('wsContainer').getHeight()) ? 0 : Ext.getCmp('wsContainer').getHeight(); + var finalHeight = Math.max(currentHeight, height * 1.1); + Ext.getCmp('visualPanel').setHeight(finalHeight); + Ext.getCmp('wsContainer').setHeight(finalHeight); + Ext.getCmp('wsPanel').setHeight(finalHeight); + Ext.getCmp('liveContainer').setHeight(finalHeight * (100 - Ext.getCmp('sizeSlider').getValue()) / 100); + Ext.getCmp('livePanel').setHeight(finalHeight); + } + } +</script> diff --git a/typo3/sysext/workspaces/Resources/Private/Partials/legend.html b/typo3/sysext/workspaces/Resources/Private/Partials/legend.html new file mode 100644 index 0000000000000000000000000000000000000000..9accc01b9e8ae7214473458b9e390316e0618448 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Partials/legend.html @@ -0,0 +1,8 @@ +<dl class="legend"> + <dt><f:translate key="legend.label" /></dt> + <dd><span class="item-state-modified"><f:translate key="legend.edited" /></span> • </dd> + <dd><span class="item-state-moved"><f:translate key="legend.moved" /></span> • </dd> + <dd><span class="item-state-new"><f:translate key="legend.new" /></span> • </dd> + <dd><span class="item-state-hidden"><f:translate key="legend.hidden" /></span> • </dd> + <dd><span class="item-state-deleted"><f:translate key="legend.deleted" /></span></dd> +</dl> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Private/Partials/navigation.html b/typo3/sysext/workspaces/Resources/Private/Partials/navigation.html new file mode 100644 index 0000000000000000000000000000000000000000..d7af3921142916b0d140a14ad1d81743bdbc5685 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Partials/navigation.html @@ -0,0 +1,34 @@ +<ul class="x-tab-strip x-tab-strip-top"> + <f:for each="{workspaceList}" as="workspace" key="uid"> + <f:if condition="{uid}=={activeWorkspaceUid}"> + <f:then> + <li class="x-tab-strip-active x-tab-strip-closable"> + <span class="x-tab-right"> + <em class="x-tab-left"><span class="x-tab-strip-inner"><span class="x-tab-strip-text">{workspace}</span></span></em> + </span> + </li> + </f:then> + <f:else> + <li class="x-tab-strip-closable"> + <f:link.action controller="Review" action="index" additionalParams="{workspace:uid}" class="x-tab-right"> + <em class="x-tab-left"><span class="x-tab-strip-inner"><span class="x-tab-strip-text">{workspace}</span></span></em> + </f:link.action> + </li> + </f:else> + </f:if> + </f:for> + <f:if condition="{showAllWorkspaceTab}"> + <li class="last {f:if(condition: '-98=={activeWorkspaceUid}', then: 'x-tab-strip-active')}"> + <f:link.action controller="Review" action="fullIndex" class="x-tab-right"> + <em class="x-tab-left"> + <span class="x-tab-strip-inner"> + <span class="x-tab-strip-text"> + All + </span> + </span> + </em> + </f:link.action> + </li> + </f:if> + <div class="x-clear"></div> + </ul> diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Help.html b/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Help.html new file mode 100644 index 0000000000000000000000000000000000000000..9b56cffcc2cc1c8108db73f3b5db8e435c4c2545 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Help.html @@ -0,0 +1,3 @@ +<f:layout name="nodoc" /> + +<f:section name="main">Help contents - not yet defined</f:section> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Index.html b/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Index.html new file mode 100644 index 0000000000000000000000000000000000000000..29d353410a5dfc56cfb48a7c71a8b71506abad4b --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Index.html @@ -0,0 +1,3 @@ +<f:layout name="popup" /> + +<f:section name="main"></f:section> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Preview/NewPage.html b/typo3/sysext/workspaces/Resources/Private/Templates/Preview/NewPage.html new file mode 100644 index 0000000000000000000000000000000000000000..41fac087cbf695e3ce2b328f020ffc8217212af7 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Templates/Preview/NewPage.html @@ -0,0 +1,3 @@ +<f:layout name="nodoc" /> + +<f:section name="main"></f:section> diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Preview.html b/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Preview.html new file mode 100644 index 0000000000000000000000000000000000000000..2629a2a3403406e1d2fd37e44e40aeaa7869a697 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Preview.html @@ -0,0 +1,47 @@ +<script type="text/javascript"> + + // @todo redirect to split module if this is opened standalone + + // having this is very important, otherwise the parent.resize call will fail + document.domain = '{backendDomain}'; + + var asNumber = function(val) { + return isNaN(val) ? 0 : parseInt(val, 10); + }; + var TYPO3 = TYPO3 || {}; + TYPO3.ready = function () { + // make sure we're in the workspace preview module + if (typeof parent.resize == 'function') { + // try to find the height of the document + var docHeight = Math.max( + asNumber(window.innerHeight), + asNumber(document.height), + asNumber(document.body.scrollHeight), + asNumber(document.body.offsetHeight), + asNumber(document.body.clientHeight), + asNumber(document.documentElement.scrollHeight), + asNumber(document.documentElement.offsetHeight), + asNumber(document.documentElement.clientHeight) + ); + parent.resize(docHeight); + // remove the ugly red box if we're in the ws-repview frames + var element = document.getElementById('typo3-previewInfo'); + if (element) { + element.parentNode.removeChild(element); + } + } + }; + // trigger this after content is loaded, inspired by jQuery + if (document.addEventListener && !/opera/.test(navigator.userAgent.toLowerCase())) { + document.addEventListener("DOMContentLoaded", TYPO3.ready, false); + } else { + (function() { + if (document.readyState != "loaded" && document.readyState != "complete") { + setTimeout(arguments.callee, 10); + } else { + TYPO3.ready(); + } + })(); + } + +</script> diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Review/FullIndex.html b/typo3/sysext/workspaces/Resources/Private/Templates/Review/FullIndex.html new file mode 100644 index 0000000000000000000000000000000000000000..e80d90f926cfd5f80bb8d654eeb6c201d4fe69c3 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Templates/Review/FullIndex.html @@ -0,0 +1,5 @@ +<f:layout name="module" /> + +<f:section name="main"> +<div id="workspacegrid"></div> +</f:section> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Review/Index.html b/typo3/sysext/workspaces/Resources/Private/Templates/Review/Index.html new file mode 100644 index 0000000000000000000000000000000000000000..629e560dac9ae5a516c7d9d47daa4fb7eb9c636d --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Templates/Review/Index.html @@ -0,0 +1,18 @@ +<f:layout name="module" /> + +<f:section name="main"> + +<f:if condition="{performWorkspaceSwitch}"> +<script type="text/javascript"> + top.TYPO3ModuleMenu.refreshMenu(); + top.TYPO3BackendWorkspaceMenu.performWorkspaceSwitch({activeWorkspaceUid}, "{activeWorkspaceTitle}"); +</script> +</f:if> + +<f:if condition="{showGrid}"> + <f:then><div id="workspacegrid"></div> + </f:then> + <f:else><f:translate key="editorInLive" /></f:else> +</f:if> + +</f:section> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Review/SingleIndex.html b/typo3/sysext/workspaces/Resources/Private/Templates/Review/SingleIndex.html new file mode 100644 index 0000000000000000000000000000000000000000..4187ced0fe2c4e87b03044859c151b4c2cb3ce8b --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Private/Templates/Review/SingleIndex.html @@ -0,0 +1,9 @@ +<f:layout name="nodoc" /> + +<f:section name="main"> + <script type="text/javascript"> + document.domain = '{backendDomain}'; + </script> + + <div id="workspacegrid"></div> +</f:section> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/Images/bg.gif b/typo3/sysext/workspaces/Resources/Public/Images/bg.gif new file mode 100644 index 0000000000000000000000000000000000000000..8624666d94348cf14ec3425b5018e8d8d5e4d358 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/bg.gif differ diff --git a/typo3/sysext/workspaces/Resources/Public/Images/button_approve.png b/typo3/sysext/workspaces/Resources/Public/Images/button_approve.png new file mode 100644 index 0000000000000000000000000000000000000000..8385dc981ae7669271c2c46ae2365f86449f4d31 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/button_approve.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/Images/button_discard.png b/typo3/sysext/workspaces/Resources/Public/Images/button_discard.png new file mode 100644 index 0000000000000000000000000000000000000000..72e9de857446961eb826f6a3fa6b78db02ba8b66 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/button_discard.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/Images/button_reject.png b/typo3/sysext/workspaces/Resources/Public/Images/button_reject.png new file mode 100644 index 0000000000000000000000000000000000000000..8f2b0d454fb834e78a1890ea54f66b1b47b3157d Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/button_reject.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/Images/generate-ws-preview-link.png b/typo3/sysext/workspaces/Resources/Public/Images/generate-ws-preview-link.png new file mode 100644 index 0000000000000000000000000000000000000000..14348abec5558eba97d7016bff4dea00f740983d Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/generate-ws-preview-link.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/Images/moduleicon.gif b/typo3/sysext/workspaces/Resources/Public/Images/moduleicon.gif new file mode 100644 index 0000000000000000000000000000000000000000..d389a762ff771ef52b6124d5a9103edaac687eb6 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/moduleicon.gif differ diff --git a/typo3/sysext/workspaces/Resources/Public/Images/slider-bg.png b/typo3/sysext/workspaces/Resources/Public/Images/slider-bg.png new file mode 100755 index 0000000000000000000000000000000000000000..cbfb8e194cd394b57e1ce79aa2745e597cdf7b6c Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/slider-bg.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/Images/slider-thumb.png b/typo3/sysext/workspaces/Resources/Public/Images/slider-thumb.png new file mode 100755 index 0000000000000000000000000000000000000000..b0f4aad805d7dba400c724eee0500f84a5f1570f Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/slider-thumb.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/Images/typo3-logo.png b/typo3/sysext/workspaces/Resources/Public/Images/typo3-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c0868a308fbee2766485dc08246c8b7c3fe1396d Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/typo3-logo.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtonextstage.png b/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtonextstage.png new file mode 100644 index 0000000000000000000000000000000000000000..b1a1819238c6de8f9e50988f4151261fa6ba64ea Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtonextstage.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtoprevstage.png b/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtoprevstage.png new file mode 100644 index 0000000000000000000000000000000000000000..50d36c703bdc2bcd114b6a6b4012441c9526cc41 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtoprevstage.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/Images/workspaces-comments-arrow.gif b/typo3/sysext/workspaces/Resources/Public/Images/workspaces-comments-arrow.gif new file mode 100644 index 0000000000000000000000000000000000000000..a9cd44c54c9642eda7f935516d4a146378952226 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/workspaces-comments-arrow.gif differ diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Ext.ux.plugins.TabStripContainer.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Ext.ux.plugins.TabStripContainer.js new file mode 100644 index 0000000000000000000000000000000000000000..07fd958e1cf8eed9f0e615fb2285b689f3f2d603 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/Ext.ux.plugins.TabStripContainer.js @@ -0,0 +1,101 @@ +/** + * Ext.ux.plugins.TabStripContainer + * + * @author Steffen Kamper + * @date December 19, 2010 + * + * @class Ext.ux.plugins.TabStripContainer + * @extends Object + */ + +Ext.ns('Ext.ux.plugins'); + +Ext.ux.plugins.TabStripContainer = Ext.extend(Object, { + + /** + * @hide private + * + * Tab panel we are plugged in. + */ + tabPanel : null, + + /** + * @hide private + * + * items for the panel + */ + items: [], + + /** + * @hide private + * + * Cached tab panel's strip wrap element container, i.e. panel's header or footer element. + */ + headerFooterEl : null, + + + /** + * @constructor + */ + constructor : function(config) { + Ext.apply(this, config); + }, + + /** + * Initializes plugin + */ + init : function(tabPanel) { + this.tabPanel = tabPanel; + tabPanel.on( + 'afterrender', + this.onTabPanelAfterRender, + this, + { + delay: 10 + } + ); + }, + + /** + * Adds the panel to the tab header/footer + * + * @param tabPanel + */ + onTabPanelAfterRender: function(tabPanel) { + var height, panelDiv, stripTarget, config; + // Getting and caching strip wrap element parent, i.e. tab panel footer or header. + this.headerFooterEl = + this.tabPanel.tabPosition == 'bottom' + ? this.tabPanel.footer + : this.tabPanel.header; + height = this.headerFooterEl.getComputedHeight(); + stripTarget = tabPanel[tabPanel.stripTarget]; + stripTarget.applyStyles('position: relative;'); + + panelDiv = this.headerFooterEl.createChild({ + tag : 'div', + id: this.id || Ext.id(), + style : { + position : 'absolute', + right: 0, + top: '1px' + } + }); + panelDiv.setSize(this.width, height, false); + config = Ext.applyIf({ + layout: 'hbox', + height: height, + width: this.width, + renderTo: panelDiv + }, this.panelConfig); + this.panelContainer = new Ext.Panel(config); + this.panelContainer.add(this.items); + this.panelContainer.doLayout(); + }, + + doLayout: function () { + this.panelContainer.doLayout(); + } + +}); +Ext.preg('Ext.ux.plugins.TabStripContainer', Ext.ux.plugins.TabStripContainer); diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Store/mainstore.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Store/mainstore.js new file mode 100644 index 0000000000000000000000000000000000000000..fa421ec8dcc0c21071d86e9b84edba4ddafcf362 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/Store/mainstore.js @@ -0,0 +1,70 @@ +Ext.ns('TYPO3.Workspaces.Configuration'); + +TYPO3.Workspaces.Configuration.StoreFieldArray = [ + {name : 'table'}, + {name : 'uid', type : 'int'}, + {name : 't3ver_oid', type : 'int'}, + {name : 'livepid', type : 'int'}, + {name : 'stage', type: 'int'}, + {name : 'change',type : 'int'}, + {name : 'languageValue'}, + {name : 'language'}, + {name : 'integrity'}, + {name : 'label_Live'}, + {name : 'label_Workspace'}, + {name : 'label_Stage'}, + {name : 'label_nextStage'}, + {name : 'label_prevStage'}, + {name : 'workspace_Title'}, + {name : 'actions'}, + {name : 'icon_Workspace'}, + {name : 'icon_Live'}, + {name : 'path_Live'}, + {name : 'path_Workspace'}, + {name : 'state_Workspace'}, + {name : 'workspace_Tstamp'}, + {name : 'workspace_Formated_Tstamp'}, + {name : 'allowedAction_nextStage'}, + {name : 'allowedAction_prevStage'}, + {name : 'allowedAction_swap'}, + {name : 'allowedAction_delete'}, + {name : 'allowedAction_edit'}, + {name : 'allowedAction_editVersionedPage'}, + {name : 'allowedAction_view'} + +]; + +TYPO3.Workspaces.MainStore = new Ext.data.GroupingStore({ + storeId : 'workspacesMainStore', + reader : new Ext.data.JsonReader({ + idProperty : 'id', + root : 'data', + totalProperty : 'total' + }, TYPO3.Workspaces.Configuration.StoreFieldArray), + groupField: 'path_Workspace', + paramsAsHash : true, + sortInfo : { + field : 'label_Live', + direction : "ASC" + }, + remoteSort : true, + baseParams: { + depth : 990, + id: TYPO3.settings.Workspaces.id, + language: TYPO3.settings.Workspaces.language, + query: '', + start: 0, + limit: 30 + }, + + showAction : false, + listeners : { + beforeload : function() { + }, + load : function(store, records) { + }, + datachanged : function(store) { + }, + scope : this + } +}); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js new file mode 100644 index 0000000000000000000000000000000000000000..ddbdac6a8723dffcf09aac47d997dff761266a93 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js @@ -0,0 +1,390 @@ +/*************************************************************** + * Copyright notice + * + * (c) 2010 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! + ***************************************************************/ + + +Ext.ns('TYPO3.Workspaces'); + +TYPO3.Workspaces.Actions = { + + runningMassAction: null, + currentSendToMode: 'next', + + checkIntegrity: function(parameters, callbackFunction, callbackArguments) { + TYPO3.Workspaces.ExtDirect.checkIntegrity( + parameters, + function (response) { + switch (response.result) { + case 'error': + top.TYPO3.Dialog.ErrorDialog({ + minWidth: 400, + title: 'Error', + msg: '<div class="scope">' + TYPO3.l10n.localize('integrity.hasIssuesDescription') + '</div>' + }); + break; + case 'warning': + top.TYPO3.Dialog.QuestionDialog({ + minWidth: 400, + title: 'Warning', + msg: '<div class="scope">' + TYPO3.l10n.localize('integrity.hasIssuesDescription') + '</div>' + + '<div class="question">' + TYPO3.l10n.localize('integrity.hasIssuesQuestion') + '</div>', + fn: function(result) { + if (result == 'yes') { + callbackFunction.call(this, callbackArguments) + } + } + }); + break; + default: + callbackFunction.call(this, callbackArguments); + } + } + ) + }, + + triggerMassAction: function(action, language) { + switch (action) { + case 'publish': + case 'swap': + this.runningMassAction = TYPO3.Workspaces.ExtDirectMassActions.publishWorkspace; + break; + case 'discard': + this.runningMassAction = TYPO3.Workspaces.ExtDirectMassActions.flushWorkspace; + break; + } + + // Publishing large amount of changes may require a longer timeout + Ext.Ajax.timeout = 3600000; + + this.runMassAction({ + init: true, + total:0, + processed:0, + language: language, + swap: (action == 'swap') + }); + }, + + runMassAction: function(parameters) { + if (parameters.init) { + top.Ext.getCmp('executeMassActionForm').hide(); + top.Ext.getCmp('executeMassActionProgressBar').show(); + top.Ext.getCmp('executeMassActionOkButton').disable(); + } + + var progress = parameters.total > 0 ? parameters.processed / parameters.total : 0; + var label = parameters.total > 0 ? parameters.processed + '/' + parameters.total : TYPO3.l10n.localize('runMassAction.init'); + top.Ext.getCmp('executeMassActionProgressBar').updateProgress(progress, label, true); + + this.runningMassAction(parameters, TYPO3.Workspaces.Actions.runMassActionCallback); + }, + + runMassActionCallback: function(response) { + if (response.error) { + top.Ext.getCmp('executeMassActionProgressBar').hide(); + top.Ext.getCmp('executeMassActionOkButton').hide(); + top.Ext.getCmp('executeMassActionCancleButton').setText(TYPO3.l10n.localize('close')); + top.Ext.getCmp('executeMassActionForm').show(); + top.Ext.getCmp('executeMassActionForm').update(response.error); + } else { + if (response.total > response.processed) { + TYPO3.Workspaces.Actions.runMassAction(response); + } else { + top.Ext.getCmp('executeMassActionProgressBar').hide(); + top.Ext.getCmp('executeMassActionOkButton').hide(); + top.Ext.getCmp('executeMassActionCancleButton').setText(TYPO3.l10n.localize('close')); + top.Ext.getCmp('executeMassActionForm').show(); + top.Ext.getCmp('executeMassActionForm').update(TYPO3.l10n.localize('runMassAction.done').replace('%d', response.total)); + top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree(); + } + } + }, + generateWorkspacePreviewLink: function() { + TYPO3.Workspaces.ExtDirectActions.generateWorkspacePreviewLink(TYPO3.settings.Workspaces.id, function(response) { + top.TYPO3.Dialog.InformationDialog({ + title: TYPO3.l10n.localize('previewLink'), + msg: String.format('<a href="{0}" target="_blank">{0}</a>', response) + }); + }); + }, + swapSingleRecord: function(table, t3ver_oid, orig_uid) { + TYPO3.Workspaces.ExtDirectActions.swapSingleRecord(table, t3ver_oid, orig_uid, function(response) { + TYPO3.Workspaces.MainStore.load(); + }); + }, + deleteSingleRecord: function(table, uid) { + TYPO3.Workspaces.ExtDirectActions.deleteSingleRecord(table, uid, function(response) { + TYPO3.Workspaces.MainStore.load(); + }); + }, + viewSingleRecord: function(table, uid) { + TYPO3.Workspaces.ExtDirectActions.viewSingleRecord(table, uid, function(response) { + eval(response); + }); + }, + sendToStageWindow: function(response, selection) { + if (Ext.isObject(response.error)) { + TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response); + } else { + var dialog = TYPO3.Workspaces.Helpers.getSendToStageWindow({ + title: response.title, + items: response.items, + executeHandler: function(event) { + var values = top.Ext.getCmp('sendToStageForm').getForm().getValues(); + affects = response.affects; + affects.elements = TYPO3.Workspaces.Helpers.getElementsArrayOfSelection(selection); + var parameters = { + affects: affects, + receipients: TYPO3.Workspaces.Helpers.getElementIdsFromFormValues(values, 'receipients'), + additional: values.additional, + comments: values.comments + }; + + TYPO3.Workspaces.Actions.sendToStageExecute(parameters); + top.TYPO3.Windows.close('sendToStageWindow'); + TYPO3.Workspaces.MainStore.reload(); + top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree(); + } + }); + } + }, + sendToNextStageWindow: function(table, uid, t3ver_oid) { + TYPO3.Workspaces.ExtDirectActions.sendToNextStageWindow(uid, table, t3ver_oid, function(response) { + TYPO3.Workspaces.Actions.currentSendToMode = 'next'; + TYPO3.Workspaces.Actions.sendToStageWindow(response); + }); + }, + sendToPrevStageWindow: function(table, uid) { + TYPO3.Workspaces.ExtDirectActions.sendToPrevStageWindow(uid, table, function(response) { + TYPO3.Workspaces.Actions.currentSendToMode = 'prev'; + TYPO3.Workspaces.Actions.sendToStageWindow(response); + }); + }, + sendToSpecificStageWindow: function(selection, nextStage) { + TYPO3.Workspaces.ExtDirectActions.sendToSpecificStageWindow(nextStage, function(response) { + TYPO3.Workspaces.Actions.currentSendToMode = 'specific'; + TYPO3.Workspaces.Actions.sendToStageWindow(response, selection); + }); + }, + sendToStageExecute: function (parameters) { + switch (TYPO3.Workspaces.Actions.currentSendToMode) { + case 'next': + TYPO3.Workspaces.ExtDirectActions.sendToNextStageExecute(parameters, TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction); + break; + case 'prev': + TYPO3.Workspaces.ExtDirectActions.sendToPrevStageExecute(parameters, TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction); + break; + case 'specific': + TYPO3.Workspaces.ExtDirectActions.sendToSpecificStageExecute(parameters, TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction); + break; + } + + }, + updateColModel: function(colModel) { + var dataArray = []; + for (var i = 0; i < colModel.config.length; i++) { + if (colModel.config[i].dataIndex !== '') { + dataArray.push({ + 'position': i, + 'column': colModel.config[i].dataIndex, + 'hidden': colModel.config[i].hidden ? 1 : 0 + }); + } + } + TYPO3.Workspaces.ExtDirectActions.saveColumnModel(dataArray); + }, + loadColModel: function(grid) { + TYPO3.Workspaces.ExtDirectActions.loadColumnModel(function(response) { + var colModel = grid.getColumnModel(); + for (var field in response) { + var colIndex = colModel.getIndexById(field); + if (colIndex != -1) { + colModel.setHidden(colModel.getIndexById(field), (response[field].hidden == 1 ? true : false)); + colModel.moveColumn(colModel.getIndexById(field), response[field].position); + } + } + }); + }, + handlerResponseOnExecuteAction: function(response) { + if (!Ext.isObject(response)) { + response = { error: { message: TYPO3.l10n.localize('error.noResponse') }}; + } + + if (Ext.isObject(response.error)) { + var error = response.error; + var code = (error.code ? ' #' + error.code : ''); + top.TYPO3.Dialog.ErrorDialog({ title: 'Error' + code, msg: error.message }); + } + }, + + /** + * Process "send to next stage" action. + * + * This method is used in the split frontend preview part. + * + * @return void + * + * @author Michael Klapper <development@morphodo.com> + */ + sendPageToNextStage: function () { + TYPO3.Workspaces.ExtDirectActions.sendPageToNextStage(TYPO3.settings.Workspaces.id, function (response) { + if (Ext.isObject(response.error)) { + TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response); + } else { + var dialog = TYPO3.Workspaces.Helpers.getSendToStageWindow({ + title: TYPO3.l10n.localize('nextStage'), + items: response.items.items, + executeHandler: function(event) { + var values = top.Ext.getCmp('sendToStageForm').getForm().getValues(); + affects = response.affects; + var parameters = { + affects: affects, + receipients: TYPO3.Workspaces.Helpers.getElementIdsFromFormValues(values, 'receipients'), + additional: values.additional, + comments: values.comments, + stageId: response.stageId + }; + TYPO3.Workspaces.ExtDirectActions.sentCollectionToStage(parameters, function (response) { + TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response); + TYPO3.Workspaces.ExtDirectActions.updateStageChangeButtons(TYPO3.settings.Workspaces.id, TYPO3.Workspaces.Actions.updateStageChangeButtons); + + if (response.refreshLivePanel == true) { + Ext.getCmp('livePanel').refresh(); + Ext.getCmp('livePanel-hbox').refresh(); + Ext.getCmp('livePanel-vbox').refresh(); + } + }); + top.TYPO3.Windows.close('sendToStageWindow'); + } + }); + } + }); + }, + + /** + * Process "send to previous stage" action. + * + * This method is used in the split frontend preview part. + * + * @return void + * + * @author Michael Klapper <development@morphodo.com> + */ + sendPageToPrevStage: function () { + TYPO3.Workspaces.ExtDirectActions.sendPageToPreviousStage(TYPO3.settings.Workspaces.id, function (response) { + if (Ext.isObject(response.error)) { + TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response); + } else { + var dialog = TYPO3.Workspaces.Helpers.getSendToStageWindow({ + title: TYPO3.l10n.localize('nextStage'), + items: response.items.items, + executeHandler: function(event) { + var values = top.Ext.getCmp('sendToStageForm').getForm().getValues(); + + affects = response.affects; + var parameters = { + affects: affects, + receipients: TYPO3.Workspaces.Helpers.getElementIdsFromFormValues(values, 'receipients'), + additional: values.additional, + comments: values.comments, + stageId: response.stageId + }; + TYPO3.Workspaces.ExtDirectActions.sentCollectionToStage(parameters, function (response) { + TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response); + TYPO3.Workspaces.ExtDirectActions.updateStageChangeButtons(TYPO3.settings.Workspaces.id, TYPO3.Workspaces.Actions.updateStageChangeButtons); + }); + top.TYPO3.Windows.close('sendToStageWindow'); + } + }); + } + }); + }, + + /** + * Update the visible state for the buttons "next stage", "prev stage" and "discard". + * + * This method is used in the split frontend preview part. + * + * @param object response + * @return void + * + * @author Michael Klapper <development@morphodo.com> + */ + updateStageChangeButtons: function (response) { + + if (Ext.isObject(response.error)) { + TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response); + } else { + for (componentId in response) { + if (response[componentId].visible) { + if (!top.Ext.getCmp(componentId).isVisible()) { + top.Ext.getCmp(componentId).show(); + } + top.Ext.getCmp(componentId).setText(response[componentId].text.substr(0, 35)); + top.Ext.getCmp(componentId).setTooltip(response[componentId].text); + } else { + if (top.Ext.getCmp(componentId).isVisible()) { + top.Ext.getCmp(componentId).hide(); + } + } + } + // force doLayout on each plugin containing the preview panel + Ext.getCmp('preview').plugins.each(function (item, index) { + if (Ext.isFunction(item.doLayout)) { + item.doLayout(); + } + }); + } + }, + + /** + * Process the discard all items from current page action. + * + * This method is used in the split frontend preview part. + * + * @return void + * + * @author Michael Klapper <development@morphodo.com> + */ + discardPage: function () { + var configuration = { + title: TYPO3.l10n.localize('window.discardAll.title'), + msg: TYPO3.l10n.localize('window.discardAll.message'), + fn: function(result) { + if (result == 'yes') { + TYPO3.Workspaces.ExtDirectActions.discardStagesFromPage(TYPO3.settings.Workspaces.id, function (response) { + TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response); + TYPO3.Workspaces.ExtDirectActions.updateStageChangeButtons(TYPO3.settings.Workspaces.id, TYPO3.Workspaces.Actions.updateStageChangeButtons); + Ext.getCmp('wsPanel').refresh(); + Ext.getCmp('wsPanel-hbox').refresh(); + Ext.getCmp('wsPanel-vbox').refresh(); + }); + } + } + }; + + top.TYPO3.Dialog.QuestionDialog(configuration); + } +}; diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/component.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/component.js new file mode 100644 index 0000000000000000000000000000000000000000..bff48edd6c07357da8747fc4509752d49e213a3a --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/component.js @@ -0,0 +1,282 @@ +/*************************************************************** + * Copyright notice + * + * (c) 2010 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! + ***************************************************************/ + + +Ext.ns('TYPO3.Workspaces'); + +TYPO3.Workspaces.Component = {}; + +TYPO3.Workspaces.RowDetail = {}; +TYPO3.Workspaces.RowDetail.rowDataStore = new Ext.data.DirectStore({ + storeId : 'rowDetailService', + root : 'data', + totalProperty : 'total', + idProperty : 'id', + fields : [ + {name : 'uid'}, + {name : 't3ver_oid'}, + {name : 'table'}, + {name : 'stage'}, + {name : 'diff'}, + {name : 'path_Live'}, + {name : 'label_Stage'}, + {name : 'stage_position'}, + {name : 'stage_count'}, + {name : 'live_record'}, + {name : 'comments'}, + {name : 'icon_Live'}, + {name : 'icon_Workspace'}, + {name : 'languageValue'}, + {name : 'integrity'} + ] +}); + +Ext.override(Ext.XTemplate, { + exists: function(o, name) { + return typeof o != 'undefined' && o != null && o!=''; + } +}); + +Ext.override(Ext.grid.GroupingView, { + constructId : function(value, field, idx) { + var cfg = this.cm.config[idx], + groupRenderer = cfg.groupRenderer || cfg.renderer, + val = (this.groupMode == 'value') ? value : this.getGroup(value, {data:{}}, groupRenderer, 0, idx, this.ds); + + var id = this.getPrefix(field) + val; + id = id.replace(/[^a-zA-Z0-9_]/g, ''); + return id; + } +}); + + +TYPO3.Workspaces.RowDetail.rowDetailTemplate = new Ext.XTemplate( + '<div class="t3-workspaces-foldoutWrapper">', + '<tpl for=".">', + '<tpl>', + '<table class="char_select_template" width="100%">', + '<tr class="header">', + '<th class="char_select_profile_titleLeft">', + '{[TYPO3.l10n.localize(\'workspace_version\')]}', + '</th>', + '<th class="char_select_profile_titleRight">', + '{[TYPO3.l10n.localize(\'live_workspace\')]}', + '</th>', + '</tr>', + '<tr>', + '<td class="t3-workspaces-foldout-subheaderLeft">', + '{[String.format(TYPO3.l10n.localize(\'current_step\'), values.label_Stage, values.stage_position, values.stage_count)]}', + '</td>', + '<td class="t3-workspaces-foldout-subheaderRight">', + '{[String.format(TYPO3.l10n.localize(\'path\'), values.path_Live)]}', + '</td>', + '</tr>', + '<tr>', + '<td class="t3-workspaces-foldout-td-contentDiffLeft">', + '<div class="t3-workspaces-foldout-contentDiff-container">', + '<table class="t3-workspaces-foldout-contentDiff">', + '<tr><th><span class="{icon_Workspace}"> </span></th><td>{type_Workspace}</td></tr>', + '<tpl for="diff">', + '<tr><th>{label}</th><td>', + '<tpl if="this.exists(content)">', + '{content}', + '</tpl>', + '</td></tr>', + '</tpl>', + '</table>', + '</div>', + '</td>', + '<td class="t3-workspaces-foldout-td-contentDiffRight">', + '<div class="t3-workspaces-foldout-contentDiff-container">', + '<table class="t3-workspaces-foldout-contentDiff">', + '<tr><th><span class="{icon_Live}"></span></th><td>{type_Live}</td></tr>', + '<tpl for="live_record">', + '<tr><th>{label}</th><td>', + '<tpl if="this.exists(content)">', + '{content}', + '</tpl>', + '</td></tr>', + '</tpl>', + '</table>', + '</div>', + '</td>', + '</tr>', + '<tpl if="this.hasComments(comments)">', + '<tr>', + '<td class="t3-workspaces-foldout-subheaderLeft">', + '<div class="t3-workspaces-foldout-subheader-container">', + '{[String.format(TYPO3.l10n.localize(\'comments\'), values.stage_position, values.label_Stage)]}', + '</div>', + '</td>', + '<td class="t3-workspaces-foldout-subheaderRight">', + ' ', + '</td>', + '</tr>', + '<tr>', + '<td class="char_select_profile_stats">', + '<div class="t3-workspaces-comments">', + '<tpl for="comments">', + '<div class="t3-workspaces-comments-singleComment">', + '<div class="t3-workspaces-comments-singleComment-author">', + '{user_username}', + '</div>', + '<div class="t3-workspaces-comments-singleComment-content-wrapper"><div class="t3-workspaces-comments-singleComment-content">', + '<span class="t3-workspaces-comments-singleComment-content-date">{tstamp}</span>', + '<div class="t3-workspaces-comments-singleComment-content-title">@ {[String.format(TYPO3.l10n.localize(\'stage\'), values.stage_title)]}</div>', + '<div class="t3-workspaces-comments-singleComment-content-text">{user_comment}</div>', + '</div></div>', + '</div>', + '</tpl>', + '</div>', + '</td>', + '<td class="char_select_profile_title">', + ' ', + '</td>', + '</tpl>', + '</tr>', + '</table>', + '</tpl>', + '</tpl>', + '</div>', + '<div class="x-clear"></div>', + { + hasComments: function(comments){ + return comments.length>0; + } + } +); + +TYPO3.Workspaces.RowDetail.rowDataView = new Ext.DataView({ + store: TYPO3.Workspaces.RowDetail.rowDataStore, + tpl: TYPO3.Workspaces.RowDetail.rowDetailTemplate +}); + +Ext.ns('Ext.ux.TYPO3.Workspace'); +Ext.ux.TYPO3.Workspace.RowPanel = Ext.extend(Ext.Panel, { + constructor: function(config) { + config = config || { + frame:true, + width:'100%', + autoHeight:true, + layout:'fit', + title: TYPO3.l10n.localize('rowDetails') + }; + Ext.apply(this, config); + Ext.ux.TYPO3.Workspace.RowPanel.superclass.constructor.call(this, config); + } +}); + +TYPO3.Workspaces.RowExpander = new Ext.grid.RowExpander({ + menuDisabled: true, + hideable: false, + getRowClass : function(record, rowIndex, p, ds) { + cssClass = ''; + if (!record.json.allowedAction_nextStage && !record.json.allowedAction_prevStage && !record.json.allowedAction_swap) { + cssClass = 'typo3-workspaces-row-disabled '; + } + if(this.state[record.id]) { + cssClass += 'x-grid3-row-expanded'; + } else { + cssClass += 'x-grid3-row-collapsed'; + } + return cssClass; + }, + remoteDataMethod : function (record, index) { + TYPO3.Workspaces.RowDetail.rowDataStore.baseParams = { + uid: record.json.uid, + table: record.json.table, + stage: record.json.stage, + t3ver_oid: record.json.t3ver_oid, + path_Live: record.json.path_Live, + label_Stage: record.json.label_Stage + }; + TYPO3.Workspaces.RowDetail.rowDataStore.load({ + callback: function(r, options, success) { + TYPO3.Workspaces.RowExpander.expandRow(index); + } + }); + new Ext.ux.TYPO3.Workspace.RowPanel({ + renderTo: 'remData' + index, + items: TYPO3.Workspaces.RowDetail.rowDataView + }); + }, + onMouseDown : function(e, t) { + tObject = Ext.get(t); + if (tObject.hasClass('x-grid3-row-expander')) { + e.stopEvent(); + var row = e.getTarget('.x-grid3-row'); + this.toggleRow(row); + } + }, + toggleRow : function(row) { + this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'beforeExpand' : 'collapseRow'](row); + }, + beforeExpand : function(row) { + if (typeof row == 'number') { + row = this.grid.view.getRow(row); + } + var record = this.grid.store.getAt(row.rowIndex); + var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row); + + if (this.fireEvent('beforexpand', this, record, body, row.rowIndex) !== false) { + this.tpl = new Ext.Template("<div id=\"remData" + row.rowIndex + "\" class=\"rem-data-expand\"><\div>"); + if (this.tpl && this.lazyRender) { + body.innerHTML = this.getBodyContent(record, row.rowIndex); + } + } + // toggle remoteData loading + this.remoteDataMethod(record, row.rowIndex); + return true; + }, + expandRow : function(row) { + if (typeof row == 'number') { + row = this.grid.view.getRow(row); + } + var record = this.grid.store.getAt(row.rowIndex); + var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row); + this.state[record.id] = true; + Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded'); + this.fireEvent('expand', this, record, body, row.rowIndex); + var i; + for(i = 0; i < this.grid.store.getCount(); i++) { + if(i != row.rowIndex) { + this.collapseRow(i); + } + } + }, + collapseRow : function(row) { + if (typeof row == 'number') { + row = this.grid.view.getRow(row); + } + var record = this.grid.store.getAt(row.rowIndex); + var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true); + if (this.fireEvent('beforcollapse', this, record, body, row.rowIndex) !== false) { + this.state[record.id] = false; + Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed'); + this.fireEvent('collapse', this, record, body, row.rowIndex); + } + } +}); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/configuration.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/configuration.js new file mode 100644 index 0000000000000000000000000000000000000000..a54177978ba2ec30a93215a4c59f68398b6631ad --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/configuration.js @@ -0,0 +1,395 @@ +/*************************************************************** + * Copyright notice + * + * (c) 2010 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! + ***************************************************************/ + +Ext.ns('TYPO3.Workspaces'); +TYPO3.Workspaces.Configuration = {}; + +TYPO3.Workspaces.Configuration.GridFilters = new Ext.ux.grid.GridFilters({ + encode : false, // json encode the filter query + local : true, // defaults to false (remote filtering) + filters : [ + { + type : 'numeric', + dataIndex : 'uid' + }, + { + type : 'string', + dataIndex : 'workspace_Title' + }, + { + type : 'numeric', + dataIndex : 'languageValue' + }, + { + type : 'string', + dataIndex : 'label_Live' + }, + { + type : 'string', + dataIndex : 'label_Workspace' + }, + { + type : 'numeric', + dataIndex : 'change' + } + ] +}); + +TYPO3.Workspaces.Configuration.Integrity = new Ext.grid.Column({ + width: 24, + hideable: true, + sortable: false, + header: '<span class="' + TYPO3.settings.Workspaces.icons.integrity + '"> </span>', + renderer: function(value, meta, record) { + if (record.json.integrity.status !== 'success') { + var cls = TYPO3.settings.Workspaces.icons[record.json.integrity.status] + ' t3-visible'; + var title = TYPO3.l10n.localize('status.' + record.json.integrity.status); + var message = record.json.integrity.messages; + + return '<span class="' + cls + '" ext:qtitle="' + title + '" ext:qtip="' + message + '"> </span>'; + } + } +}); +TYPO3.Workspaces.Configuration.WsPath = { + id: 'path_Workspace', + dataIndex : 'path_Workspace', + width: 120, + hidden: true, + hideable: false, + sortable: true, + header : TYPO3.l10n.localize('column.wsPath'), + renderer: function(value, metaData, record, rowIndex, colIndex, store) { + var path = record.json.path_Workspace; + return path; + }, + filter : {type: 'string'} +}; + +TYPO3.Workspaces.Configuration.LivePath = { + id: 'path_Live', + dataIndex : 'path_Live', + width: 120, + hidden: true, + hideable: true, + sortable: true, + header : TYPO3.l10n.localize('column.livePath'), + renderer: function(value, metaData, record, rowIndex, colIndex, store) { + var path = record.json.path_Live; + return path; + }, + filter : {type: 'string'} +}; + +TYPO3.Workspaces.Configuration.WsTitleWithIcon = { + id: 'label_Workspace', + dataIndex : 'label_Workspace', + width: 120, + hideable: true, + sortable: true, + header : TYPO3.l10n.localize('column.wsTitle'), + renderer: function(value, metaData, record, rowIndex, colIndex, store) { + var dekoClass = 'item-state-' + record.json.state_Workspace; + value = "<span class=\"" + dekoClass + "\">" + value + "</span>"; + if (record.json.icon_Live === record.json.icon_Workspace) { + return value; + } else { + return "<span class=\"" + record.json.icon_Workspace + "\"> </span> " + value; + } + + }, + filter : {type: 'string'} +}; + +TYPO3.Workspaces.Configuration.Language = { + id: 'language', + dataIndex: 'languageValue', + width: 30, + hideable: true, + sortable: true, + header: '<span class="' + TYPO3.settings.Workspaces.icons.language + '"> </span>', + filter: { type: 'string '}, + renderer: function(value, metaData, record) { + return '<span class="' + record.json.language.cls + '" title="' + record.json.language.title + '"> </span>'; + } +}; + +TYPO3.Workspaces.Configuration.TitleWithIcon = { + id: 'label_Live', + dataIndex : 'label_Live', + width: 120, + hideable: false, + sortable: true, + header : TYPO3.l10n.localize('column.liveTitle'), + renderer: function(value, metaData, record, rowIndex, colIndex, store) { + var dekoClass = ''; + if (record.json.state_Workspace == 'unhidden') { + dekoClass = 'item-state-hidden'; + } + + value = "<span class=\"" + dekoClass + "\">" + value + "</span>"; + return "<span class=\"" + record.json.icon_Live + "\"> </span> " + value; + }, + filter : {type: 'string'} +}; + +TYPO3.Workspaces.Configuration.ChangeDate = { + id: 'workspace_Tstamp', + dataIndex : 'workspace_Tstamp', + width: 120, + sortable: true, + header : TYPO3.l10n.localize('column.changeDate'), + renderer: function(value, metaData, record, rowIndex, colIndex, store) { + return record.json.workspace_Formated_Tstamp; + }, + hidden: true, + filter : {type : 'string'} +}; + +TYPO3.Workspaces.Configuration.SendToPrevStageButton = { + xtype: 'actioncolumn', + header:'', + width: 18, + hidden: (TYPO3.settings.Workspaces.allView === '1'), + items:[ + { + iconCls: 't3-icon t3-icon-extensions t3-icon-extensions-workspaces t3-icon-workspaces-sendtoprevstage', + tooltip: TYPO3.l10n.localize('tooltip.sendToPrevStage'), + handler: function(grid, rowIndex, colIndex) { + var record = TYPO3.Workspaces.MainStore.getAt(rowIndex); + TYPO3.Workspaces.Actions.sendToPrevStageWindow(record.json.table, record.json.uid); + } + } + ] +}; + +TYPO3.Workspaces.Configuration.SendToNextStageButton = { + xtype: 'actioncolumn', + header:'', + width: 18, + hidden: (TYPO3.settings.Workspaces.allView === '1'), + items: [ + {},{ // empty dummy important!!!! + iconCls: 't3-icon t3-icon-extensions t3-icon-extensions-workspaces t3-icon-workspaces-sendtonextstage', + tooltip: TYPO3.l10n.localize('tooltip.sendToNextStage'), + handler: function(grid, rowIndex, colIndex) { + var record = TYPO3.Workspaces.MainStore.getAt(rowIndex); + TYPO3.Workspaces.Actions.sendToNextStageWindow(record.json.table, record.json.uid, record.json.t3ver_oid); + } + } + ] +}; + +TYPO3.Workspaces.Configuration.Stage = { + id: 'label_Stage', + dataIndex : 'label_Stage', + width: 80, + sortable: true, + header : TYPO3.l10n.localize('column.stage'), + hidden: false, + filter : { + type : 'string' + }, + renderer: function(value, metaData, record, rowIndex, colIndex, store) { + var returnCode = ''; + if (record.json.allowedAction_prevStage && TYPO3.settings.Workspaces.allView !== '1') { + var tempTooltip = TYPO3.Workspaces.Configuration.SendToPrevStageButton.items[0].tooltip; + TYPO3.Workspaces.Configuration.SendToPrevStageButton.items[0].tooltip += ' "'+ record.json.label_prevStage + '"'; + var prevButton = new Ext.grid.ActionColumn(TYPO3.Workspaces.Configuration.SendToPrevStageButton); + returnCode += prevButton.renderer(1, metaData, record, rowIndex, 1, store); + TYPO3.Workspaces.Configuration.SendToPrevStageButton.items[0].tooltip = tempTooltip; + } else { + returnCode += "<span class=\"t3-icon t3-icon-empty t3-icon-empty-empty\"> </span>"; + } + returnCode += record.json.label_Stage; + if (record.json.allowedAction_nextStage && TYPO3.settings.Workspaces.allView !== '1') { + var tempTooltip = TYPO3.Workspaces.Configuration.SendToNextStageButton.items[1].tooltip; + TYPO3.Workspaces.Configuration.SendToNextStageButton.items[1].tooltip += ' "'+ record.json.label_nextStage + '"'; + var nextButton = new Ext.grid.ActionColumn(TYPO3.Workspaces.Configuration.SendToNextStageButton); + returnCode += nextButton.renderer(2, metaData, record, rowIndex, 2, store); + TYPO3.Workspaces.Configuration.SendToNextStageButton.items[1].tooltip = tempTooltip; + } else { + returnCode += "<span class=\"t3-icon t3-icon-empty t3-icon-empty-empty\"> </span>"; + } + return returnCode; + }, + processEvent : function(name, e, grid, rowIndex, colIndex){ + var m = e.getTarget().className.match(/x-action-col-(\d+)/); + if(m && m[1] == 0) { + TYPO3.Workspaces.Configuration.SendToPrevStageButton.items[0].handler(grid, rowIndex, colIndex); + return false; + } else if (m && m[1] == 1 ) { + TYPO3.Workspaces.Configuration.SendToNextStageButton.items[1].handler(grid, rowIndex, colIndex); + return false; + } + return Ext.grid.ActionColumn.superclass.processEvent.apply(this, arguments); + } +} + +TYPO3.Workspaces.Configuration.RowButtons = { + xtype: 'actioncolumn', + header: TYPO3.l10n.localize('column.actions'), + width: 70, + hideable: false, + hidden: (TYPO3.settings.Workspaces.allView === '1'), + menuDisabled: true, + items: [ + { + iconCls:'t3-icon t3-icon-actions t3-icon-actions-version t3-icon-version-workspace-preview' + ,tooltip: TYPO3.l10n.localize('tooltip.viewElementAction') + ,handler: function(grid, rowIndex, colIndex) { + var record = TYPO3.Workspaces.MainStore.getAt(rowIndex); + TYPO3.Workspaces.Actions.viewSingleRecord(record.json.table, record.json.uid); + }, + getClass: function(v, meta, rec) { + if(!rec.json.allowedAction_view) { + return 'icon-hidden'; + } else { + return ''; + } + } + }, + { + iconCls:'t3-icon t3-icon-actions t3-icon-actions-document t3-icon-document-open', + tooltip: TYPO3.l10n.localize('tooltip.editElementAction'), + handler: function(grid, rowIndex, colIndex) { + var record = TYPO3.Workspaces.MainStore.getAt(rowIndex); + var newUrl = 'alt_doc.php?returnUrl=' + encodeURIComponent(document.location.href) + '&id=' + TYPO3.settings.Workspaces.id + '&edit[' + record.json.table + '][' + record.json.uid + ']=edit'; + window.location.href = newUrl; + }, + getClass: function(v, meta, rec) { + if(!rec.json.allowedAction_edit) { + return 'icon-hidden'; + } else { + return ''; + } + } + }, + { + iconCls:'t3-icon t3-icon-actions t3-icon-actions-system t3-icon-system-pagemodule-open', + tooltip: TYPO3.l10n.localize('tooltip.openPage'), + handler: function(grid, rowIndex, colIndex) { + var record = TYPO3.Workspaces.MainStore.getAt(rowIndex); + if (record.json.table == 'pages') { + top.loadEditId(record.json.t3ver_oid); + } else { + top.loadEditId(record.json.livepid); + } + }, + getClass: function(v, meta, rec) { + if(!rec.json.allowedAction_editVersionedPage || !top.TYPO3.configuration.pageModule) { + return 'icon-hidden'; + } else { + return ''; + } + } + }, + { + iconCls:'t3-icon t3-icon-actions t3-icon-actions-version t3-icon-version-document-remove', + tooltip: TYPO3.l10n.localize('tooltip.discardVersion'), + handler: function(grid, rowIndex, colIndex) { + var record = TYPO3.Workspaces.MainStore.getAt(rowIndex); + var configuration = { + title: TYPO3.l10n.localize('window.discard.title'), + msg: TYPO3.l10n.localize('window.discard.message'), + fn: function(result) { + if (result == 'yes') { + TYPO3.Workspaces.Actions.deleteSingleRecord(record.json.table, record.json.uid); + } + } + }; + + top.TYPO3.Dialog.QuestionDialog(configuration); + }, + getClass: function(v, meta, rec) { + if(!rec.json.allowedAction_delete) { + return 'icon-hidden'; + } else { + return ''; + } + } + }, + { + iconCls: 't3-icon t3-icon-actions t3-icon-actions-document t3-icon-document-history-open', + tooltip: TYPO3.l10n.localize('tooltip.showHistory'), + handler: function(grid, rowIndex, colIndex) { + var record = TYPO3.Workspaces.MainStore.getAt(rowIndex); + TYPO3.Workspaces.Helpers.getHistoryWindow({ + table: record.json.table, + liveId: record.json.t3ver_oid, + versionId: record.json.uid + }); + } + } + ] +}; + +TYPO3.Workspaces.Configuration.SwapButton = { + xtype: 'actioncolumn', + header: '', + id: 'wsSwapColumn', + width: 18, + menuDisabled: true, + sortable: false, + hidden: (TYPO3.settings.Workspaces.allView === '1'), + items: [ + { + iconCls:'t3-icon t3-icon-actions t3-icon-actions-version t3-icon-version-swap-workspace' + ,tooltip: TYPO3.l10n.localize('tooltip.swap') + ,handler: function(grid, rowIndex, colIndex) { + var record = TYPO3.Workspaces.MainStore.getAt(rowIndex); + var parameters = { + type: 'selection', + selection: [{ + table: record.json.table, + versionId: record.json.uid, + liveId: record.json.t3ver_oid + }] + }; + + var configuration = { + title: TYPO3.l10n.localize('window.swap.title'), + msg: TYPO3.l10n.localize('window.swap.message'), + fn: function(result) { + if (result == 'yes') { + TYPO3.Workspaces.Actions.swapSingleRecord(record.json.table, record.json.t3ver_oid, record.json.uid); + } + } + }; + + TYPO3.Workspaces.Actions.checkIntegrity(parameters, function() { + top.TYPO3.Dialog.QuestionDialog(configuration); + }); + }, + getClass: function(v, meta, rec) { + if(!rec.json.allowedAction_swap) { + return 'icon-hidden'; + } else { + return ''; + } + } + } + ] +}; diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/grid.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/grid.js new file mode 100644 index 0000000000000000000000000000000000000000..4933880df61d77154b4b5b50295cc13b896f2485 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/grid.js @@ -0,0 +1,175 @@ +/*************************************************************** + * Copyright notice + * + * (c) 2010 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! + ***************************************************************/ + +Ext.ns('TYPO3.Workspaces'); + +Ext.override(Ext.grid.GridView, { + beforeColMenuShow : function() { + var colModel = this.cm, + colCount = colModel.getColumnCount(), + colMenu = this.colMenu, + i, text; + + colMenu.removeAll(); + + for (i = 0; i < colCount; i++) { + if (colModel.config[i].hideable !== false) { + text = colModel.getColumnHeader(i); + if (colModel.getColumnId(i) === 'wsSwapColumn') { + text = TYPO3.l10n.localize('column.wsSwapColumn'); + } + colMenu.add(new Ext.menu.CheckItem({ + text: text, + itemId: 'col-' + colModel.getColumnId(i), + checked: !colModel.isHidden(i), + disabled: colModel.config[i].hideable === false, + hideOnClick: false + })); + } + } + } +}); + +/** override mousedown for grid to select checkbox respecting singleSelect */ +Ext.override(Ext.grid.CheckboxSelectionModel, { + handleMouseDown: function(g, rowIndex, e) { + e.stopEvent(); + if (this.isSelected(rowIndex)) { + this.deselectRow(rowIndex); + } else { + this.selectRow(rowIndex, true); + this.grid.getView().focusRow(rowIndex); + } + } +}); + +TYPO3.Workspaces.SelectionModel = new Ext.grid.CheckboxSelectionModel({ + singleSelect: false, + hidden: true, + listeners: { + beforerowselect : function (selection, rowIndex, keep, rec) { + if (rec.json.allowedAction_nextStage || rec.json.allowedAction_prevStage || rec.json.allowedAction_swap) { + return true; + } else { + return false; + } + }, + selectionchange: function (selection) { + var record = selection.grid.getSelectionModel().getSelections(); + if (record.length > 0) { + TYPO3.Workspaces.Toolbar.selectStateActionCombo.setDisabled(false); + TYPO3.Workspaces.Toolbar.selectStateMassActionCombo.setDisabled(true); + } else { + TYPO3.Workspaces.Toolbar.selectStateActionCombo.setDisabled(true); + TYPO3.Workspaces.Toolbar.selectStateMassActionCombo.setDisabled(false); + } + } + } +}); + +TYPO3.Workspaces.WorkspaceGrid = new Ext.grid.GridPanel({ + initColModel: function() { + if (TYPO3.settings.Workspaces.isLiveWorkspace) { + this.colModel = new Ext.grid.ColumnModel({ + columns: [ + TYPO3.Workspaces.RowExpander, + TYPO3.Workspaces.Configuration.Integrity, + {id: 'uid', dataIndex : 'uid', width: 40, sortable: true, header : TYPO3.l10n.localize('column.uid'), hidden: true, filterable : true }, + {id: 't3ver_oid', dataIndex : 't3ver_oid', width: 40, sortable: true, header : TYPO3.l10n.localize('column.oid'), hidden: true, filterable : true }, + {id: 'workspace_Title', dataIndex : 'workspace_Title', width: 120, sortable: true, header : TYPO3.l10n.localize('column.workspaceName'), hidden: true, filter : {type : 'string'}}, + TYPO3.Workspaces.Configuration.WsPath, + TYPO3.Workspaces.Configuration.Language, + TYPO3.Workspaces.Configuration.LivePath, + TYPO3.Workspaces.Configuration.WsTitleWithIcon, + TYPO3.Workspaces.Configuration.TitleWithIcon, + TYPO3.Workspaces.Configuration.ChangeDate + ], + listeners: { + + columnmoved: function(colModel) { + TYPO3.Workspaces.Actions.updateColModel(colModel); + }, + hiddenchange: function(colModel) { + TYPO3.Workspaces.Actions.updateColModel(colModel); + } + } + }); + } else { + this.colModel = new Ext.grid.ColumnModel({ + columns: [ + TYPO3.Workspaces.SelectionModel, + TYPO3.Workspaces.RowExpander, + TYPO3.Workspaces.Configuration.Integrity, + {id: 'uid', dataIndex : 'uid', width: 40, sortable: true, header : TYPO3.l10n.localize('column.uid'), hidden: true, filterable : true }, + {id: 't3ver_oid', dataIndex : 't3ver_oid', width: 40, sortable: true, header : TYPO3.l10n.localize('column.oid'), hidden: true, filterable : true }, + {id: 'workspace_Title', dataIndex : 'workspace_Title', width: 120, sortable: true, header : TYPO3.l10n.localize('column.workspaceName'), hidden: true, filter : {type : 'string'}}, + TYPO3.Workspaces.Configuration.WsPath, + TYPO3.Workspaces.Configuration.Language, + TYPO3.Workspaces.Configuration.LivePath, + TYPO3.Workspaces.Configuration.WsTitleWithIcon, + TYPO3.Workspaces.Configuration.SwapButton, + TYPO3.Workspaces.Configuration.TitleWithIcon, + TYPO3.Workspaces.Configuration.ChangeDate, + TYPO3.Workspaces.Configuration.Stage, + TYPO3.Workspaces.Configuration.RowButtons + ], + listeners: { + + columnmoved: function(colModel) { + TYPO3.Workspaces.Actions.updateColModel(colModel); + }, + hiddenchange: function(colModel) { + TYPO3.Workspaces.Actions.updateColModel(colModel); + } + } + }); + } + + }, + border : true, + store : TYPO3.Workspaces.MainStore, + colModel : null, + sm: TYPO3.Workspaces.SelectionModel, + loadMask : true, + height: 630, + stripeRows: true, + // below the grid we need 40px space for the legend + heightOffset: 40, + plugins : [ + TYPO3.Workspaces.RowExpander, + TYPO3.Workspaces.Configuration.GridFilters, + new Ext.ux.plugins.FitToParent() + ], + view : new Ext.grid.GroupingView({ + forceFit: true, + groupTextTpl : '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "' + TYPO3.l10n.localize('items') + '" : "' + TYPO3.l10n.localize('item') + '"]})', + enableGroupingMenu: false, + enableNoGroups: false, + hideGroupedColumn: true + }), + bbar : TYPO3.Workspaces.Toolbar.FullBottomBar, + tbar : TYPO3.Workspaces.Toolbar.FullTopToolbar +}); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/GridFilters.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/GridFilters.js new file mode 100644 index 0000000000000000000000000000000000000000..07dbfe33963500bae164a911bf3c9884b440abd6 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/GridFilters.js @@ -0,0 +1,740 @@ +/*! + * Ext JS Library 3.3.0 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +Ext.namespace('Ext.ux.grid'); + +/** + * @class Ext.ux.grid.GridFilters + * @extends Ext.util.Observable + * <p>GridFilter is a plugin (<code>ptype='gridfilters'</code>) for grids that + * allow for a slightly more robust representation of filtering than what is + * provided by the default store.</p> + * <p>Filtering is adjusted by the user using the grid's column header menu + * (this menu can be disabled through configuration). Through this menu users + * can configure, enable, and disable filters for each column.</p> + * <p><b><u>Features:</u></b></p> + * <div class="mdetail-params"><ul> + * <li><b>Filtering implementations</b> : + * <div class="sub-desc"> + * Default filtering for Strings, Numeric Ranges, Date Ranges, Lists (which can + * be backed by a Ext.data.Store), and Boolean. Additional custom filter types + * and menus are easily created by extending Ext.ux.grid.filter.Filter. + * </div></li> + * <li><b>Graphical indicators</b> : + * <div class="sub-desc"> + * Columns that are filtered have {@link #filterCls a configurable css class} + * applied to the column headers. + * </div></li> + * <li><b>Paging</b> : + * <div class="sub-desc"> + * If specified as a plugin to the grid's configured PagingToolbar, the current page + * will be reset to page 1 whenever you update the filters. + * </div></li> + * <li><b>Automatic Reconfiguration</b> : + * <div class="sub-desc"> + * Filters automatically reconfigure when the grid 'reconfigure' event fires. + * </div></li> + * <li><b>Stateful</b> : + * Filter information will be persisted across page loads by specifying a + * <code>stateId</code> in the Grid configuration. + * <div class="sub-desc"> + * The filter collection binds to the + * <code>{@link Ext.grid.GridPanel#beforestaterestore beforestaterestore}</code> + * and <code>{@link Ext.grid.GridPanel#beforestatesave beforestatesave}</code> + * events in order to be stateful. + * </div></li> + * <li><b>Grid Changes</b> : + * <div class="sub-desc"><ul> + * <li>A <code>filters</code> <i>property</i> is added to the grid pointing to + * this plugin.</li> + * <li>A <code>filterupdate</code> <i>event</i> is added to the grid and is + * fired upon onStateChange completion.</li> + * </ul></div></li> + * <li><b>Server side code examples</b> : + * <div class="sub-desc"><ul> + * <li><a href="http://www.vinylfox.com/extjs/grid-filter-php-backend-code.php">PHP</a> - (Thanks VinylFox)</li> + * <li><a href="http://extjs.com/forum/showthread.php?p=77326#post77326">Ruby on Rails</a> - (Thanks Zyclops)</li> + * <li><a href="http://extjs.com/forum/showthread.php?p=176596#post176596">Ruby on Rails</a> - (Thanks Rotomaul)</li> + * <li><a href="http://www.debatablybeta.com/posts/using-extjss-grid-filtering-with-django/">Python</a> - (Thanks Matt)</li> + * <li><a href="http://mcantrell.wordpress.com/2008/08/22/extjs-grids-and-grails/">Grails</a> - (Thanks Mike)</li> + * </ul></div></li> + * </ul></div> + * <p><b><u>Example usage:</u></b></p> + * <pre><code> +var store = new Ext.data.GroupingStore({ + ... +}); + +var filters = new Ext.ux.grid.GridFilters({ + autoReload: false, //don't reload automatically + local: true, //only filter locally + // filters may be configured through the plugin, + // or in the column definition within the column model configuration + filters: [{ + type: 'numeric', + dataIndex: 'id' + }, { + type: 'string', + dataIndex: 'name' + }, { + type: 'numeric', + dataIndex: 'price' + }, { + type: 'date', + dataIndex: 'dateAdded' + }, { + type: 'list', + dataIndex: 'size', + options: ['extra small', 'small', 'medium', 'large', 'extra large'], + phpMode: true + }, { + type: 'boolean', + dataIndex: 'visible' + }] +}); +var cm = new Ext.grid.ColumnModel([{ + ... +}]); + +var grid = new Ext.grid.GridPanel({ + ds: store, + cm: cm, + view: new Ext.grid.GroupingView(), + plugins: [filters], + height: 400, + width: 700, + bbar: new Ext.PagingToolbar({ + store: store, + pageSize: 15, + plugins: [filters] //reset page to page 1 if filters change + }) + }); + +store.load({params: {start: 0, limit: 15}}); + +// a filters property is added to the grid +grid.filters + * </code></pre> + */ +Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, { + /** + * @cfg {Boolean} autoReload + * Defaults to true, reloading the datasource when a filter change happens. + * Set this to false to prevent the datastore from being reloaded if there + * are changes to the filters. See <code>{@link updateBuffer}</code>. + */ + autoReload : true, + /** + * @cfg {Boolean} encode + * Specify true for {@link #buildQuery} to use Ext.util.JSON.encode to + * encode the filter query parameter sent with a remote request. + * Defaults to false. + */ + /** + * @cfg {Array} filters + * An Array of filters config objects. Refer to each filter type class for + * configuration details specific to each filter type. Filters for Strings, + * Numeric Ranges, Date Ranges, Lists, and Boolean are the standard filters + * available. + */ + /** + * @cfg {String} filterCls + * The css class to be applied to column headers with active filters. + * Defaults to <tt>'ux-filterd-column'</tt>. + */ + filterCls : 'ux-filtered-column', + /** + * @cfg {Boolean} local + * <tt>true</tt> to use Ext.data.Store filter functions (local filtering) + * instead of the default (<tt>false</tt>) server side filtering. + */ + local : false, + /** + * @cfg {String} menuFilterText + * defaults to <tt>'Filters'</tt>. + */ + menuFilterText : 'Filters', + /** + * @cfg {String} paramPrefix + * The url parameter prefix for the filters. + * Defaults to <tt>'filter'</tt>. + */ + paramPrefix : 'filter', + /** + * @cfg {Boolean} showMenu + * Defaults to true, including a filter submenu in the default header menu. + */ + showMenu : true, + /** + * @cfg {String} stateId + * Name of the value to be used to store state information. + */ + stateId : undefined, + /** + * @cfg {Integer} updateBuffer + * Number of milliseconds to defer store updates since the last filter change. + */ + updateBuffer : 500, + + /** @private */ + constructor : function (config) { + config = config || {}; + this.deferredUpdate = new Ext.util.DelayedTask(this.reload, this); + this.filters = new Ext.util.MixedCollection(); + this.filters.getKey = function (o) { + return o ? o.dataIndex : null; + }; + this.addFilters(config.filters); + delete config.filters; + Ext.apply(this, config); + }, + + /** @private */ + init : function (grid) { + if (grid instanceof Ext.grid.GridPanel) { + this.grid = grid; + + this.bindStore(this.grid.getStore(), true); + // assumes no filters were passed in the constructor, so try and use ones from the colModel + if(this.filters.getCount() == 0){ + this.addFilters(this.grid.getColumnModel()); + } + + this.grid.filters = this; + + this.grid.addEvents({'filterupdate': true}); + + grid.on({ + scope: this, + beforestaterestore: this.applyState, + beforestatesave: this.saveState, + beforedestroy: this.destroy, + reconfigure: this.onReconfigure + }); + + if (grid.rendered){ + this.onRender(); + } else { + grid.on({ + scope: this, + single: true, + render: this.onRender + }); + } + + } else if (grid instanceof Ext.PagingToolbar) { + this.toolbar = grid; + } + }, + + /** + * @private + * Handler for the grid's beforestaterestore event (fires before the state of the + * grid is restored). + * @param {Object} grid The grid object + * @param {Object} state The hash of state values returned from the StateProvider. + */ + applyState : function (grid, state) { + var key, filter; + this.applyingState = true; + this.clearFilters(); + if (state.filters) { + for (key in state.filters) { + filter = this.filters.get(key); + if (filter) { + filter.setValue(state.filters[key]); + filter.setActive(true); + } + } + } + this.deferredUpdate.cancel(); + if (this.local) { + this.reload(); + } + delete this.applyingState; + delete state.filters; + }, + + /** + * Saves the state of all active filters + * @param {Object} grid + * @param {Object} state + * @return {Boolean} + */ + saveState : function (grid, state) { + var filters = {}; + this.filters.each(function (filter) { + if (filter.active) { + filters[filter.dataIndex] = filter.getValue(); + } + }); + return (state.filters = filters); + }, + + /** + * @private + * Handler called when the grid is rendered + */ + onRender : function () { + this.grid.getView().on('refresh', this.onRefresh, this); + this.createMenu(); + }, + + /** + * @private + * Handler called by the grid 'beforedestroy' event + */ + destroy : function () { + this.removeAll(); + this.purgeListeners(); + + if(this.filterMenu){ + Ext.menu.MenuMgr.unregister(this.filterMenu); + this.filterMenu.destroy(); + this.filterMenu = this.menu.menu = null; + } + }, + + /** + * Remove all filters, permanently destroying them. + */ + removeAll : function () { + if(this.filters){ + Ext.destroy.apply(Ext, this.filters.items); + // remove all items from the collection + this.filters.clear(); + } + }, + + + /** + * Changes the data store bound to this view and refreshes it. + * @param {Store} store The store to bind to this view + */ + bindStore : function(store, initial){ + if(!initial && this.store){ + if (this.local) { + store.un('load', this.onLoad, this); + } else { + store.un('beforeload', this.onBeforeLoad, this); + } + } + if(store){ + if (this.local) { + store.on('load', this.onLoad, this); + } else { + store.on('beforeload', this.onBeforeLoad, this); + } + } + this.store = store; + }, + + /** + * @private + * Handler called when the grid reconfigure event fires + */ + onReconfigure : function () { + this.bindStore(this.grid.getStore()); + this.store.clearFilter(); + this.removeAll(); + this.addFilters(this.grid.getColumnModel()); + this.updateColumnHeadings(); + }, + + createMenu : function () { + var view = this.grid.getView(), + hmenu = view.hmenu; + + if (this.showMenu && hmenu) { + + this.sep = hmenu.addSeparator(); + this.filterMenu = new Ext.menu.Menu({ + id: this.grid.id + '-filters-menu' + }); + this.menu = hmenu.add({ + checked: false, + itemId: 'filters', + text: this.menuFilterText, + menu: this.filterMenu + }); + + this.menu.on({ + scope: this, + checkchange: this.onCheckChange, + beforecheckchange: this.onBeforeCheck + }); + hmenu.on('beforeshow', this.onMenu, this); + } + this.updateColumnHeadings(); + }, + + /** + * @private + * Get the filter menu from the filters MixedCollection based on the clicked header + */ + getMenuFilter : function () { + var view = this.grid.getView(); + if (!view || view.hdCtxIndex === undefined) { + return null; + } + return this.filters.get( + view.cm.config[view.hdCtxIndex].dataIndex + ); + }, + + /** + * @private + * Handler called by the grid's hmenu beforeshow event + */ + onMenu : function (filterMenu) { + var filter = this.getMenuFilter(); + + if (filter) { +/* +TODO: lazy rendering + if (!filter.menu) { + filter.menu = filter.createMenu(); + } +*/ + this.menu.menu = filter.menu; + this.menu.setChecked(filter.active, false); + // disable the menu if filter.disabled explicitly set to true + this.menu.setDisabled(filter.disabled === true); + } + + this.menu.setVisible(filter !== undefined); + this.sep.setVisible(filter !== undefined); + }, + + /** @private */ + onCheckChange : function (item, value) { + this.getMenuFilter().setActive(value); + }, + + /** @private */ + onBeforeCheck : function (check, value) { + return !value || this.getMenuFilter().isActivatable(); + }, + + /** + * @private + * Handler for all events on filters. + * @param {String} event Event name + * @param {Object} filter Standard signature of the event before the event is fired + */ + onStateChange : function (event, filter) { + if (event === 'serialize') { + return; + } + + if (filter == this.getMenuFilter()) { + this.menu.setChecked(filter.active, false); + } + + if ((this.autoReload || this.local) && !this.applyingState) { + this.deferredUpdate.delay(this.updateBuffer); + } + this.updateColumnHeadings(); + + if (!this.applyingState) { + this.grid.saveState(); + } + this.grid.fireEvent('filterupdate', this, filter); + }, + + /** + * @private + * Handler for store's beforeload event when configured for remote filtering + * @param {Object} store + * @param {Object} options + */ + onBeforeLoad : function (store, options) { + options.params = options.params || {}; + this.cleanParams(options.params); + var params = this.buildQuery(this.getFilterData()); + Ext.apply(options.params, params); + }, + + /** + * @private + * Handler for store's load event when configured for local filtering + * @param {Object} store + * @param {Object} options + */ + onLoad : function (store, options) { + store.filterBy(this.getRecordFilter()); + }, + + /** + * @private + * Handler called when the grid's view is refreshed + */ + onRefresh : function () { + this.updateColumnHeadings(); + }, + + /** + * Update the styles for the header row based on the active filters + */ + updateColumnHeadings : function () { + var view = this.grid.getView(), + i, len, filter; + if (view.mainHd) { + for (i = 0, len = view.cm.config.length; i < len; i++) { + filter = this.getFilter(view.cm.config[i].dataIndex); + Ext.fly(view.getHeaderCell(i))[filter && filter.active ? 'addClass' : 'removeClass'](this.filterCls); + } + } + }, + + /** @private */ + reload : function () { + if (this.local) { + this.grid.store.clearFilter(true); + this.grid.store.filterBy(this.getRecordFilter()); + } else { + var start, + store = this.grid.store; + this.deferredUpdate.cancel(); + if (this.toolbar) { + start = store.paramNames.start; + if (store.lastOptions && store.lastOptions.params && store.lastOptions.params[start]) { + store.lastOptions.params[start] = 0; + } + } + store.reload(); + } + }, + + /** + * Method factory that generates a record validator for the filters active at the time + * of invokation. + * @private + */ + getRecordFilter : function () { + var f = [], len, i; + this.filters.each(function (filter) { + if (filter.active) { + f.push(filter); + } + }); + + len = f.length; + return function (record) { + for (i = 0; i < len; i++) { + if (!f[i].validateRecord(record)) { + return false; + } + } + return true; + }; + }, + + /** + * Adds a filter to the collection and observes it for state change. + * @param {Object/Ext.ux.grid.filter.Filter} config A filter configuration or a filter object. + * @return {Ext.ux.grid.filter.Filter} The existing or newly created filter object. + */ + addFilter : function (config) { + var Cls = this.getFilterClass(config.type), + filter = config.menu ? config : (new Cls(config)); + this.filters.add(filter); + + Ext.util.Observable.capture(filter, this.onStateChange, this); + return filter; + }, + + /** + * Adds filters to the collection. + * @param {Array/Ext.grid.ColumnModel} filters Either an Array of + * filter configuration objects or an Ext.grid.ColumnModel. The columns + * of a passed Ext.grid.ColumnModel will be examined for a <code>filter</code> + * property and, if present, will be used as the filter configuration object. + */ + addFilters : function (filters) { + if (filters) { + var i, len, filter, cm = false, dI; + if (filters instanceof Ext.grid.ColumnModel) { + filters = filters.config; + cm = true; + } + for (i = 0, len = filters.length; i < len; i++) { + filter = false; + if (cm) { + dI = filters[i].dataIndex; + filter = filters[i].filter || filters[i].filterable; + if (filter){ + filter = (filter === true) ? {} : filter; + Ext.apply(filter, {dataIndex:dI}); + // filter type is specified in order of preference: + // filter type specified in config + // type specified in store's field's type config + filter.type = filter.type || this.store.fields.get(dI).type.type; + } + } else { + filter = filters[i]; + } + // if filter config found add filter for the column + if (filter) { + this.addFilter(filter); + } + } + } + }, + + /** + * Returns a filter for the given dataIndex, if one exists. + * @param {String} dataIndex The dataIndex of the desired filter object. + * @return {Ext.ux.grid.filter.Filter} + */ + getFilter : function (dataIndex) { + return this.filters.get(dataIndex); + }, + + /** + * Turns all filters off. This does not clear the configuration information + * (see {@link #removeAll}). + */ + clearFilters : function () { + this.filters.each(function (filter) { + filter.setActive(false); + }); + }, + + /** + * Returns an Array of the currently active filters. + * @return {Array} filters Array of the currently active filters. + */ + getFilterData : function () { + var filters = [], i, len; + + this.filters.each(function (f) { + if (f.active) { + var d = [].concat(f.serialize()); + for (i = 0, len = d.length; i < len; i++) { + filters.push({ + field: f.dataIndex, + data: d[i] + }); + } + } + }); + return filters; + }, + + /** + * Function to take the active filters data and build it into a query. + * The format of the query depends on the <code>{@link #encode}</code> + * configuration: + * <div class="mdetail-params"><ul> + * + * <li><b><tt>false</tt></b> : <i>Default</i> + * <div class="sub-desc"> + * Flatten into query string of the form (assuming <code>{@link #paramPrefix}='filters'</code>: + * <pre><code> +filters[0][field]="someDataIndex"& +filters[0][data][comparison]="someValue1"& +filters[0][data][type]="someValue2"& +filters[0][data][value]="someValue3"& + * </code></pre> + * </div></li> + * <li><b><tt>true</tt></b> : + * <div class="sub-desc"> + * JSON encode the filter data + * <pre><code> +filters[0][field]="someDataIndex"& +filters[0][data][comparison]="someValue1"& +filters[0][data][type]="someValue2"& +filters[0][data][value]="someValue3"& + * </code></pre> + * </div></li> + * </ul></div> + * Override this method to customize the format of the filter query for remote requests. + * @param {Array} filters A collection of objects representing active filters and their configuration. + * Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured + * to be unique as any one filter may be a composite of more basic filters for the same dataIndex. + * @return {Object} Query keys and values + */ + buildQuery : function (filters) { + var p = {}, i, f, root, dataPrefix, key, tmp, + len = filters.length; + + if (!this.encode){ + for (i = 0; i < len; i++) { + f = filters[i]; + root = [this.paramPrefix, '[', i, ']'].join(''); + p[root + '[field]'] = f.field; + + dataPrefix = root + '[data]'; + for (key in f.data) { + p[[dataPrefix, '[', key, ']'].join('')] = f.data[key]; + } + } + } else { + tmp = []; + for (i = 0; i < len; i++) { + f = filters[i]; + tmp.push(Ext.apply( + {}, + {field: f.field}, + f.data + )); + } + // only build if there is active filter + if (tmp.length > 0){ + p[this.paramPrefix] = Ext.util.JSON.encode(tmp); + } + } + return p; + }, + + /** + * Removes filter related query parameters from the provided object. + * @param {Object} p Query parameters that may contain filter related fields. + */ + cleanParams : function (p) { + // if encoding just delete the property + if (this.encode) { + delete p[this.paramPrefix]; + // otherwise scrub the object of filter data + } else { + var regex, key; + regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]'); + for (key in p) { + if (regex.test(key)) { + delete p[key]; + } + } + } + }, + + /** + * Function for locating filter classes, overwrite this with your favorite + * loader to provide dynamic filter loading. + * @param {String} type The type of filter to load ('Filter' is automatically + * appended to the passed type; eg, 'string' becomes 'StringFilter'). + * @return {Class} The Ext.ux.grid.filter.Class + */ + getFilterClass : function (type) { + // map the supported Ext.data.Field type values into a supported filter + switch(type) { + case 'auto': + type = 'string'; + break; + case 'int': + case 'float': + type = 'numeric'; + break; + case 'bool': + type = 'boolean'; + break; + } + return Ext.ux.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter']; + } +}); + +// register ptype +Ext.preg('gridfilters', Ext.ux.grid.GridFilters); diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/GridFilters.css b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/GridFilters.css new file mode 100644 index 0000000000000000000000000000000000000000..0d1057866ad5d1cfb9b6568f2218088265fa00cf --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/GridFilters.css @@ -0,0 +1,53 @@ +/*! + * Ext JS Library 3.3.0 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * GridFilters Styles + **/ +/* +.x-grid3-hd-row .ux-filtered-column { + border-left: 1px solid #C7E3B4; + border-right: 1px solid #C7E3B4; +} + +.x-grid3-hd-row .ux-filtered-column .x-grid3-hd-inner { + background-image: url(../images/header_bg.gif); +} + +.ux-filtered-column .x-grid3-hd-btn { + background-image: url(../images/hd-btn.gif); +} +*/ +.x-grid3-hd-row td.ux-filtered-column { + font-style: italic; + font-weight: bold; +} + +.ux-filtered-column.sort-asc .x-grid3-sort-icon { + background-image: url(../images/sort_filtered_asc.gif) !important; +} + +.ux-filtered-column.sort-desc .x-grid3-sort-icon { + background-image: url(../images/sort_filtered_desc.gif) !important; +} + +.ux-gridfilter-text-icon { + background-image: url(../images/find.png) !important; +} + +/* Temporary Patch for Bug ??? */ +.x-menu-list-item-indent .x-menu-item-icon { + position: relative; + top: 3px; + left: 3px; + margin-right: 10px; +} +li.x-menu-list-item-indent { + padding-left:0px; +} +li.x-menu-list-item div { + display: block; +} \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/RangeMenu.css b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/RangeMenu.css new file mode 100644 index 0000000000000000000000000000000000000000..9495126e2e86b6a15a62e40e20256ac410d4a876 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/RangeMenu.css @@ -0,0 +1,20 @@ +/*! + * Ext JS Library 3.3.0 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * RangeMenu Styles + **/ +.ux-rangemenu-gt { + background-image: url(../images/greater_than.png) !important; +} + +.ux-rangemenu-lt { + background-image: url(../images/less_than.png) !important; +} + +.ux-rangemenu-eq { + background-image: url(../images/equals.png) !important; +} diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/BooleanFilter.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/BooleanFilter.js new file mode 100644 index 0000000000000000000000000000000000000000..3d9af5baf737d589ddc9b1107e8088b76ca8b00a --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/BooleanFilter.js @@ -0,0 +1,103 @@ +/*! + * Ext JS Library 3.3.0 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * @class Ext.ux.grid.filter.BooleanFilter + * @extends Ext.ux.grid.filter.Filter + * Boolean filters use unique radio group IDs (so you can have more than one!) + * <p><b><u>Example Usage:</u></b></p> + * <pre><code> +var filters = new Ext.ux.grid.GridFilters({ + ... + filters: [{ + // required configs + type: 'boolean', + dataIndex: 'visible' + + // optional configs + defaultValue: null, // leave unselected (false selected by default) + yesText: 'Yes', // default + noText: 'No' // default + }] +}); + * </code></pre> + */ +Ext.ux.grid.filter.BooleanFilter = Ext.extend(Ext.ux.grid.filter.Filter, { + /** + * @cfg {Boolean} defaultValue + * Set this to null if you do not want either option to be checked by default. Defaults to false. + */ + defaultValue : false, + /** + * @cfg {String} yesText + * Defaults to 'Yes'. + */ + yesText : 'Yes', + /** + * @cfg {String} noText + * Defaults to 'No'. + */ + noText : 'No', + + /** + * @private + * Template method that is to initialize the filter and install required menu items. + */ + init : function (config) { + var gId = Ext.id(); + this.options = [ + new Ext.menu.CheckItem({text: this.yesText, group: gId, checked: this.defaultValue === true}), + new Ext.menu.CheckItem({text: this.noText, group: gId, checked: this.defaultValue === false})]; + + this.menu.add(this.options[0], this.options[1]); + + for(var i=0; i<this.options.length; i++){ + this.options[i].on('click', this.fireUpdate, this); + this.options[i].on('checkchange', this.fireUpdate, this); + } + }, + + /** + * @private + * Template method that is to get and return the value of the filter. + * @return {String} The value of this filter + */ + getValue : function () { + return this.options[0].checked; + }, + + /** + * @private + * Template method that is to set the value of the filter. + * @param {Object} value The value to set the filter + */ + setValue : function (value) { + this.options[value ? 0 : 1].setChecked(true); + }, + + /** + * @private + * Template method that is to get and return serialized filter data for + * transmission to the server. + * @return {Object/Array} An object or collection of objects containing + * key value pairs representing the current configuration of the filter. + */ + getSerialArgs : function () { + var args = {type: 'boolean', value: this.getValue()}; + return args; + }, + + /** + * Template method that is to validate the provided Ext.data.Record + * against the filters configuration. + * @param {Ext.data.Record} record The record to validate + * @return {Boolean} true if the record is valid within the bounds + * of the filter, false otherwise. + */ + validateRecord : function (record) { + return record.get(this.dataIndex) == this.getValue(); + } +}); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/DateFilter.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/DateFilter.js new file mode 100644 index 0000000000000000000000000000000000000000..af8e3f12829830992726ada399c6ffddb5896aa5 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/DateFilter.js @@ -0,0 +1,313 @@ +/*! + * Ext JS Library 3.3.0 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * @class Ext.ux.grid.filter.DateFilter + * @extends Ext.ux.grid.filter.Filter + * Filter by a configurable Ext.menu.DateMenu + * <p><b><u>Example Usage:</u></b></p> + * <pre><code> +var filters = new Ext.ux.grid.GridFilters({ + ... + filters: [{ + // required configs + type: 'date', + dataIndex: 'dateAdded', + + // optional configs + dateFormat: 'm/d/Y', // default + beforeText: 'Before', // default + afterText: 'After', // default + onText: 'On', // default + pickerOpts: { + // any DateMenu configs + }, + + active: true // default is false + }] +}); + * </code></pre> + */ +Ext.ux.grid.filter.DateFilter = Ext.extend(Ext.ux.grid.filter.Filter, { + /** + * @cfg {String} afterText + * Defaults to 'After'. + */ + afterText : 'After', + /** + * @cfg {String} beforeText + * Defaults to 'Before'. + */ + beforeText : 'Before', + /** + * @cfg {Object} compareMap + * Map for assigning the comparison values used in serialization. + */ + compareMap : { + before: 'lt', + after: 'gt', + on: 'eq' + }, + /** + * @cfg {String} dateFormat + * The date format to return when using getValue. + * Defaults to 'm/d/Y'. + */ + dateFormat : 'm/d/Y', + + /** + * @cfg {Date} maxDate + * Allowable date as passed to the Ext.DatePicker + * Defaults to undefined. + */ + /** + * @cfg {Date} minDate + * Allowable date as passed to the Ext.DatePicker + * Defaults to undefined. + */ + /** + * @cfg {Array} menuItems + * The items to be shown in this menu + * Defaults to:<pre> + * menuItems : ['before', 'after', '-', 'on'], + * </pre> + */ + menuItems : ['before', 'after', '-', 'on'], + + /** + * @cfg {Object} menuItemCfgs + * Default configuration options for each menu item + */ + menuItemCfgs : { + selectOnFocus: true, + width: 125 + }, + + /** + * @cfg {String} onText + * Defaults to 'On'. + */ + onText : 'On', + + /** + * @cfg {Object} pickerOpts + * Configuration options for the date picker associated with each field. + */ + pickerOpts : {}, + + /** + * @private + * Template method that is to initialize the filter and install required menu items. + */ + init : function (config) { + var menuCfg, i, len, item, cfg, Cls; + + menuCfg = Ext.apply(this.pickerOpts, { + minDate: this.minDate, + maxDate: this.maxDate, + format: this.dateFormat, + listeners: { + scope: this, + select: this.onMenuSelect + } + }); + + this.fields = {}; + for (i = 0, len = this.menuItems.length; i < len; i++) { + item = this.menuItems[i]; + if (item !== '-') { + cfg = { + itemId: 'range-' + item, + text: this[item + 'Text'], + menu: new Ext.menu.DateMenu( + Ext.apply(menuCfg, { + itemId: item + }) + ), + listeners: { + scope: this, + checkchange: this.onCheckChange + } + }; + Cls = Ext.menu.CheckItem; + item = this.fields[item] = new Cls(cfg); + } + //this.add(item); + this.menu.add(item); + } + }, + + onCheckChange : function () { + this.setActive(this.isActivatable()); + this.fireEvent('update', this); + }, + + /** + * @private + * Handler method called when there is a keyup event on an input + * item of this menu. + */ + onInputKeyUp : function (field, e) { + var k = e.getKey(); + if (k == e.RETURN && field.isValid()) { + e.stopEvent(); + this.menu.hide(true); + return; + } + }, + + /** + * Handler for when the menu for a field fires the 'select' event + * @param {Object} date + * @param {Object} menuItem + * @param {Object} value + * @param {Object} picker + */ + onMenuSelect : function (menuItem, value, picker) { + var fields = this.fields, + field = this.fields[menuItem.itemId]; + + field.setChecked(true); + + if (field == fields.on) { + fields.before.setChecked(false, true); + fields.after.setChecked(false, true); + } else { + fields.on.setChecked(false, true); + if (field == fields.after && fields.before.menu.picker.value < value) { + fields.before.setChecked(false, true); + } else if (field == fields.before && fields.after.menu.picker.value > value) { + fields.after.setChecked(false, true); + } + } + this.fireEvent('update', this); + }, + + /** + * @private + * Template method that is to get and return the value of the filter. + * @return {String} The value of this filter + */ + getValue : function () { + var key, result = {}; + for (key in this.fields) { + if (this.fields[key].checked) { + result[key] = this.fields[key].menu.picker.getValue(); + } + } + return result; + }, + + /** + * @private + * Template method that is to set the value of the filter. + * @param {Object} value The value to set the filter + * @param {Boolean} preserve true to preserve the checked status + * of the other fields. Defaults to false, unchecking the + * other fields + */ + setValue : function (value, preserve) { + var key; + for (key in this.fields) { + if(value[key]){ + this.fields[key].menu.picker.setValue(value[key]); + this.fields[key].setChecked(true); + } else if (!preserve) { + this.fields[key].setChecked(false); + } + } + this.fireEvent('update', this); + }, + + /** + * @private + * Template method that is to return <tt>true</tt> if the filter + * has enough configuration information to be activated. + * @return {Boolean} + */ + isActivatable : function () { + var key; + for (key in this.fields) { + if (this.fields[key].checked) { + return true; + } + } + return false; + }, + + /** + * @private + * Template method that is to get and return serialized filter data for + * transmission to the server. + * @return {Object/Array} An object or collection of objects containing + * key value pairs representing the current configuration of the filter. + */ + getSerialArgs : function () { + var args = []; + for (var key in this.fields) { + if(this.fields[key].checked){ + args.push({ + type: 'date', + comparison: this.compareMap[key], + value: this.getFieldValue(key).format(this.dateFormat) + }); + } + } + return args; + }, + + /** + * Get and return the date menu picker value + * @param {String} item The field identifier ('before', 'after', 'on') + * @return {Date} Gets the current selected value of the date field + */ + getFieldValue : function(item){ + return this.fields[item].menu.picker.getValue(); + }, + + /** + * Gets the menu picker associated with the passed field + * @param {String} item The field identifier ('before', 'after', 'on') + * @return {Object} The menu picker + */ + getPicker : function(item){ + return this.fields[item].menu.picker; + }, + + /** + * Template method that is to validate the provided Ext.data.Record + * against the filters configuration. + * @param {Ext.data.Record} record The record to validate + * @return {Boolean} true if the record is valid within the bounds + * of the filter, false otherwise. + */ + validateRecord : function (record) { + var key, + pickerValue, + val = record.get(this.dataIndex); + + if(!Ext.isDate(val)){ + return false; + } + val = val.clearTime(true).getTime(); + + for (key in this.fields) { + if (this.fields[key].checked) { + pickerValue = this.getFieldValue(key).clearTime(true).getTime(); + if (key == 'before' && pickerValue <= val) { + return false; + } + if (key == 'after' && pickerValue >= val) { + return false; + } + if (key == 'on' && pickerValue != val) { + return false; + } + } + } + return true; + } +}); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/Filter.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/Filter.js new file mode 100644 index 0000000000000000000000000000000000000000..79f7883bcb743ca4886fcbea861ac023e292773d --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/Filter.js @@ -0,0 +1,185 @@ +/*! + * Ext JS Library 3.3.0 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +Ext.namespace('Ext.ux.grid.filter'); + +/** + * @class Ext.ux.grid.filter.Filter + * @extends Ext.util.Observable + * Abstract base class for filter implementations. + */ +Ext.ux.grid.filter.Filter = Ext.extend(Ext.util.Observable, { + /** + * @cfg {Boolean} active + * Indicates the initial status of the filter (defaults to false). + */ + active : false, + /** + * True if this filter is active. Use setActive() to alter after configuration. + * @type Boolean + * @property active + */ + /** + * @cfg {String} dataIndex + * The {@link Ext.data.Store} dataIndex of the field this filter represents. + * The dataIndex does not actually have to exist in the store. + */ + dataIndex : null, + /** + * The filter configuration menu that will be installed into the filter submenu of a column menu. + * @type Ext.menu.Menu + * @property + */ + menu : null, + /** + * @cfg {Number} updateBuffer + * Number of milliseconds to wait after user interaction to fire an update. Only supported + * by filters: 'list', 'numeric', and 'string'. Defaults to 500. + */ + updateBuffer : 500, + + constructor : function (config) { + Ext.apply(this, config); + + this.addEvents( + /** + * @event activate + * Fires when an inactive filter becomes active + * @param {Ext.ux.grid.filter.Filter} this + */ + 'activate', + /** + * @event deactivate + * Fires when an active filter becomes inactive + * @param {Ext.ux.grid.filter.Filter} this + */ + 'deactivate', + /** + * @event serialize + * Fires after the serialization process. Use this to attach additional parameters to serialization + * data before it is encoded and sent to the server. + * @param {Array/Object} data A map or collection of maps representing the current filter configuration. + * @param {Ext.ux.grid.filter.Filter} filter The filter being serialized. + */ + 'serialize', + /** + * @event update + * Fires when a filter configuration has changed + * @param {Ext.ux.grid.filter.Filter} this The filter object. + */ + 'update' + ); + Ext.ux.grid.filter.Filter.superclass.constructor.call(this); + + this.menu = new Ext.menu.Menu(); + this.init(config); + if(config && config.value){ + this.setValue(config.value); + this.setActive(config.active !== false, true); + delete config.value; + } + }, + + /** + * Destroys this filter by purging any event listeners, and removing any menus. + */ + destroy : function(){ + if (this.menu){ + this.menu.destroy(); + } + this.purgeListeners(); + }, + + /** + * Template method to be implemented by all subclasses that is to + * initialize the filter and install required menu items. + * Defaults to Ext.emptyFn. + */ + init : Ext.emptyFn, + + /** + * Template method to be implemented by all subclasses that is to + * get and return the value of the filter. + * Defaults to Ext.emptyFn. + * @return {Object} The 'serialized' form of this filter + * @methodOf Ext.ux.grid.filter.Filter + */ + getValue : Ext.emptyFn, + + /** + * Template method to be implemented by all subclasses that is to + * set the value of the filter and fire the 'update' event. + * Defaults to Ext.emptyFn. + * @param {Object} data The value to set the filter + * @methodOf Ext.ux.grid.filter.Filter + */ + setValue : Ext.emptyFn, + + /** + * Template method to be implemented by all subclasses that is to + * return <tt>true</tt> if the filter has enough configuration information to be activated. + * Defaults to <tt>return true</tt>. + * @return {Boolean} + */ + isActivatable : function(){ + return true; + }, + + /** + * Template method to be implemented by all subclasses that is to + * get and return serialized filter data for transmission to the server. + * Defaults to Ext.emptyFn. + */ + getSerialArgs : Ext.emptyFn, + + /** + * Template method to be implemented by all subclasses that is to + * validates the provided Ext.data.Record against the filters configuration. + * Defaults to <tt>return true</tt>. + * @param {Ext.data.Record} record The record to validate + * @return {Boolean} true if the record is valid within the bounds + * of the filter, false otherwise. + */ + validateRecord : function(){ + return true; + }, + + /** + * Returns the serialized filter data for transmission to the server + * and fires the 'serialize' event. + * @return {Object/Array} An object or collection of objects containing + * key value pairs representing the current configuration of the filter. + * @methodOf Ext.ux.grid.filter.Filter + */ + serialize : function(){ + var args = this.getSerialArgs(); + this.fireEvent('serialize', args, this); + return args; + }, + + /** @private */ + fireUpdate : function(){ + if (this.active) { + this.fireEvent('update', this); + } + this.setActive(this.isActivatable()); + }, + + /** + * Sets the status of the filter and fires the appropriate events. + * @param {Boolean} active The new filter state. + * @param {Boolean} suppressEvent True to prevent events from being fired. + * @methodOf Ext.ux.grid.filter.Filter + */ + setActive : function(active, suppressEvent){ + if(this.active != active){ + this.active = active; + if (suppressEvent !== true) { + this.fireEvent(active ? 'activate' : 'deactivate', this); + } + } + } +}); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/ListFilter.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/ListFilter.js new file mode 100644 index 0000000000000000000000000000000000000000..234e9e575126728c4eb208f369ec2987ce4a4139 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/ListFilter.js @@ -0,0 +1,176 @@ +/*! + * Ext JS Library 3.3.0 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * @class Ext.ux.grid.filter.ListFilter + * @extends Ext.ux.grid.filter.Filter + * <p>List filters are able to be preloaded/backed by an Ext.data.Store to load + * their options the first time they are shown. ListFilter utilizes the + * {@link Ext.ux.menu.ListMenu} component.</p> + * <p>Although not shown here, this class accepts all configuration options + * for {@link Ext.ux.menu.ListMenu}.</p> + * + * <p><b><u>Example Usage:</u></b></p> + * <pre><code> +var filters = new Ext.ux.grid.GridFilters({ + ... + filters: [{ + type: 'list', + dataIndex: 'size', + phpMode: true, + // options will be used as data to implicitly creates an ArrayStore + options: ['extra small', 'small', 'medium', 'large', 'extra large'] + }] +}); + * </code></pre> + * + */ +Ext.ux.grid.filter.ListFilter = Ext.extend(Ext.ux.grid.filter.Filter, { + + /** + * @cfg {Array} options + * <p><code>data</code> to be used to implicitly create a data store + * to back this list when the data source is <b>local</b>. If the + * data for the list is remote, use the <code>{@link #store}</code> + * config instead.</p> + * <br><p>Each item within the provided array may be in one of the + * following formats:</p> + * <div class="mdetail-params"><ul> + * <li><b>Array</b> : + * <pre><code> +options: [ + [11, 'extra small'], + [18, 'small'], + [22, 'medium'], + [35, 'large'], + [44, 'extra large'] +] + * </code></pre> + * </li> + * <li><b>Object</b> : + * <pre><code> +labelField: 'name', // override default of 'text' +options: [ + {id: 11, name:'extra small'}, + {id: 18, name:'small'}, + {id: 22, name:'medium'}, + {id: 35, name:'large'}, + {id: 44, name:'extra large'} +] + * </code></pre> + * </li> + * <li><b>String</b> : + * <pre><code> + * options: ['extra small', 'small', 'medium', 'large', 'extra large'] + * </code></pre> + * </li> + */ + /** + * @cfg {Boolean} phpMode + * <p>Adjust the format of this filter. Defaults to false.</p> + * <br><p>When GridFilters <code>@cfg encode = false</code> (default):</p> + * <pre><code> +// phpMode == false (default): +filter[0][data][type] list +filter[0][data][value] value1 +filter[0][data][value] value2 +filter[0][field] prod + +// phpMode == true: +filter[0][data][type] list +filter[0][data][value] value1, value2 +filter[0][field] prod + * </code></pre> + * When GridFilters <code>@cfg encode = true</code>: + * <pre><code> +// phpMode == false (default): +filter : [{"type":"list","value":["small","medium"],"field":"size"}] + +// phpMode == true: +filter : [{"type":"list","value":"small,medium","field":"size"}] + * </code></pre> + */ + phpMode : false, + /** + * @cfg {Ext.data.Store} store + * The {@link Ext.data.Store} this list should use as its data source + * when the data source is <b>remote</b>. If the data for the list + * is local, use the <code>{@link #options}</code> config instead. + */ + + /** + * @private + * Template method that is to initialize the filter and install required menu items. + * @param {Object} config + */ + init : function (config) { + this.dt = new Ext.util.DelayedTask(this.fireUpdate, this); + + // if a menu already existed, do clean up first + if (this.menu){ + this.menu.destroy(); + } + this.menu = new Ext.ux.menu.ListMenu(config); + this.menu.on('checkchange', this.onCheckChange, this); + }, + + /** + * @private + * Template method that is to get and return the value of the filter. + * @return {String} The value of this filter + */ + getValue : function () { + return this.menu.getSelected(); + }, + /** + * @private + * Template method that is to set the value of the filter. + * @param {Object} value The value to set the filter + */ + setValue : function (value) { + this.menu.setSelected(value); + this.fireEvent('update', this); + }, + + /** + * @private + * Template method that is to return <tt>true</tt> if the filter + * has enough configuration information to be activated. + * @return {Boolean} + */ + isActivatable : function () { + return this.getValue().length > 0; + }, + + /** + * @private + * Template method that is to get and return serialized filter data for + * transmission to the server. + * @return {Object/Array} An object or collection of objects containing + * key value pairs representing the current configuration of the filter. + */ + getSerialArgs : function () { + var args = {type: 'list', value: this.phpMode ? this.getValue().join(',') : this.getValue()}; + return args; + }, + + /** @private */ + onCheckChange : function(){ + this.dt.delay(this.updateBuffer); + }, + + + /** + * Template method that is to validate the provided Ext.data.Record + * against the filters configuration. + * @param {Ext.data.Record} record The record to validate + * @return {Boolean} true if the record is valid within the bounds + * of the filter, false otherwise. + */ + validateRecord : function (record) { + return this.getValue().indexOf(record.get(this.dataIndex)) > -1; + } +}); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/NumericFilter.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/NumericFilter.js new file mode 100644 index 0000000000000000000000000000000000000000..181af8dca268065eb2b78855dd555b68677f2b39 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/NumericFilter.js @@ -0,0 +1,202 @@ +/*! + * Ext JS Library 3.3.0 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * @class Ext.ux.grid.filter.NumericFilter + * @extends Ext.ux.grid.filter.Filter + * Filters using an Ext.ux.menu.RangeMenu. + * <p><b><u>Example Usage:</u></b></p> + * <pre><code> +var filters = new Ext.ux.grid.GridFilters({ + ... + filters: [{ + type: 'numeric', + dataIndex: 'price' + }] +}); + * </code></pre> + */ +Ext.ux.grid.filter.NumericFilter = Ext.extend(Ext.ux.grid.filter.Filter, { + + /** + * @cfg {Object} fieldCls + * The Class to use to construct each field item within this menu + * Defaults to:<pre> + * fieldCls : Ext.form.NumberField + * </pre> + */ + fieldCls : Ext.form.NumberField, + /** + * @cfg {Object} fieldCfg + * The default configuration options for any field item unless superseded + * by the <code>{@link #fields}</code> configuration. + * Defaults to:<pre> + * fieldCfg : {} + * </pre> + * Example usage: + * <pre><code> +fieldCfg : { + width: 150, +}, + * </code></pre> + */ + /** + * @cfg {Object} fields + * The field items may be configured individually + * Defaults to <tt>undefined</tt>. + * Example usage: + * <pre><code> +fields : { + gt: { // override fieldCfg options + width: 200, + fieldCls: Ext.ux.form.CustomNumberField // to override default {@link #fieldCls} + } +}, + * </code></pre> + */ + /** + * @cfg {Object} iconCls + * The iconCls to be applied to each comparator field item. + * Defaults to:<pre> +iconCls : { + gt : 'ux-rangemenu-gt', + lt : 'ux-rangemenu-lt', + eq : 'ux-rangemenu-eq' +} + * </pre> + */ + iconCls : { + gt : 'ux-rangemenu-gt', + lt : 'ux-rangemenu-lt', + eq : 'ux-rangemenu-eq' + }, + + /** + * @cfg {Object} menuItemCfgs + * Default configuration options for each menu item + * Defaults to:<pre> +menuItemCfgs : { + emptyText: 'Enter Filter Text...', + selectOnFocus: true, + width: 125 +} + * </pre> + */ + menuItemCfgs : { + emptyText: 'Enter Filter Text...', + selectOnFocus: true, + width: 125 + }, + + /** + * @cfg {Array} menuItems + * The items to be shown in this menu. Items are added to the menu + * according to their position within this array. Defaults to:<pre> + * menuItems : ['lt','gt','-','eq'] + * </pre> + */ + menuItems : ['lt', 'gt', '-', 'eq'], + + /** + * @private + * Template method that is to initialize the filter and install required menu items. + */ + init : function (config) { + // if a menu already existed, do clean up first + if (this.menu){ + this.menu.destroy(); + } + this.menu = new Ext.ux.menu.RangeMenu(Ext.apply(config, { + // pass along filter configs to the menu + fieldCfg : this.fieldCfg || {}, + fieldCls : this.fieldCls, + fields : this.fields || {}, + iconCls: this.iconCls, + menuItemCfgs: this.menuItemCfgs, + menuItems: this.menuItems, + updateBuffer: this.updateBuffer + })); + // relay the event fired by the menu + this.menu.on('update', this.fireUpdate, this); + }, + + /** + * @private + * Template method that is to get and return the value of the filter. + * @return {String} The value of this filter + */ + getValue : function () { + return this.menu.getValue(); + }, + + /** + * @private + * Template method that is to set the value of the filter. + * @param {Object} value The value to set the filter + */ + setValue : function (value) { + this.menu.setValue(value); + }, + + /** + * @private + * Template method that is to return <tt>true</tt> if the filter + * has enough configuration information to be activated. + * @return {Boolean} + */ + isActivatable : function () { + var values = this.getValue(); + for (key in values) { + if (values[key] !== undefined) { + return true; + } + } + return false; + }, + + /** + * @private + * Template method that is to get and return serialized filter data for + * transmission to the server. + * @return {Object/Array} An object or collection of objects containing + * key value pairs representing the current configuration of the filter. + */ + getSerialArgs : function () { + var key, + args = [], + values = this.menu.getValue(); + for (key in values) { + args.push({ + type: 'numeric', + comparison: key, + value: values[key] + }); + } + return args; + }, + + /** + * Template method that is to validate the provided Ext.data.Record + * against the filters configuration. + * @param {Ext.data.Record} record The record to validate + * @return {Boolean} true if the record is valid within the bounds + * of the filter, false otherwise. + */ + validateRecord : function (record) { + var val = record.get(this.dataIndex), + values = this.getValue(); + if (values.eq !== undefined && val != values.eq) { + return false; + } + if (values.lt !== undefined && val >= values.lt) { + return false; + } + if (values.gt !== undefined && val <= values.gt) { + return false; + } + return true; + } +}); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/StringFilter.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/StringFilter.js new file mode 100644 index 0000000000000000000000000000000000000000..d1af8e2edd20b14cd83128242dce4455a854b303 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/StringFilter.js @@ -0,0 +1,132 @@ +/*! + * Ext JS Library 3.3.0 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * @class Ext.ux.grid.filter.StringFilter + * @extends Ext.ux.grid.filter.Filter + * Filter by a configurable Ext.form.TextField + * <p><b><u>Example Usage:</u></b></p> + * <pre><code> +var filters = new Ext.ux.grid.GridFilters({ + ... + filters: [{ + // required configs + type: 'string', + dataIndex: 'name', + + // optional configs + value: 'foo', + active: true, // default is false + iconCls: 'ux-gridfilter-text-icon' // default + // any Ext.form.TextField configs accepted + }] +}); + * </code></pre> + */ +Ext.ux.grid.filter.StringFilter = Ext.extend(Ext.ux.grid.filter.Filter, { + + /** + * @cfg {String} iconCls + * The iconCls to be applied to the menu item. + * Defaults to <tt>'ux-gridfilter-text-icon'</tt>. + */ + iconCls : 'ux-gridfilter-text-icon', + + emptyText: 'Enter Filter Text...', + selectOnFocus: true, + width: 125, + + /** + * @private + * Template method that is to initialize the filter and install required menu items. + */ + init : function (config) { + Ext.applyIf(config, { + enableKeyEvents: true, + iconCls: this.iconCls, + listeners: { + scope: this, + keyup: this.onInputKeyUp + } + }); + + this.inputItem = new Ext.form.TextField(config); + this.menu.add(this.inputItem); + this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this); + }, + + /** + * @private + * Template method that is to get and return the value of the filter. + * @return {String} The value of this filter + */ + getValue : function () { + return this.inputItem.getValue(); + }, + + /** + * @private + * Template method that is to set the value of the filter. + * @param {Object} value The value to set the filter + */ + setValue : function (value) { + this.inputItem.setValue(value); + this.fireEvent('update', this); + }, + + /** + * @private + * Template method that is to return <tt>true</tt> if the filter + * has enough configuration information to be activated. + * @return {Boolean} + */ + isActivatable : function () { + return this.inputItem.getValue().length > 0; + }, + + /** + * @private + * Template method that is to get and return serialized filter data for + * transmission to the server. + * @return {Object/Array} An object or collection of objects containing + * key value pairs representing the current configuration of the filter. + */ + getSerialArgs : function () { + return {type: 'string', value: this.getValue()}; + }, + + /** + * Template method that is to validate the provided Ext.data.Record + * against the filters configuration. + * @param {Ext.data.Record} record The record to validate + * @return {Boolean} true if the record is valid within the bounds + * of the filter, false otherwise. + */ + validateRecord : function (record) { + var val = record.get(this.dataIndex); + + if(typeof val != 'string') { + return (this.getValue().length === 0); + } + + return val.toLowerCase().indexOf(this.getValue().toLowerCase()) > -1; + }, + + /** + * @private + * Handler method called when there is a keyup event on this.inputItem + */ + onInputKeyUp : function (field, e) { + var k = e.getKey(); + if (k == e.RETURN && field.isValid()) { + e.stopEvent(); + this.menu.hide(true); + return; + } + // restart the timer + this.updateTask.delay(this.updateBuffer); + } +}); diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/equals.png b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/equals.png new file mode 100644 index 0000000000000000000000000000000000000000..ea1dffbbd2e38cb1c0856b38fc7b2bb0b8ec5e20 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/equals.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/find.png b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/find.png new file mode 100644 index 0000000000000000000000000000000000000000..1547479646722bda4647df52cf3e8bc9b77428c6 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/find.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/greater_than.png b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/greater_than.png new file mode 100644 index 0000000000000000000000000000000000000000..edbf2fe705ab2cc79afff6afe9ac6e96d5da8c56 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/greater_than.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/less_than.png b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/less_than.png new file mode 100644 index 0000000000000000000000000000000000000000..5eda3a52f5f2d50789ce92815c861a3bd6974ca8 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/less_than.png differ diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_asc.gif b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_asc.gif new file mode 100644 index 0000000000000000000000000000000000000000..90064192d4bbb5a056e5b2214c79807dccb27846 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_asc.gif differ diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_desc.gif b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_desc.gif new file mode 100644 index 0000000000000000000000000000000000000000..7147bcacc91b8f9a2a9c0d73f88fe31f41245bd1 Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_desc.gif differ diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/ListMenu.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/ListMenu.js new file mode 100644 index 0000000000000000000000000000000000000000..c5162a4d5014df55b6fb52fc9d466a2ccd469bf7 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/ListMenu.js @@ -0,0 +1,177 @@ +/*! + * Ext JS Library 3.3.0 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +Ext.namespace('Ext.ux.menu'); + +/** + * @class Ext.ux.menu.ListMenu + * @extends Ext.menu.Menu + * This is a supporting class for {@link Ext.ux.grid.filter.ListFilter}. + * Although not listed as configuration options for this class, this class + * also accepts all configuration options from {@link Ext.ux.grid.filter.ListFilter}. + */ +Ext.ux.menu.ListMenu = Ext.extend(Ext.menu.Menu, { + /** + * @cfg {String} labelField + * Defaults to 'text'. + */ + labelField : 'text', + /** + * @cfg {String} paramPrefix + * Defaults to 'Loading...'. + */ + loadingText : 'Loading...', + /** + * @cfg {Boolean} loadOnShow + * Defaults to true. + */ + loadOnShow : true, + /** + * @cfg {Boolean} single + * Specify true to group all items in this list into a single-select + * radio button group. Defaults to false. + */ + single : false, + + constructor : function (cfg) { + this.selected = []; + this.addEvents( + /** + * @event checkchange + * Fires when there is a change in checked items from this list + * @param {Object} item Ext.menu.CheckItem + * @param {Object} checked The checked value that was set + */ + 'checkchange' + ); + + Ext.ux.menu.ListMenu.superclass.constructor.call(this, cfg = cfg || {}); + + if(!cfg.store && cfg.options){ + var options = []; + for(var i=0, len=cfg.options.length; i<len; i++){ + var value = cfg.options[i]; + switch(Ext.type(value)){ + case 'array': options.push(value); break; + case 'object': options.push([value.id, value[this.labelField]]); break; + case 'string': options.push([value, value]); break; + } + } + + this.store = new Ext.data.Store({ + reader: new Ext.data.ArrayReader({id: 0}, ['id', this.labelField]), + data: options, + listeners: { + 'load': this.onLoad, + scope: this + } + }); + this.loaded = true; + } else { + this.add({text: this.loadingText, iconCls: 'loading-indicator'}); + this.store.on('load', this.onLoad, this); + } + }, + + destroy : function () { + if (this.store) { + this.store.destroy(); + } + Ext.ux.menu.ListMenu.superclass.destroy.call(this); + }, + + /** + * Lists will initially show a 'loading' item while the data is retrieved from the store. + * In some cases the loaded data will result in a list that goes off the screen to the + * right (as placement calculations were done with the loading item). This adapter will + * allow show to be called with no arguments to show with the previous arguments and + * thus recalculate the width and potentially hang the menu from the left. + */ + show : function () { + var lastArgs = null; + return function(){ + if(arguments.length === 0){ + Ext.ux.menu.ListMenu.superclass.show.apply(this, lastArgs); + } else { + lastArgs = arguments; + if (this.loadOnShow && !this.loaded) { + this.store.load(); + } + Ext.ux.menu.ListMenu.superclass.show.apply(this, arguments); + } + }; + }(), + + /** @private */ + onLoad : function (store, records) { + var visible = this.isVisible(); + this.hide(false); + + this.removeAll(true); + + var gid = this.single ? Ext.id() : null; + for(var i=0, len=records.length; i<len; i++){ + var item = new Ext.menu.CheckItem({ + text: records[i].get(this.labelField), + group: gid, + checked: this.selected.indexOf(records[i].id) > -1, + hideOnClick: false}); + + item.itemId = records[i].id; + item.on('checkchange', this.checkChange, this); + + this.add(item); + } + + this.loaded = true; + + if (visible) { + this.show(); + } + this.fireEvent('load', this, records); + }, + + /** + * Get the selected items. + * @return {Array} selected + */ + getSelected : function () { + return this.selected; + }, + + /** @private */ + setSelected : function (value) { + value = this.selected = [].concat(value); + + if (this.loaded) { + this.items.each(function(item){ + item.setChecked(false, true); + for (var i = 0, len = value.length; i < len; i++) { + if (item.itemId == value[i]) { + item.setChecked(true, true); + } + } + }, this); + } + }, + + /** + * Handler for the 'checkchange' event from an check item in this menu + * @param {Object} item Ext.menu.CheckItem + * @param {Object} checked The checked value that was set + */ + checkChange : function (item, checked) { + var value = []; + this.items.each(function(item){ + if (item.checked) { + value.push(item.itemId); + } + },this); + this.selected = value; + + this.fireEvent('checkchange', item, checked); + } +}); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/RangeMenu.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/RangeMenu.js new file mode 100644 index 0000000000000000000000000000000000000000..539281a92fdbf8ab9e777a2dfe3a7772d1ab695d --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/RangeMenu.js @@ -0,0 +1,128 @@ +/*! + * Ext JS Library 3.3.0 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +Ext.ns('Ext.ux.menu'); + +/** + * @class Ext.ux.menu.RangeMenu + * @extends Ext.menu.Menu + * Custom implementation of Ext.menu.Menu that has preconfigured + * items for gt, lt, eq. + * <p><b><u>Example Usage:</u></b></p> + * <pre><code> + + * </code></pre> + */ +Ext.ux.menu.RangeMenu = Ext.extend(Ext.menu.Menu, { + + constructor : function (config) { + + Ext.ux.menu.RangeMenu.superclass.constructor.call(this, config); + + this.addEvents( + /** + * @event update + * Fires when a filter configuration has changed + * @param {Ext.ux.grid.filter.Filter} this The filter object. + */ + 'update' + ); + + this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this); + + var i, len, item, cfg, Cls; + + for (i = 0, len = this.menuItems.length; i < len; i++) { + item = this.menuItems[i]; + if (item !== '-') { + // defaults + cfg = { + itemId: 'range-' + item, + enableKeyEvents: true, + iconCls: this.iconCls[item] || 'no-icon', + listeners: { + scope: this, + keyup: this.onInputKeyUp + } + }; + Ext.apply( + cfg, + // custom configs + Ext.applyIf(this.fields[item] || {}, this.fieldCfg[item]), + // configurable defaults + this.menuItemCfgs + ); + Cls = cfg.fieldCls || this.fieldCls; + item = this.fields[item] = new Cls(cfg); + } + this.add(item); + } + }, + + /** + * @private + * called by this.updateTask + */ + fireUpdate : function () { + this.fireEvent('update', this); + }, + + /** + * Get and return the value of the filter. + * @return {String} The value of this filter + */ + getValue : function () { + var result = {}, key, field; + for (key in this.fields) { + field = this.fields[key]; + if (field.isValid() && String(field.getValue()).length > 0) { + result[key] = field.getValue(); + } + } + return result; + }, + + /** + * Set the value of this menu and fires the 'update' event. + * @param {Object} data The data to assign to this menu + */ + setValue : function (data) { + var key; + for (key in this.fields) { + this.fields[key].setValue(data[key] !== undefined ? data[key] : ''); + } + this.fireEvent('update', this); + }, + + /** + * @private + * Handler method called when there is a keyup event on an input + * item of this menu. + */ + onInputKeyUp : function (field, e) { + var k = e.getKey(); + if (k == e.RETURN && field.isValid()) { + e.stopEvent(); + this.hide(true); + return; + } + + if (field == this.fields.eq) { + if (this.fields.gt) { + this.fields.gt.setValue(null); + } + if (this.fields.lt) { + this.fields.lt.setValue(null); + } + } + else { + this.fields.eq.setValue(null); + } + + // restart the timer + this.updateTask.delay(this.updateBuffer); + } +}); diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/helpers.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..4c0b6a3a656606a08b18b813a256e214f380e567 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/helpers.js @@ -0,0 +1,182 @@ +/*************************************************************** + * Copyright notice + * + * (c) 2010 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! + ***************************************************************/ + +Ext.ns('TYPO3.Workspaces'); + +TYPO3.Workspaces.Helpers = { + /** + * Gets an form values object like {'element-1':on, 'element-2':on} and returns + * the checked results in an array like ['1', '2']. + * + * @param object values + * @param string elementPrefix + * @return array + */ + getElementIdsFromFormValues: function(values, elementPrefix) { + var results = []; + var pattern = new RegExp('^' + elementPrefix + '-' + '(.+)$'); + + Ext.iterate(values, function(key, value) { + if (value == 'on' && pattern.test(key)) { + results.push(RegExp.$1); + } + }); + + return results; + }, + + getSendToStageWindow: function(configuration) { + top.TYPO3.Windows.close('sendToStageWindow'); + return top.TYPO3.Windows.showWindow({ + id: 'sendToStageWindow', + title: configuration.title, + items: [ + { + xtype: 'form', + id: 'sendToStageForm', + width: '100%', + bodyStyle: 'padding: 5px 5px 3px 5px; border-width: 0; margin-bottom: 7px;', + items: configuration.items + } + ], + buttons: [ + { + text: TYPO3.l10n.localize('ok'), + handler: configuration.executeHandler + }, + { + text: TYPO3.l10n.localize('cancel'), + handler: function(event) { + top.TYPO3.Windows.close('sendToStageWindow'); + } + } + ] + }); + }, + + getElementsArrayOfSelection: function(selection) { + var elements = []; + + Ext.each(selection, function(item) { + var element = { + table: item.data.table, + t3ver_oid: item.data.t3ver_oid, + uid: item.data.uid + } + elements.push(element); + }); + + return elements; + }, + + getElementsArrayOfSelectionForIntegrityCheck: function(selection) { + var elements = []; + + Ext.each(selection, function(item) { + var element = { + table: item.data.table, + liveId: item.data.t3ver_oid, + versionId: item.data.uid + } + elements.push(element); + }); + + return elements; + }, + + getHistoryWindow: function(configuration) { + top.TYPO3.Windows.close('historyWindow'); + return top.TYPO3.Windows.showWindow({ + id: 'historyWindow', + title: 'Record History', + stateful: false, + modal: false, + + autoHeight: true, + boxMaxHeight: 500, + width: 700, + + buttons: [ + { + text: TYPO3.l10n.localize('ok'), + handler: function(event) { + top.TYPO3.Windows.close('historyWindow'); + } + } + ], + + items: [ + { + xtype: 'grid', + + border : false, + loadMask : true, + stripeRows: true, + autoHeight: true, + style: 'min-height: 100px', + + viewConfig: { + forceFit: true + }, + + store: { + xtype: 'directstore', + autoLoad: true, + autoDestroy: true, + reader: new Ext.data.JsonReader({ + idProperty : 'id', + root : 'data', + totalProperty : 'total', + fields: [ + { name: 'datetime' }, + { name: 'user' }, + { name: 'differences' } + ] + }), + proxy: new Ext.data.DirectProxy({ + directFn : TYPO3.Workspaces.ExtDirect.getHistory + }), + listeners: { + beforeload: function(store, options) { + store.setBaseParam('table', configuration.table); + store.setBaseParam('liveId', configuration.liveId); + store.setBaseParam('versionId', configuration.versionId); + } + } + }, + + colModel: new Ext.grid.ColumnModel({ + columns: [ + { width: 30, id: 'datetime', header: 'Date' }, + { width: 20, id: 'user', header: 'User', dataIndex: 'user' }, + { id: 'differences', header: 'Differences', dataIndex: 'differences' } + ] + }) + } + ] + }); + } +}; \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/preview.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/preview.js new file mode 100644 index 0000000000000000000000000000000000000000..a83a4062b585d82fbb8d57c8667a0badb08224c7 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/preview.js @@ -0,0 +1,404 @@ +/*************************************************************** + * Copyright notice + * + * (c) 2010 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! + ***************************************************************/ + +Ext.onReady(function() { + + Ext.state.Manager.setProvider(new TYPO3.state.ExtDirectProvider({ + key: 'moduleData.Workspaces.States', + autoRead: false + })); + + if (Ext.isObject(TYPO3.settings.Workspaces.States)) { + Ext.state.Manager.getProvider().initState(TYPO3.settings.Workspaces.States); + } + // late binding of ExtDirect + TYPO3.Workspaces.MainStore.proxy = new Ext.data.DirectProxy({ + directFn : TYPO3.Workspaces.ExtDirect.getWorkspaceInfos + }); + + var iconClsChecked = 't3-icon t3-icon-status t3-icon-status-status t3-icon-status-checked'; + var iconClsEmpty = 't3-icon t3-icon-empty t3-icon-empty-empty t3-icon-empty'; + var viewMode = 0; + var changePreviewMode = function(config, mode) { + var visual = Ext.getCmp('wsVisualWrap'); + if ((typeof mode != 'undefined') && (mode != viewMode)) { + viewMode = mode; + visual.remove(0); + visual.add(Ext.apply(config, {})); + }; + visual.doLayout(); + } + + var sliderSetup = { + layout: 'fit', + x: 0, y:0, + anchor: '100% 100%', + autoScroll: true, + items: [{ + layout: 'absolute', + id: 'visualPanel', + items: [{ + x: 0, y:0, + anchor: '100% 100%', + id: 'wsContainer', + layout: 'absolute', + autoScroll: false, + items:[{ + xtype: 'iframePanel', + x: 0, y:0, + id: 'wsPanel', + doMask: false, + src: wsUrl, + autoScroll: false + }] + },{ + x: 0, y:0, + anchor: '100% 0%', + id: 'liveContainer', + layout: 'absolute', + bodyStyle: 'height:0px;border-bottom: 2px solid red;', + autoScroll: false, + items:[{ + xtype: 'iframePanel', + x: 0, y:0, + id: 'livePanel', + doMask: false, + src: liveUrl, + autoScroll: false + }] + }] + }] + }; + var hboxSetup = { + layout: 'hbox', + x: 0, y:0, + anchor: '100% 100%', + layoutConfig: { + align : 'stretch', + pack : 'start' + }, + id: 'visualPanel-hbox', + items: [{ + xtype: 'iframePanel', + x: 0, y:0, + id: 'wsPanel-hbox', + doMask: false, + src: wsUrl, + autoScroll: false, + flex: 1 + },{ + xtype: 'iframePanel', + x: 0, y:0, + id: 'livePanel-hbox', + doMask: false, + src: liveUrl, + autoScroll: false, + flex: 1 + }] + }; + var vboxSetup = { + layout: 'vbox', + x: 0, y:0, + anchor: '100% 100%', + layoutConfig: { + align : 'stretch', + pack : 'start' + }, + id: 'visualPanel-vbox', + items: [{ + xtype: 'iframePanel', + x: 0, y:0, + id: 'wsPanel-vbox', + doMask: false, + src: wsUrl, + autoScroll: false, + flex: 1 + },{ + xtype: 'iframePanel', + x: 0, y:0, + id: 'livePanel-vbox', + doMask: false, + src: liveUrl, + autoScroll: false, + flex: 1 + }] + }; + + var viewport = new Ext.Viewport({ + layout: 'border', + items: [{ + xtype: 'tabpanel', + region: 'center', // a center region is ALWAYS required for border layout + id: 'preview', + activeTab: 0, + plugins : [{ + ptype : 'Ext.ux.plugins.TabStripContainer', + id: 'controls', + width: 930, + items: [ + { + xtype: 'panel', + width: 360, + items: [{ + xtype: 'panel', + id: 'slider', + layout: 'hbox', + items: [ + { + xtype: 'button', + id: 'sizeSliderButtonLive', + cls: 'sliderButton', + text: TYPO3.l10n.localize('livePreview'), + tooltip: TYPO3.l10n.localize('livePreviewDetail'), + width: 100, + listeners: { + click: { + fn: function () { + Ext.getCmp('sizeSlider').setValue(0); + } + } + } + }, + { + xtype: 'slider', + id: 'sizeSlider', + margins: '0 10 0 10', + maxValue: 100, + minValue: 0, + value: 100, + width: 150, + flex: 1, + listeners: { + change: { + fn: function resizeFromValue(slider, newValue, thumb) { + var height = Ext.getCmp('wsPanel').getHeight(); + Ext.getCmp('liveContainer').setHeight(height * (100 - newValue) / 100); + } + } + } + }, + { + xtype: 'button', + id: 'sizeSliderButtonWorkspace', + cls: 'sliderButton', + text: TYPO3.l10n.localize('workspacePreview'), + tooltip: TYPO3.l10n.localize('workspacePreviewDetail'), + width: 100, + listeners: { + click: { + fn: function () { + Ext.getCmp('sizeSlider').setValue(100); + } + } + } + } + ] + }] + }, { + xtype: 'buttongroup', + id: 'stageButtonGroup', + columns: 4, + width: 400, + items: [{ + text: TYPO3.l10n.localize('nextStage').substr(0, 35), + tooltip: TYPO3.l10n.localize('nextStage'), + xtype: 'button', + iconCls: 'x-btn-text', + id: 'feToolbarButtonNextStage', + hidden: TYPO3.settings.Workspaces.disableNextStageButton, + listeners: { + click: { + fn: function () { + TYPO3.Workspaces.Actions.sendPageToNextStage(); + } + } + } + }, { + text: TYPO3.l10n.localize('previousStage').substr(0, 35), + tooltip: TYPO3.l10n.localize('previousStage'), + xtype: 'button', + iconCls: 'x-btn-text', + id: 'feToolbarButtonPreviousStage', + hidden: TYPO3.settings.Workspaces.disablePreviousStageButton, + listeners: { + click: { + fn: function () { + TYPO3.Workspaces.Actions.sendPageToPrevStage(); + } + } + } + }, { + text: TYPO3.l10n.localize('discard'), + iconCls: 'x-btn-text', + xtype: 'button', + id: 'feToolbarButtonDiscardStage', + hidden: TYPO3.settings.Workspaces.disableDiscardStageButton, + listeners: { + click: { + fn: function () { + TYPO3.Workspaces.Actions.discardPage(); + } + } + } + }, { + xtype: 'button', + iconCls: 'x-btn-icon t3-icon t3-icon-actions t3-icon-actions-system t3-icon-system-options-view', + id: 'visual-mode-options', + menu: { + id: 'visual-mode-selector', + stateful: true, + stateId: 'WorkspacePreviewModeSelect', + stateEvents: ['itemclick'], + items: [{ + text: TYPO3.l10n.localize('modeSlider'), + id: 'visual-mode-selector-slider', + checked: false, + group: 'mode', + hidden: TYPO3.settings.Workspaces.SplitPreviewModes.indexOf('slider') == -1, + checkHandler: modeChange + },{ + text: TYPO3.l10n.localize('modeVbox'), + id: 'visual-mode-selector-vbox', + checked: false, + group: 'mode', + hidden: TYPO3.settings.Workspaces.SplitPreviewModes.indexOf('vbox') == -1, + checkHandler: modeChange + + },{ + text: TYPO3.l10n.localize('modeHbox'), + id: 'visual-mode-selector-hbox', + checked: false, + group: 'mode', + hidden: TYPO3.settings.Workspaces.SplitPreviewModes.indexOf('hbox') == -1, + checkHandler: modeChange + }], + getState:function() { + return {viewMode: viewMode}; + }, + applyState: function(state) { + modeChange(null, true, viewMode); + } + } + }] + } + ] + }], + items: [{ + title: TYPO3.l10n.localize('visualPreview'), + id: 'wsVisual', + layout: 'fit', + anchor: '100% 100%', + listeners: { + activate: function () { + if (Ext.isObject(top.Ext.getCmp('slider'))) { + top.Ext.getCmp('slider').show(); + top.Ext.getCmp('visual-mode-options').show(); + TYPO3.Workspaces.ExtDirectActions.updateStageChangeButtons(TYPO3.settings.Workspaces.id, TYPO3.Workspaces.Actions.updateStageChangeButtons); + } + } + }, + items: [{ + id: 'wsVisualWrap', + layout: 'absolute', + anchor: '100% 100%', + x: 0, y:0, + items: [sliderSetup] + }] + },{ + title: TYPO3.l10n.localize('listView'), + id: 'wsSettings', + layout: 'fit', + listeners: { + activate: function () { + top.Ext.getCmp('slider').hide(); + top.Ext.getCmp('visual-mode-options').hide(); + top.Ext.getCmp('feToolbarButtonNextStage').hide(); + top.Ext.getCmp('feToolbarButtonPreviousStage').hide(); + top.Ext.getCmp('feToolbarButtonDiscardStage').hide(); + } + }, + items: [{ + xtype: 'iframePanel', + id: 'settingsPanel', + doMask: false, + src: wsSettingsUrl + }] + }] + }] + }); + + function modeChange(item, checked, mode) { + if (checked) { + var id , + ids = ['visual-mode-selector-slider', 'visual-mode-selector-hbox', 'visual-mode-selector-vbox'], + slider = Ext.getCmp('slider'), + itemSlider = Ext.getCmp('visual-mode-selector-slider'), + itemHbox = Ext.getCmp('visual-mode-selector-hbox'), + itemVbox = Ext.getCmp('visual-mode-selector-vbox'); + + if (item) { + mode = ids.indexOf(item.id); + } + + Ext.select('#visual-mode-selector ul li a img.t3-icon-status-checked').removeClass(iconClsChecked.split(" ")); + + var splitPreviewModes = TYPO3.settings.Workspaces.SplitPreviewModes; + if (splitPreviewModes.length == 1) { + Ext.getCmp('visual-mode-options').hide(); + } + + if (splitPreviewModes.indexOf('vbox') == -1 && mode === 2) { + mode = 0 + } + if (splitPreviewModes.indexOf('slider') == -1 && mode === 0) { + mode = 1 + } + if (splitPreviewModes.indexOf('hbox') == -1 && mode === 1) { + mode = 2 + } + + if (mode === 0) { + changePreviewMode(sliderSetup, mode); + slider.show(); + itemSlider.setIconClass(iconClsChecked); + itemHbox.setIconClass(iconClsEmpty); + itemVbox.setIconClass(iconClsEmpty); + } else if (mode === 1) { + changePreviewMode(vboxSetup, mode); + slider.hide(); + itemSlider.setIconClass(iconClsEmpty); + itemHbox.setIconClass(iconClsChecked); + itemVbox.setIconClass(iconClsEmpty); + } else if (mode === 2) { + changePreviewMode(hboxSetup, mode); + slider.hide(); + itemSlider.setIconClass(iconClsEmpty); + itemHbox.setIconClass(iconClsEmpty); + itemVbox.setIconClass(iconClsChecked); + } + } + } +}); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/toolbar.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/toolbar.js new file mode 100644 index 0000000000000000000000000000000000000000..f1ff51fc490dd172faec00ff43163f615bfea2a7 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/toolbar.js @@ -0,0 +1,316 @@ +/*************************************************************** + * Copyright notice + * + * (c) 2010 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! + ***************************************************************/ + +Ext.ns('TYPO3.Workspaces'); + +TYPO3.Workspaces.Toolbar = {}; + +TYPO3.Workspaces.Toolbar.search = new Ext.app.SearchField({ + store: TYPO3.Workspaces.MainStore, + trigger1Class : 't3-icon t3-icon-actions t3-icon-actions-input t3-icon-input-clear t3-tceforms-input-clearer', + trigger2Class : 't3-icon t3-icon-actions t3-icon-actions-system t3-icon-system-tree-search-open', + width: 200 +}); + +TYPO3.Workspaces.Toolbar.selectActionStore = new Ext.data.DirectStore({ + storeId : 'stagesService', + root : 'data', + totalProperty : 'total', + idProperty : 'uid', + fields : [ + {name : 'uid'}, + {name : 'title'} + ], + listeners : { + load : function(store, records) { + if (records.length == 0) { + TYPO3.Workspaces.Toolbar.selectStateActionCombo.hide(); + } else { + TYPO3.Workspaces.Toolbar.selectStateActionCombo.show(); + TYPO3.Workspaces.WorkspaceGrid.colModel.setHidden(0, false); + } + } + } +}); + +TYPO3.Workspaces.Toolbar.selectStateActionCombo = new Ext.form.ComboBox({ + width: 150, + listWidth: 350, + lazyRender: true, + valueField: 'uid', + displayField: 'title', + mode: 'local', + emptyText: TYPO3.l10n.localize('chooseAction'), + selectOnFocus: true, + disabled : true, + hidden : true, // we hide it by default and show it in case there are any actions available + triggerAction: 'all', + editable: false, + forceSelection: true, + store: TYPO3.Workspaces.Toolbar.selectActionStore, + listeners: { + 'select' : function () { + var selection = TYPO3.Workspaces.WorkspaceGrid.getSelectionModel().getSelections(); + var nextStage = this.getValue(); + + // Use integrity check since "publish execute" stage is effective + if (nextStage == -20) { + var parameters = { + type: 'selection', + selection: TYPO3.Workspaces.Helpers.getElementsArrayOfSelectionForIntegrityCheck(selection) + }; + + TYPO3.Workspaces.Actions.checkIntegrity(parameters, function() { + TYPO3.Workspaces.Actions.sendToSpecificStageWindow(selection, nextStage); + }); + } else { + TYPO3.Workspaces.Actions.sendToSpecificStageWindow(selection, nextStage); + } + } + } +}); + +TYPO3.Workspaces.Toolbar.selectMassActionStore = new Ext.data.DirectStore({ + storeId : 'stagesService', + root : 'data', + totalProperty : 'total', + idProperty : 'action', + fields : [ + {name : 'action'}, + {name : 'title'} + ], + listeners : { + load : function(store, records) { + if (records.length == 0 || TYPO3.settings.Workspaces.singleView === '1') { + TYPO3.Workspaces.Toolbar.selectStateMassActionCombo.hide(); + } else { + TYPO3.Workspaces.Toolbar.selectStateMassActionCombo.show(); + } + } + } +}); + +TYPO3.Workspaces.Toolbar.selectStateMassActionCombo = new Ext.form.ComboBox({ + width: 150, + lazyRender: true, + valueField: 'action', + displayField: 'title', + mode: 'local', + emptyText: TYPO3.l10n.localize('chooseMassAction'), + selectOnFocus: true, + triggerAction: 'all', + editable: false, + hidden : true, // we hide it by default and show it in case there are any actions available + forceSelection: true, + store: TYPO3.Workspaces.Toolbar.selectMassActionStore, + listeners: { + 'select' : function (combo, record) { + var label = ''; + var affectWholeWorkspaceWarning = TYPO3.l10n.localize('tooltip.affectWholeWorkspace'); + var language = TYPO3.Workspaces.MainStore.baseParams.language; + var checkIntegrity = false; + + switch (record.data.action) { + case 'publish': + label = TYPO3.l10n.localize('tooltip.publishAll'); + checkIntegrity = true; + break; + case 'swap': + label = TYPO3.l10n.localize('tooltip.swapAll'); + checkIntegrity = true; + break; + case 'discard': + label = TYPO3.l10n.localize('tooltip.discardAll'); + break; + } + top.TYPO3.Windows.close('executeMassActionWindow'); + + var configuration = { + id: 'executeMassActionWindow', + title: TYPO3.l10n.localize('window.massAction.title'), + items: [ + { + xtype: 'form', + id: 'executeMassActionForm', + width: '100%', + html: label + '<br /><br />' + affectWholeWorkspaceWarning, + bodyStyle: 'padding: 5px 5px 3px 5px; border-width: 0; margin-bottom: 7px;' + }, + { + xtype: 'progress', + id: 'executeMassActionProgressBar', + autoWidth: true, + autoHeight: true, + hidden: true, + value: 0 + } + ], + buttons: [ + { + id: 'executeMassActionOkButton', + data: record.data, + scope: this, + text: TYPO3.l10n.localize('ok'), + disabled:false, + handler: function(event) { + TYPO3.Workspaces.Actions.triggerMassAction( + event.data.action, + language + ); + } + }, + { + id: 'executeMassActionCancleButton', + scope: this, + text: TYPO3.l10n.localize('cancel'), + handler: function() { + top.TYPO3.Windows.close('executeMassActionWindow'); + // if clicks during action - this also interrupts the running process -- not the nices way but efficient + top.TYPO3.ModuleMenu.App.reloadFrames(); + } + } + ] + }; + + if (checkIntegrity && language != 'all') { + var parameters = { + type: 'all', + language: language + }; + TYPO3.Workspaces.Actions.checkIntegrity(parameters, function() { + top.TYPO3.Windows.showWindow(configuration); + }); + } else { + top.TYPO3.Windows.showWindow(configuration); + } + } + } +}); + +TYPO3.Workspaces.Toolbar.Pager = new Ext.PagingToolbar({ + store : TYPO3.Workspaces.MainStore, + pageSize : 30, + displayInfo: false, + plugins : [ TYPO3.Workspaces.Configuration.GridFilters ] +}); + +/**************************************************** + * Depth menu + ****************************************************/ +TYPO3.Workspaces.Toolbar.depthFilter = new Ext.form.ComboBox({ + width: 150, + lazyRender: true, + valueField: 'depth', + displayField: 'label', + id: 'depthSelector', + mode: 'local', + emptyText: TYPO3.l10n.localize('depth'), + selectOnFocus: true, + triggerAction: 'all', + editable: false, + forceSelection: true, + hidden: (TYPO3.settings.Workspaces.singleView === '1'), + store: new Ext.data.SimpleStore({ + autoLoad: true, + fields: ['depth','label'], + data : [ + ['0', TYPO3.l10n.localize('depth_0')], + ['1', TYPO3.l10n.localize('depth_1')], + ['2', TYPO3.l10n.localize('depth_2')], + ['3', TYPO3.l10n.localize('depth_3')], + ['4', TYPO3.l10n.localize('depth_4')], + ['999', TYPO3.l10n.localize('depth_infi')] + ] + }), + value: 999, + listeners: { + 'select': { + fn: function(cmp, rec, index) { + var depth = rec.get('depth'); + TYPO3.Workspaces.MainStore.setBaseParam('depth', depth); + TYPO3.Workspaces.MainStore.load({ + params: { + wsId: 0 + } + }); + } + } + } +}); + +TYPO3.Workspaces.Toolbar.LanguageSelector = new Ext.form.ComboBox({ + width: 150, + listWidth: 350, + lazyRender: true, + valueField: 'uid', + displayField: 'title', + mode: 'local', + emptyText: TYPO3.l10n.localize('language.selectLanguage'), + selectOnFocus: true, + triggerAction: 'all', + editable: false, + forceSelection: true, + tpl: '<tpl for="."><div class="x-combo-list-item"><span class="{cls}"> </span> {title}</div></tpl>', + store: new Ext.data.DirectStore({ + storeId: 'languages', + root: 'data', + totalProperty: 'total', + idProperty: 'uid', + fields: [ + {name : 'uid'}, + {name : 'title'}, + {name : 'cls'} + ], + listeners: { + load: function() { + TYPO3.Workspaces.Toolbar.LanguageSelector.setValue(TYPO3.settings.Workspaces.language); + } + } + }), + listeners: { + select: function (comboBox, record, index) { + TYPO3.Workspaces.ExtDirectActions.saveLanguageSelection(this.getValue()); + TYPO3.Workspaces.MainStore.setBaseParam('language', this.getValue()); + TYPO3.Workspaces.MainStore.load(); + } + } +}); + +TYPO3.Workspaces.Toolbar.FullTopToolbar = [ + TYPO3.Workspaces.Toolbar.depthFilter, + '-', + TYPO3.Workspaces.Toolbar.LanguageSelector, + {xtype: 'tbfill'}, + TYPO3.Workspaces.Toolbar.search +]; + +TYPO3.Workspaces.Toolbar.FullBottomBar = [ + (TYPO3.settings.Workspaces.isLiveWorkspace == true) ? {hidden: true} : TYPO3.Workspaces.Toolbar.selectStateActionCombo, + (TYPO3.settings.Workspaces.isLiveWorkspace == true) ? {hidden: true} : '-', + (TYPO3.settings.Workspaces.isLiveWorkspace == true) ? {hidden: true} : TYPO3.Workspaces.Toolbar.selectStateMassActionCombo, + {xtype: 'tbfill'}, + TYPO3.Workspaces.Toolbar.Pager +]; diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/workspacemenu.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/workspacemenu.js new file mode 100644 index 0000000000000000000000000000000000000000..bf239a4223297f3878e594ffb7510241087254ce --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/workspacemenu.js @@ -0,0 +1,171 @@ +/*************************************************************** + * Copyright notice + * + * (c) 2008-2010 Ingo Renner <ingo@typo3.org> + * (c) 2010 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! + ***************************************************************/ + +Ext.ns('TYPO3', 'TYPO3.configuration'); +/** + * class to handle the workspace menu + * + * $Id$ + */ +var WorkspaceMenu = Class.create({ + + /** + * registers for resize event listener and executes on DOM ready + */ + initialize: function() { + + Ext.onReady(function() { + Event.observe( + window, 'resize', + function() { TYPO3BackendToolbarManager.positionMenu('workspace-selector-menu'); } + ); + if (top.TYPO3.configuration.inWorkspace == 1) { + Ext.getBody().addClass('typo3-in-workspace'); + this.updateTopBar(top.TYPO3.Workspaces.workspaceTitle); + } else { + Ext.getBody().removeClass('typo3-in-workspace'); + } + TYPO3BackendToolbarManager.refreshAll(); + Event.observe('workspace-selector-menu', 'click', this.toggleMenu); + if(Ext.get('goToWsModule')) { + Event.observe('goToWsModule', 'click', this.goToWorkspaceModule.bind(this)); + } + + // observe all clicks on workspace links in the menu + $$('#workspace-selector-menu li a.ws').each(function(element) { + Event.observe(element, 'click', this.switchWorkspace.bind(this)); + }.bindAsEventListener(this)); + + }, this); + }, + + /** + * toggles the visibility of the menu and places it under the toolbar icon + */ + toggleMenu: function(event) { + var toolbarItem = $$('#workspace-selector-menu > a')[0]; + var menu = $$('#workspace-selector-menu .toolbar-item-menu')[0]; + toolbarItem.blur(); + + if (!toolbarItem.hasClassName('toolbar-item-active')) { + toolbarItem.addClassName('toolbar-item-active'); + Effect.Appear(menu, {duration: 0.2}); + TYPO3BackendToolbarManager.hideOthers(toolbarItem); + } else { + toolbarItem.removeClassName('toolbar-item-active'); + Effect.Fade(menu, {duration: 0.1}); + } + + if (event) { + Event.stop(event); + } + }, + + /** + * redirects the user to the workspace module + */ + goToWorkspaceModule: function(event) { + top.goToModule('web_WorkspacesWorkspaces'); + this.toggleMenu(event); + }, + + /** + * switches the workspace, reloads the module menu, and the content frame + */ + switchWorkspace: function(event) { + var clickedElement = Event.element(event); + var workspaceId = clickedElement.identify().substring(3); + + TYPO3.Ajax.ExtDirect.ToolbarMenu.setWorkspace({'workSpaceId': workspaceId }, function(response) { + if (!response.id) { + response.id = 0; + } + + TYPO3BackendWorkspaceMenu.performWorkspaceSwitch(response.id, response.title); + + // when in web module reload, otherwise send the user to the web module + if (currentModuleLoaded.startsWith('web_')) { + top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree(); + top.TYPO3.ModuleMenu.App.reloadFrames(); + } else { + if (TYPO3.configuration.pageModule) { + top.TYPO3.ModuleMenu.App.showModule(TYPO3.configuration.pageModule); + } + } + + // reload the module menu + TYPO3ModuleMenu.refreshMenu(); + }); + + TYPO3BackendWorkspaceMenu.toggleMenu(event); + }, + + performWorkspaceSwitch: function(id, title) { + top.TYPO3.Workspaces.workspaceTitle = title; + top.TYPO3.configuration.inWorkspace = id === 0 ? 0 : 1; + if (top.TYPO3.configuration.inWorkspace == 1) { + Ext.getBody().addClass('typo3-in-workspace'); + this.updateTopBar(top.TYPO3.Workspaces.workspaceTitle); + } else { + Ext.getBody().removeClass('typo3-in-workspace'); + this.updateTopBar(); + } + + TYPO3BackendToolbarManager.refreshAll(); + + // first remove all checks, then set the check in front of the selected workspace + var stateActiveClass = 't3-icon t3-icon-status t3-icon-status-status t3-icon-status-checked'; + var stateInactiveClass = 't3-icon t3-icon-empty t3-icon-empty-empty t3-icon-empty'; + + // remove "selected" class and checkmark + $$('#workspace-selector-menu li.selected a span.t3-icon-status-checked')[0].removeClassName(stateActiveClass).addClassName(stateInactiveClass); + $$('#workspace-selector-menu li.selected')[0].removeClassName('selected'); + + // add "selected" class and checkmark + $$('#ws-' + id)[0].down().removeClassName(stateInactiveClass).addClassName(stateActiveClass); + $$('#ws-' + id)[0].up().addClassName('selected'); + }, + + updateTopBar: function(workspaceTitle) { + if (Ext.get('typo3-topbar-workspaces-title')) { + Ext.get('typo3-topbar-workspaces-title').remove(); + } + + if (workspaceTitle && workspaceTitle.length) { + var userItem; + if (Ext.select('#username a').elements.length) { + userItem = Ext.select('#username a'); + } else { + userItem = Ext.select ('#username'); + } + userItem.insertHtml('beforeEnd', '<span id="typo3-topbar-workspaces-title">@' + Ext.util.Format.htmlEncode(workspaceTitle) + '</span>') + } + } + +}); + +var TYPO3BackendWorkspaceMenu = new WorkspaceMenu(); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/workspaces.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/workspaces.js new file mode 100644 index 0000000000000000000000000000000000000000..fe8a17edac6dbe011c3960043f18bd253154a852 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/workspaces.js @@ -0,0 +1,81 @@ +/*************************************************************** + * Copyright notice + * + * (c) 2010 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! + ***************************************************************/ + +Ext.ns('TYPO3.Workspaces'); + +TYPO3.Workspaces.App = { + + init : function() { + TYPO3.Workspaces.WorkspaceGrid.initColModel(); + TYPO3.Workspaces.WorkspaceGrid.render('workspacegrid'); + + TYPO3.Workspaces.MainStore.load(); + TYPO3.Workspaces.Toolbar.selectActionStore.load(); + TYPO3.Workspaces.Toolbar.selectMassActionStore.load(); + TYPO3.Workspaces.Toolbar.LanguageSelector.getStore().load(); + } +}; + +Ext.onReady(function() { + Ext.state.Manager.setProvider(new TYPO3.state.ExtDirectProvider({ + key: 'moduleData.Workspaces.States', + autoRead: false + })); + + if (Ext.isObject(TYPO3.settings.Workspaces.States)) { + Ext.state.Manager.getProvider().initState(TYPO3.settings.Workspaces.States); + } + + // Quicktips initialisieren + Ext.QuickTips.init(); + + // rearrange columns in grid + TYPO3.Workspaces.Actions.loadColModel(TYPO3.Workspaces.WorkspaceGrid); + + // late binding of ExtDirect + TYPO3.Workspaces.Toolbar.selectMassActionStore.proxy = new Ext.data.DirectProxy({ + directFn : TYPO3.Workspaces.ExtDirectMassActions.getMassStageActions + }); + // late binding of ExtDirect + TYPO3.Workspaces.Toolbar.selectActionStore.proxy = new Ext.data.DirectProxy({ + directFn : TYPO3.Workspaces.ExtDirect.getStageActions + }); + // late binding of ExtDirect + TYPO3.Workspaces.Toolbar.LanguageSelector.getStore().proxy = new Ext.data.DirectProxy({ + directFn : TYPO3.Workspaces.ExtDirect.getSystemLanguages + }); + + TYPO3.Workspaces.RowDetail.rowDataStore.proxy = new Ext.data.DirectProxy({ + directFn: TYPO3.Workspaces.ExtDirect.getRowDetails + }); + // late binding of ExtDirect + TYPO3.Workspaces.MainStore.proxy = new Ext.data.DirectProxy({ + directFn : TYPO3.Workspaces.ExtDirect.getWorkspaceInfos + }); + // fire grid + var WS = new TYPO3.Workspaces.App.init(); + +}); \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/StyleSheet/module.css b/typo3/sysext/workspaces/Resources/Public/StyleSheet/module.css new file mode 100644 index 0000000000000000000000000000000000000000..1797ae94e97f053034ce8d41aad1b4054e5a3476 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/StyleSheet/module.css @@ -0,0 +1,213 @@ + +ul.x-tab-strip.x-tab-strip-top { + width: 100%; +} +ul.x-tab-strip.x-tab-strip-top li.last { + float: right; +} +span.item-state-modified { + color: #f78f25; +} +span.item-state-moved { + color: #457fb8; +} +span.item-state-new { + color: #3c9934; +} +span.item-state-hidden { + color: #abaaaa; +} +span.item-state-deleted { + color: #000000; + text-decoration: line-through; +} +.legend { + margin: 5px; + height: 18px; + color: #888888; +} +.legend dd, .legend dt { + display: inline; + font-size: 11px; + line-height: 13px; + overflow: hidden; +} +.legend dd span { + display: inline-block; + padding: 4px 4px; +} +div.typo3-dyntabmenu-divs { + border-top: 0px none; + padding: 5px 5px; + padding-bottom: 10px; + overflow: hidden; +} +.x-toolbar { + background:none !important; +} +/* fixes positioning of toolbar in IE6 */ +* html .x-panel-tbar { + border: 1px none; +} +.x-grid3 { + background: none !important; +} +.x-grid3-row-expanded { + background-color: #ececec; +} +.x-grid3-row-selected { + color: #4D4D4D; +} +#typo3-mod-php div.typo3-noDoc { + margin: 0px 0px; +} +#typo3-mod-php div.typo3-noDoc #typo3-docbody { + padding: 0px 0px; + top: 0px; +} +.icon-hidden { + display: none; + visibility: hidden; +} +div.x-grid3-row img.x-action-col-icon { + display:none; +} + +div.x-grid3-row-over img.x-action-col-icon, +div.x-grid3-row img.x-action-col-icon.t3-visible { + display:inline-block; +} + +div.t3-workspaces-foldoutWrapper { + padding: 10px; + margin-left: 40px; + background-color: #FFFFFF; + background-image: -moz-linear-gradient(center top , #ececec 0px, #f7f7f7 200px); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0, #ececec), color-stop(0.3, #f7f7f7)); + background-image: linear-gradient(center top, #ececec 0px, #f7f7f7 200px); + background-repeat: repeat-x; + color: #606060; +} +.x-grid3-row-selected div.t3-workspaces-foldoutWrapper { + background-image: -moz-linear-gradient(center top , #dedede 0px, #f7f7f7 200px); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0, #dedede), color-stop(200px, #f7f7f7)); + background-image: linear-gradient(center top, #dedede 0px, #f7f7f7 200px); + background-repeat: repeat-x; +} +div.t3-workspaces-foldoutWrapper table { + border-collapse: collapse; + width: 100%; +} +div.t3-workspaces-foldoutWrapper tr.header { + background-color: #B5B5B5; + background-image: -moz-linear-gradient(center top , #7f7f7f 10%, #5b5b5b 100%); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0.1, #7f7f7f), color-stop(1, #5b5b5b)); + background-image: linear-gradient(center top, #7f7f7f 10%, #5b5b5b 100%); + background-repeat: repeat-x; +} +div.t3-workspaces-foldoutWrapper tr.header th { + padding: 4px 8px; + color: #FFFFFF; + line-height: 15px; +} + +.t3-workspaces-foldout-subheaderLeft, +.t3-workspaces-foldout-subheaderRight { + padding: 15px 0 10px 0; + margin: 10px; +} +.t3-workspaces-foldout-td-contentDiffLeft .t3-workspaces-foldout-contentDiff-container, +.t3-workspaces-foldout-td-contentDiffRight .t3-workspaces-foldout-contentDiff-container { + padding-top: 10px; + border-top: 1px solid #cdcdcd; +} +.x-grid3-row .t3-workspaces-foldout-td-contentDiffLeft, +.x-grid3-row .t3-workspaces-foldout-subheaderLeft { + padding-right: 10px; +} +.x-grid3-row .t3-workspaces-foldout-td-contentDiffRight, +.x-grid3-row .t3-workspaces-foldout-subheaderRight { + padding-left: 10px; +} +.x-grid3-row .t3-workspaces-foldout-subheaderLeft .t3-workspaces-foldout-subheader-container { + padding-bottom: 10px; + border-bottom: 1px solid #cdcdcd; +} +table.t3-workspaces-foldout-contentDiff { + padding: 8px 8px; + table-layout: auto; + background-color: #ffffff; +} +table.t3-workspaces-foldout-contentDiff th { + padding: 8px 0 8px 8px; + width: 80px; +} +table.t3-workspaces-foldout-contentDiff td { + padding: 8px 8px 8px 0; + line-height: 1.3em; +} +table.t3-workspaces-foldout-contentDiff .diff-r { + text-decoration: line-through; +} +div.t3-workspaces-foldoutWrapper td.char_select_profile_stats { + padding-right: 10px; +} +div.t3-workspaces-comments { + background-color: #dedede; + padding: 10px 10px 10px 10px; +} +div.t3-workspaces-comments-singleComment { + overflow: hidden; + margin-bottom: 10px; + position: relative; +} +div.t3-workspaces-comments-singleComment:last-child { + margin: 0; +} +div .t3-workspaces-comments-singleComment-author { + width: 60px; + margin: 10px 0; + position: absolute; + left: 0px; + top: 0px; + font-weight: bold; + overflow: hidden; +} +div .t3-workspaces-comments-singleComment-content-wrapper { + background: url(../Images/workspaces-comments-arrow.gif) left 10px no-repeat; + margin-left: 70px; +} +div .t3-workspaces-comments-singleComment-content-date { + font-size: 10px; +} +div .t3-workspaces-comments-singleComment-content { + background-color: #ffffff; + padding: 10px 10px; + margin-left: 10px; +} +div .t3-workspaces-comments-singleComment-content-title { + padding: 8px 0 8px 0; + font-weight: bold; +} + +.typo3-workspaces-row-disabled .x-grid3-td-checker { + visibility: hidden; +} + +div.x-grid3-row img.t3-icon-extensions-workspaces { + display: inline-block !important; + width: 17px; + visibility: hidden; +} + +div.x-grid3-row-over img.t3-icon-extensions-workspaces { + visibility: visible; +} + +img.t3-icon-workspaces-sendtonextstage { + background-position: 0 4px !important; +} + +img.t3-icon-workspaces-sendtoprevstage { + background-position: 0 4px !important; +} \ No newline at end of file diff --git a/typo3/sysext/workspaces/Resources/Public/StyleSheet/preview.css b/typo3/sysext/workspaces/Resources/Public/StyleSheet/preview.css new file mode 100644 index 0000000000000000000000000000000000000000..2ecc4d89ff2ef9e738d50ec26dca86b135aee8c7 --- /dev/null +++ b/typo3/sysext/workspaces/Resources/Public/StyleSheet/preview.css @@ -0,0 +1,381 @@ +/** + * Top bar + */ + +/** + * Tabs + */ + +/* panel containing the tabs */ +.x-tab-panel-header { + background-color: #3f3f3f; + background-image: url('../../../../../../typo3/sysext/t3skin/extjs/images/backgrounds/topbar.png'); + background-image: -moz-linear-gradient(center top, #494949 0%, #373737 91%, #343434 92%, #2A2A2A 100%); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0, #494949), color-stop(0.91, #373737), color-stop(0.92, #343434), color-stop(1, #2a2a2a)); + background-repeat: repeat-x; + border: none; + padding-bottom: 0; + padding-top: 9px; +} + +.x-tab-strip-wrap { + background: url('../Images/typo3-logo.png') no-repeat 16px 0; + padding-left: 140px; +} + +/* normal tab */ +ul.x-tab-strip li { + margin-left: 4px; +} + +ul.x-tab-strip-top { + border-bottom: 0; +} + +/* reset ExtJS "no skin" nonsense */ +.x-tab-strip-top .x-tab-right, +.x-tab-strip-top .x-tab-strip-over .x-tab-right { + background-position: 0 0; +} + +/* we don't need the active tab to be 1px below the inactive */ +.x-tab-strip-top .x-tab-strip-active .x-tab-right { + margin-bottom: 0; +} + +.x-tab-strip span.x-tab-strip-text, +.x-tab-strip-top .x-tab-strip-active .x-tab-right span.x-tab-strip-text { + padding-bottom: 4px; +} + +/* normal tab styling */ +.x-tab-strip .x-tab-right { + background-color: #707171; + background-image: -moz-linear-gradient(center top, #707171 0%, #474747 85%, #363636 100%); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0, #707171), color-stop(0.85, #474747), color-stop(1, #363636)); + filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#707171', EndColorStr='#474747'); /* IE6, IE7 */ + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#707171', EndColorStr='#474747')"; /* IE8 */ + -moz-border-radius-topleft: 3px; + -moz-border-radius-topright: 3px; + -webkit-border-top-left-radius: 3px; + -webkit-border-top-right-radius: 3px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + +} + +/* container surrounding text */ +.x-tab-strip-inner { + padding: 4px; +} + +/* hover tab */ +.x-tab-strip-over .x-tab-right { + background-color: #707171; + background-image: -moz-linear-gradient(center top, #888888 0%, #474747 85%, #363636 100%); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0, #888888), color-stop(0.85, #474747), color-stop(1, #363636)); + filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#888888', EndColorStr='#474747'); /* IE6, IE7 */ + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#888888', EndColorStr='#474747')"; /* IE8 */ +} + +/* active tab */ +.x-tab-strip-active .x-tab-right { + background-color: #989898; + background-image: -moz-linear-gradient(center top, #989898 0%, #6c6c6c 85%, #474747 100%); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0, #989898), color-stop(0.85, #6c6c6c), color-stop(1, #474747)); + filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#989898', EndColorStr='#6c6c6c'); /* IE6, IE7 */ + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#989898', EndColorStr='#6c6c6c')"; /* IE8 */ +} + +/* tab label */ +.x-tab-strip span.x-tab-strip-text { + color: #ffffff; +} + +/* text in tabs should not be italic.. why should it be? */ +.x-tab-strip em { + font-style: normal; +} + +/** + * Slider + */ +#controls { + padding-top: 5px; +} +#slider { + padding-top: 5px; +} + +/* remove default ExtJS border */ +.x-panel-body { + border: none; +} + +.x-slider-horz .x-slider-thumb { + background-image: url('../Images/slider-thumb.png'); + height: 21px; + padding: 0 2px; + top: 0; + width: 7px; +} + +.x-slider-horz .x-slider-thumb-over, .x-slider-horz .x-slider-thumb-drag { + background-position: -7px -21px; +} + +.x-slider-horz .x-slider-inner { + background-image: url('../Images/slider-bg.png'); +} + +#visual-mode-selector { + list-style: none; + background-color: #f9f9f9; + border: 1px solid #abb2bc; + border-top: none; +} + +#visual-mode-selector td { + text-align: left; +} + +#visual-mode-selector td button { + font-size: 11px; + line-height: 12px; + text-decoration: none; +} + +#visual-mode-options { + display: block; + height: 20px; + margin: 0px 0 0 10px; +} +#visual-mode-options.x-btn-menu-active { + background-color: #f9f9f9; + border: 1px solid #abb2bc; + border-bottom: none; +} +#visual-mode-options .x-btn-arrow { + padding-right: 2px; +} + +#visual-mode-options .x-btn-text { + font-size: 11px; +} +#visual-mode-options.x-btn-menu-active .x-btn-text { + color: black; +} + +#visual-mode-toolbar { + border:none; +} + +/** + * Preview panel + */ +.x-panel-body-noheader { + border-top: 0; +} + +.x-tip { + background-color: #ffffc7; + border: 1px solid #cccccc; + -moz-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.3); + -ms-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.3); + box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.3); +} + +/* the mask for dialogs */ +.ext-el-mask { + background-color: #000000; + -moz-opacity: 0.75; + opacity: 0.75; + filter:alpha(opacity=75); +} + +.x-mask-loading { + border: none; +} + +.x-mask-loading div { + background-image: url("../../../../t3skin/images/spinner/big-f0f0f0.gif"); + background-position: top center; + border: none; + color: #828282; + padding-top: 40px; +} + +.x-window-tc { + background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/top-bottom.png); +} +.x-window-tl { + background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/left-corners.png); +} +.x-window-tr { + background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/right-corners.png); +} +.x-window-bc { + background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/top-bottom.png); +} +.x-window-bl { + background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/left-corners.png); +} +.x-window-br { + background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/right-corners.png); +} + +.x-window-ml { + background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/left-right.png); +} +.x-window-mr { + background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/window/left-right.png); +} + +.x-window-mc { + border:1px solid #A2AAB8; + background:#e8e8e8; +} + +.x-window-tl .x-window-header { + color: #FFFFFF; + font: bold 10px verdana,arial,tahoma,verdana,sans-serif; + padding: 4px 0; +} +.x-window-draggable, .x-window-draggable .x-window-header-text { + cursor: move; +} + +.x-window-tl .x-window-header { + color:#fff; + font:bold 10px verdana, arial,tahoma,verdana,sans-serif; + padding:4px 0 4px 0; +} + +.x-window-mc { + border-color:#A2AAB8; + font: normal 10px verdana, arial,tahoma,helvetica,sans-serif; + background-color:#e7e7e7; +} + +.x-window-maximized .x-window-tc { + background-color:#fff; +} + +.x-window-bbar .x-toolbar { + border-top-color:#A2AAB8; +} + +.x-form-text, textarea.x-form-field { + background-color: #FFFFFF; + background-image: none; + border-color: #B5B8C8; +} + +.x-btn { + color: #FFF; +} +.t3-icon-system-options-view { + float: right; +} +#feToolbarButtonNextStage.x-btn, #feToolbarButtonPreviousStage.x-btn, #feToolbarButtonDiscardStage.x-btn { + background-image: url('../Images/button_approve.png'); + background-repeat: repeat-x; + border: 1px solid #7c7c7c; + -moz-border-radius: 1px; + -webkit-border-radius: 1px; + border-radius: 1px; + height: 13px; + line-height: 8px; + margin-right: 10px; + font-size: 13px; + margin-top:0px; +} +#feToolbarButtonNextStage.x-btn .x-btn-text, #feToolbarButtonPreviousStage.x-btn .x-btn-text, #feToolbarButtonDiscardStage.x-btn .x-btn-text { + color: #FFF; + font-size: 11px; + line-height: 8px; + height: 13px; + padding: 0 3px 0 3px; +} +#feToolbarButtonPreviousStage.x-btn .x-btn-text { + color:#7c7c7c; +} +#feToolbarButtonPreviousStage.x-btn { + background-image: url('../Images/button_reject.png'); +} +#feToolbarButtonDiscardStage.x-btn { + background-image: url('../Images/button_discard.png'); +} +#sendToStageWindow .x-btn { + background-color: #d5d5d5; + background-image: url('../../../../../../typo3/sysext/t3skin/extjs/images/backgrounds/button.png'); + background-repeat: repeat-x; + background-image: -moz-linear-gradient(center top, #f6f6f6 10%, #d5d5d5 90%); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0.1, #f6f6f6), color-stop(0.9, #d5d5d5)); + background-image: linear-gradient(center top, #f6f6f6 10%, #d5d5d5 90%); + border: 1px solid #7c7c7c; + -moz-border-radius: 1px; + -webkit-border-radius: 1px; + border-radius: 1px; + color: #434343; +} +#sendToStageWindow .x-btn-pressed, +#sendToStageWindow .x-btn-over, +#sendToStageWindow .x-btn-icon.x-btn-over { + background-color: #bdbcbc; + background-image: url('../../../../../../typo3/sysext/t3skin/extjs/images/backgrounds/button-hover.png'); + background-image: -moz-linear-gradient(center top, #f6f6f6 10%, #bdbcbc 90%); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0.1, #f6f6f6), color-stop(0.9, #bdbcbc)); + background-image: linear-gradient(center top, #f6f6f6 10%, #bdbcbc 90%); + border-color: #737f91; + color: #1e1e1e; +} + +#sendToStageWindow .x-btn-over .x-btn-mc em.x-btn-split, +#sendToStageWindow .x-btn-click .x-btn-mc em.x-btn-split, +#sendToStageWindow .x-btn-menu-active .x-btn-mc em.x-btn-split, +#sendToStageWindow .x-btn-pressed .x-btn-mc em.x-btn-split { + background-image:url(../../../../../../typo3/sysext/t3skin/extjs/images/button/s-arrow-o.gif); +} + +.x-tool { + background-image: url("../../../../../../typo3/sysext/t3skin/extjs/images/panel/tool-sprites.gif"); +} + +.x-tool-close { + background-position: 0 0; +} +.x-tool-close-over { + background-position: -15px 0; +} + +/* text */ +.x-btn.sliderButton .x-btn-text { + color: #A0A0A0; + font-style: normal; +} + +/* alignment of text in Button "Live" */ +#sizeSliderButtonLive .x-btn-mc { + text-align: right; +} + +/* alignment of text in Button "Workspace" */ +#sizeSliderButtonWorkspace .x-btn-mc { + text-align: left; +} +.x-panel-header { + border: none; + font-weight: bold; + padding-left:0px; +} + +.x-window-dlg .x-btn { + background-color: #D5D5D5; + background-image: -moz-linear-gradient(center top , #F6F6F6 10%, #D5D5D5 90%); + background-repeat: repeat-x; + border-radius: 1px 1px 1px 1px; + border: 1px solid #7C7C7C; + color: #434343; +} \ No newline at end of file diff --git a/typo3/sysext/workspaces/Tests/Functional/Service/WorkspaceTest.php b/typo3/sysext/workspaces/Tests/Functional/Service/WorkspaceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..442263a519010136e1b45adf23e3ea085ccfd630 --- /dev/null +++ b/typo3/sysext/workspaces/Tests/Functional/Service/WorkspaceTest.php @@ -0,0 +1,197 @@ +<?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 test + * + * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces) + */ +class WorkspacesServiceTest extends \tx_phpunit_database_testcase { + + /** + + */ + public function setUp() { + $GLOBALS['BE_USER']->user['admin'] = 1; + $this->createDatabase(); + $db = $this->useTestDatabase(); + $this->importStdDB(); + $this->importExtensions(array('cms', 'version', 'workspaces')); + } + + /** + + */ + public function tearDown() { + $this->cleanDatabase(); + $this->dropDatabase(); + $GLOBALS['TYPO3_DB']->sql_select_db(TYPO3_db); + } + + /** + * @test + */ + public function emptyWorkspaceReturnsEmptyArray() { + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultWorkspaces.xml'); + $service = new \TYPO3\CMS\Workspaces\Service\WorkspaceService(); + $result = $service->selectVersionsInWorkspace(90); + $this->assertTrue(empty($result), 'The workspace 90 contains no changes and the result was supposed to be empty'); + $this->assertTrue(is_array($result), 'Even the empty result from workspace 90 is supposed to be an array'); + } + + /** + * @test + */ + public function versionsFromSpecificWorkspaceCanBeFound() { + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultWorkspaces.xml'); + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultPages.xml'); + $service = new \TYPO3\CMS\Workspaces\Service\WorkspaceService(); + $result = $service->selectVersionsInWorkspace(91, 0, -99, 2); + $this->assertTrue(is_array($result), 'The result from workspace 91 is supposed to be an array'); + $this->assertEquals(1, sizeof($result['pages']), 'The result is supposed to contain one version for this page in workspace 91'); + $this->assertEquals(102, $result['pages'][0]['uid'], 'Wrong workspace overlay record picked'); + $this->assertEquals(1, $result['pages'][0]['livepid'], 'Real pid wasn\'t resolved correctly'); + } + + /** + * @test + */ + public function versionsFromAllWorkspaceCanBeFound() { + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultWorkspaces.xml'); + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultPages.xml'); + $service = new \TYPO3\CMS\Workspaces\Service\WorkspaceService(); + $result = $service->selectVersionsInWorkspace(\TYPO3\CMS\Workspaces\Service\WorkspaceService::SELECT_ALL_WORKSPACES, 0, -99, 2); + $this->assertTrue(is_array($result), 'The result from workspace 91 is supposed to be an array'); + $this->assertEquals(2, sizeof($result['pages']), 'The result is supposed to contain one version for this page in workspace 91'); + } + + /** + * @test + */ + public function versionsCanBeFoundRecursive() { + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultWorkspaces.xml'); + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultPages.xml'); + $service = new \TYPO3\CMS\Workspaces\Service\WorkspaceService(); + $result = $service->selectVersionsInWorkspace(91, 0, -99, 1, 99); + $this->assertTrue(is_array($result), 'The result from workspace 91 is supposed to be an array'); + $this->assertEquals(4, sizeof($result['pages']), 'The result is supposed to contain four versions for this page in workspace 91'); + } + + /** + * @test + */ + public function versionsCanBeFilteredToSpecificStage() { + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultWorkspaces.xml'); + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultPages.xml'); + $service = new \TYPO3\CMS\Workspaces\Service\WorkspaceService(); + // testing stage 1 + $result = $service->selectVersionsInWorkspace(91, 0, 1, 1, 99); + $this->assertTrue(is_array($result), 'The result from workspace 91 is supposed to be an array'); + $this->assertEquals(2, sizeof($result['pages']), 'The result is supposed to contain two versions for this page in workspace 91'); + $this->assertEquals(102, $result['pages'][0]['uid'], 'First records is supposed to have the uid 102'); + $this->assertEquals(105, $result['pages'][1]['uid'], 'First records is supposed to have the uid 105'); + // testing stage 2 + $result = $service->selectVersionsInWorkspace(91, 0, 2, 1, 99); + $this->assertTrue(is_array($result), 'The result from workspace 91 is supposed to be an array'); + $this->assertEquals(2, sizeof($result['pages']), 'The result is supposed to contain two versions for this page in workspace 91'); + $this->assertEquals(104, $result['pages'][0]['uid'], 'First records is supposed to have the uid 106'); + $this->assertEquals(106, $result['pages'][1]['uid'], 'First records is supposed to have the uid 106'); + } + + /** + * @test + */ + public function versionsCanBeFilteredToSpecificLifecycleStep() { + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultWorkspaces.xml'); + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultPages.xml'); + $service = new \TYPO3\CMS\Workspaces\Service\WorkspaceService(); + // testing all "draft" records + $result = $service->selectVersionsInWorkspace(91, 1, -99, 1, 99); + $this->assertTrue(is_array($result), 'The result from workspace 91 is supposed to be an array'); + $this->assertEquals(2, sizeof($result['pages']), 'The result is supposed to contain three versions for this page in workspace 91'); + // testing all "archive" records + $result = $service->selectVersionsInWorkspace(91, 2, -99, 1, 99); + $this->assertEquals(2, sizeof($result['pages']), 'The result is supposed to contain two versions for this page in workspace 91'); + // testing both types records + $result = $service->selectVersionsInWorkspace(91, 0, -99, 1, 99); + $this->assertEquals(4, sizeof($result['pages']), 'The result is supposed to contain two versions for this page in workspace 91'); + } + + /** + * The only change which we could find here actually moved away from this + * branch of the tree - therefore we're not supposed to find anything here + * + * @test + */ + public function movedElementsCanNotBeFoundAtTheirOrigin() { + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultWorkspaces.xml'); + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbMovedContent.xml'); + // Test if the placeholder can be found when we ask using recursion (same result) + $service = new \TYPO3\CMS\Workspaces\Service\WorkspaceService(); + $result = $service->selectVersionsInWorkspace(91, 0, -99, 2, 99); + $this->assertEquals(0, sizeof($result['pages']), 'Changes should not show up in this branch of the tree within workspace 91'); + $this->assertEquals(0, sizeof($result['tt_content']), 'Changes should not show up in this branch of the tree within workspace 91'); + } + + /** + * @test + */ + public function movedElementsCanBeFoundAtTheirDestination() { + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultWorkspaces.xml'); + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbMovedContent.xml'); + // Test if the placeholder can be found when we ask using recursion (same result) + $service = new \TYPO3\CMS\Workspaces\Service\WorkspaceService(); + $result = $service->selectVersionsInWorkspace(91, 0, -99, 5, 99); + $this->assertEquals(1, sizeof($result['pages']), 'Wrong amount of page versions found within workspace 91'); + $this->assertEquals(103, $result['pages'][0]['uid'], 'Wrong move-to pointer found for page 3 in workspace 91'); + $this->assertEquals(5, $result['pages'][0]['wspid'], 'Wrong workspace-pointer found for page 3 in workspace 91'); + $this->assertEquals(2, $result['pages'][0]['livepid'], 'Wrong live-pointer found for page 3 in workspace 91'); + $this->assertEquals(1, sizeof($result['tt_content']), 'Wrong amount of tt_content versions found within workspace 91'); + $this->assertEquals(106, $result['tt_content'][0]['uid'], 'Wrong move-to pointer found for page 3 in workspace 91'); + $this->assertEquals(7, $result['tt_content'][0]['wspid'], 'Wrong workspace-pointer found for page 3 in workspace 91'); + $this->assertEquals(2, $result['tt_content'][0]['livepid'], 'Wrong live-pointer found for page 3 in workspace 91'); + } + + /** + * @test + */ + public function movedElementsCanBeFoundUsingTheirLiveUID() { + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbDefaultWorkspaces.xml'); + $this->importDataSet(dirname(__FILE__) . '/fixtures/dbMovedContent.xml'); + // Test if the placeholder can be found when we ask using recursion (same result) + $service = new \TYPO3\CMS\Workspaces\Service\WorkspaceService(); + $result = $service->selectVersionsInWorkspace(91, 0, -99, 3, 99); + $this->assertEquals(1, sizeof($result), 'Wrong amount of versions found within workspace 91'); + $this->assertEquals(1, sizeof($result['pages']), 'Wrong amount of page versions found within workspace 91'); + $this->assertEquals(103, $result['pages'][0]['uid'], 'Wrong move-to pointer found for page 3 in workspace 91'); + } + +} + + +?> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbDefaultPages.xml b/typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbDefaultPages.xml new file mode 100644 index 0000000000000000000000000000000000000000..09f1ccc6bb36d6b4496789a459bebfee07d7b0b3 --- /dev/null +++ b/typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbDefaultPages.xml @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="utf-8"?> +<dataset> + <pages> + <uid>1</uid> + <pid>0</pid> + <title>Root</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>2</uid> + <pid>1</pid> + <title>Dummy 1-2</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>3</uid> + <pid>2</pid> + <title>Dummy 1-2-3</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>4</uid> + <pid>3</pid> + <title>Dummy 1-2-3-4</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>5</uid> + <pid>1</pid> + <title>Dummy 1-5</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>6</uid> + <pid>5</pid> + <title>Dummy 1-5-6</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + + <pages> + <uid>102</uid> + <pid>-1</pid> + <title>Dummy WS 91</title> + <t3ver_oid>2</t3ver_oid> + <t3ver_stage>1</t3ver_stage> + <t3ver_wsid>91</t3ver_wsid> + <t3ver_count>0</t3ver_count> + <deleted>0</deleted> + </pages> + <pages> + <uid>202</uid> + <pid>-1</pid> + <title>Dummy WS 92</title> + <t3ver_oid>2</t3ver_oid> + <t3ver_stage>1</t3ver_stage> + <t3ver_wsid>92</t3ver_wsid> + <t3ver_count>0</t3ver_count> + <deleted>0</deleted> + </pages> + <pages> + <uid>104</uid> + <pid>-1</pid> + <title>Dummy WS 91</title> + <t3ver_oid>4</t3ver_oid> + <t3ver_stage>2</t3ver_stage> + <t3ver_wsid>91</t3ver_wsid> + <t3ver_count>0</t3ver_count> + <deleted>0</deleted> + </pages> + <pages> + <uid>105</uid> + <pid>-1</pid> + <title>Dummy WS 91</title> + <t3ver_oid>6</t3ver_oid> + <t3ver_stage>1</t3ver_stage> + <t3ver_wsid>91</t3ver_wsid> + <t3ver_count>1</t3ver_count> + <deleted>0</deleted> + </pages> + <pages> + <uid>106</uid> + <pid>-1</pid> + <title>Dummy WS 91</title> + <t3ver_oid>6</t3ver_oid> + <t3ver_stage>2</t3ver_stage> + <t3ver_wsid>91</t3ver_wsid> + <t3ver_count>1</t3ver_count> + <deleted>0</deleted> + </pages> +</dataset> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbDefaultWorkspaces.xml b/typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbDefaultWorkspaces.xml new file mode 100644 index 0000000000000000000000000000000000000000..fc2e75f2fe43ac0c5319917178b608ff5fa1b169 --- /dev/null +++ b/typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbDefaultWorkspaces.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<dataset> + <sys_workspace> + <uid>90</uid> + <pid>0</pid> + <title>Empty Workspace</title> + </sys_workspace> + <sys_workspace> + <uid>91</uid> + <pid>0</pid> + <title>Filled Workspace #1</title> + </sys_workspace> + <sys_workspace> + <uid>92</uid> + <pid>0</pid> + <title>Filled Workspace #2</title> + </sys_workspace> +</dataset> \ No newline at end of file diff --git a/typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbMovedContent.xml b/typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbMovedContent.xml new file mode 100644 index 0000000000000000000000000000000000000000..ad2c46a9dbbe86896b45d9732947ed70c562d4cc --- /dev/null +++ b/typo3/sysext/workspaces/Tests/Functional/Service/fixtures/dbMovedContent.xml @@ -0,0 +1,143 @@ +<?xml version="1.0" encoding="utf-8"?> +<dataset> + <pages> + <uid>1</uid> + <pid>0</pid> + <title>Root</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>2</uid> + <pid>1</pid> + <title>Dummy 1-2</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>3</uid> + <pid>2</pid> + <title>Dummy 1-2-3 - to be moved</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>4</uid> + <pid>3</pid> + <title>Dummy 1-2-3-4</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>5</uid> + <pid>1</pid> + <title>Dummy 1-5</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>6</uid> + <pid>5</pid> + <title>Dummy 1-5-6</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>7</uid> + <pid>5</pid> + <title>Dummy 1-5-7</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + </pages> + + <pages> + <uid>103</uid> + <pid>-1</pid> + <title>Dummy 1-2-3 - "move from placeholder"</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + <t3ver_oid>3</t3ver_oid> + <t3ver_wsid>91</t3ver_wsid> + <t3ver_state>4</t3ver_state> + <t3ver_stage>1</t3ver_stage> + <t3ver_move_id>0</t3ver_move_id> + </pages> + <pages> + <uid>203</uid> + <pid>5</pid> + <title>Dummy 1-2-3 - "move to placeholder"</title> + <deleted>0</deleted> + <perms_everybody>15</perms_everybody> + <t3ver_oid>0</t3ver_oid> + <t3ver_wsid>91</t3ver_wsid> + <t3ver_state>3</t3ver_state> + <t3ver_stage>0</t3ver_stage> + <t3ver_move_id>3</t3ver_move_id> + </pages> + + <tt_content> + <uid>5</uid> + <pid>7</pid> + <header>Content - to be moved from page 7 to page 1</header> + <deleted>0</deleted> + <t3ver_oid>0</t3ver_oid> + <t3ver_wsid>0</t3ver_wsid> + </tt_content> + + <tt_content> + <uid>105</uid> + <pid>-1</pid> + <header>Content - "move from placeholder"</header> + <deleted>0</deleted> + <t3ver_oid>5</t3ver_oid> + <t3ver_wsid>91</t3ver_wsid> + <t3ver_state>4</t3ver_state> + <t3ver_stage>1</t3ver_stage> + <t3ver_move_id>0</t3ver_move_id> + </tt_content> + + <tt_content> + <uid>205</uid> + <pid>1</pid> + <header>Content - "move to placeholder"</header> + <deleted>0</deleted> + <t3ver_oid>0</t3ver_oid> + <t3ver_wsid>91</t3ver_wsid> + <t3ver_state>3</t3ver_state> + <t3ver_stage>0</t3ver_stage> + <t3ver_move_id>5</t3ver_move_id> + </tt_content> + + <tt_content> + <uid>6</uid> + <pid>2</pid> + <header>Content - to be moved from page 2 to page 7</header> + <deleted>0</deleted> + <t3ver_oid>0</t3ver_oid> + <t3ver_wsid>0</t3ver_wsid> + </tt_content> + + <tt_content> + <uid>106</uid> + <pid>-1</pid> + <header>Content - "move from placeholder"</header> + <deleted>0</deleted> + <t3ver_oid>6</t3ver_oid> + <t3ver_wsid>91</t3ver_wsid> + <t3ver_state>4</t3ver_state> + <t3ver_stage>1</t3ver_stage> + <t3ver_move_id>0</t3ver_move_id> + </tt_content> + + <tt_content> + <uid>206</uid> + <pid>7</pid> + <header>Content - "move to placeholder"</header> + <deleted>0</deleted> + <t3ver_oid>0</t3ver_oid> + <t3ver_wsid>91</t3ver_wsid> + <t3ver_state>3</t3ver_state> + <t3ver_stage>0</t3ver_stage> + <t3ver_move_id>6</t3ver_move_id> + </tt_content> +</dataset> \ No newline at end of file diff --git a/typo3/sysext/workspaces/ext_emconf.php b/typo3/sysext/workspaces/ext_emconf.php new file mode 100644 index 0000000000000000000000000000000000000000..23888f0972e5b6a4443b4494532943287bf07ce4 --- /dev/null +++ b/typo3/sysext/workspaces/ext_emconf.php @@ -0,0 +1,43 @@ +<?php +/*************************************************************** + * Extension Manager/Repository config file for ext "workspaces". + * + * Auto generated 13-03-2012 16:03 + * + * Manual updates: + * Only the data in the array - everything else is removed by next + * writing. "version" and "dependencies" must not be touched! + ***************************************************************/ +$EM_CONF[$_EXTKEY] = array( + 'title' => 'Workspaces Management', + 'description' => 'Adds workspaces functionality with custom stages to TYPO3.', + 'category' => 'be', + 'author' => 'Workspaces Team', + 'author_email' => '', + 'shy' => '', + 'dependencies' => 'version', + 'conflicts' => '', + 'priority' => '', + 'module' => '', + 'state' => 'stable', + 'internal' => '', + 'uploadfolder' => 0, + 'docPath' => 'Documentation', + 'createDirs' => '', + 'modify_tables' => '', + 'clearCacheOnLoad' => 1, + 'lockType' => '', + 'author_company' => '', + 'version' => '6.2.0', + 'constraints' => array( + 'depends' => array( + 'typo3' => '6.2.0-6.2.99', + 'version' => '6.2.0-6.2.99', + ), + 'conflicts' => array(), + 'suggests' => array() + ), + '_md5_values_when_last_written' => '', + 'suggests' => array() +); +?> \ No newline at end of file diff --git a/typo3/sysext/workspaces/ext_icon.gif b/typo3/sysext/workspaces/ext_icon.gif new file mode 100644 index 0000000000000000000000000000000000000000..d389a762ff771ef52b6124d5a9103edaac687eb6 Binary files /dev/null and b/typo3/sysext/workspaces/ext_icon.gif differ diff --git a/typo3/sysext/workspaces/ext_localconf.php b/typo3/sysext/workspaces/ext_localconf.php new file mode 100644 index 0000000000000000000000000000000000000000..e34c893c1571be9120621364f037bd7b708f2cb5 --- /dev/null +++ b/typo3/sysext/workspaces/ext_localconf.php @@ -0,0 +1,38 @@ +<?php +if (!defined('TYPO3_MODE')) { + die('Access denied.'); +} + +if (TYPO3_MODE == 'BE') { + $workspaceSelectorToolbarItemClassPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('workspaces', 'Classes/ExtDirect/WorkspaceSelectorToolbarItem.php'); + $GLOBALS['TYPO3_CONF_VARS']['typo3/backend.php']['additionalBackendItems'][] = $workspaceSelectorToolbarItemClassPath; +} + +// Register the autopublishing task +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['TYPO3\\CMS\\Workspaces\\Task\\AutoPublishTask'] = array( + 'extension' => $_EXTKEY, + 'title' => 'LLL:EXT:' . $_EXTKEY . '/Resources/Private/Language/locallang_mod.xml:autopublishTask.name', + 'description' => 'LLL:EXT:' . $_EXTKEY . '/Resources/Private/Language/locallang_mod.xml:autopublishTask.description' +); + +// Register the cleanup preview links task +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['TYPO3\\CMS\\Workspaces\\Task\\CleanupPreviewLinkTask'] = array( + 'extension' => $_EXTKEY, + 'title' => 'LLL:EXT:' . $_EXTKEY . '/Resources/Private/Language/locallang_mod.xml:cleanupPreviewLinkTask.name', + 'description' => 'LLL:EXT:' . $_EXTKEY . '/Resources/Private/Language/locallang_mod.xml:cleanupPreviewLinkTask.description' +); + +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['workspaces'] = 'TYPO3\\CMS\\Workspaces\\Hook\\DataHandlerHook'; +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass']['workspaces'] = 'TYPO3\\CMS\\Workspaces\\Hook\\BackendUtilityHook'; +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_eofe']['workspaces'] = 'TYPO3\\CMS\\Workspaces\\Hook\\TypoScriptFrontendControllerHook->hook_eofe'; +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck']['workspaces'] = 'TYPO3\\CMS\\Workspaces\\Hook\\BackendUtilityHook->makeEditForm_accessCheck'; + +// Register workspaces cache if not already done in localconf.php or a previously loaded extension. +if (!is_array($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['workspaces_cache'])) { + $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['workspaces_cache'] = array(); +} + +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig('options.workspaces.considerReferences = 1'); +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/tree/pagetree/class.t3lib_tree_pagetree_dataprovider.php']['postProcessCollections'][] = 'TYPO3\\CMS\\Workspaces\\ExtDirect\\PagetreeCollectionsProcessor'; +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig('options.workspaces.considerReferences = 1'); +?> \ No newline at end of file diff --git a/typo3/sysext/workspaces/ext_tables.php b/typo3/sysext/workspaces/ext_tables.php new file mode 100644 index 0000000000000000000000000000000000000000..d81893cd4756708254804607fde864b7868f96b2 --- /dev/null +++ b/typo3/sysext/workspaces/ext_tables.php @@ -0,0 +1,59 @@ +<?php +if (!defined('TYPO3_MODE')) { + die('Access denied.'); +} +// avoid that this block is loaded in the frontend or within the upgrade-wizards +if (TYPO3_MODE == 'BE' && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_INSTALL)) { + /** Registers a Backend Module */ + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule( + 'TYPO3.CMS.' . $_EXTKEY, + 'web', + 'workspaces', + 'before:info', + array( + // An array holding the controller-action-combinations that are accessible + 'Review' => 'index,fullIndex,singleIndex', + 'Preview' => 'index,newPage' + ), + array( + 'access' => 'user,group', + 'icon' => 'EXT:workspaces/Resources/Public/Images/moduleicon.gif', + 'labels' => 'LLL:EXT:' . $_EXTKEY . '/Resources/Private/Language/locallang_mod.xml', + 'navigationComponentId' => 'typo3-pagetree' + ) + ); + + // register ExtDirect + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::registerExtDirectComponent( + 'TYPO3.Workspaces.ExtDirect', + 'TYPO3\\CMS\\Workspaces\\ExtDirect\\ExtDirectServer', + 'web_WorkspacesWorkspaces', + 'user,group' + ); + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::registerExtDirectComponent( + 'TYPO3.Workspaces.ExtDirectActions', + 'TYPO3\\CMS\\Workspaces\\ExtDirect\\ActionHandler', + 'web_WorkspacesWorkspaces', + 'user,group' + ); + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::registerExtDirectComponent( + 'TYPO3.Workspaces.ExtDirectMassActions', + 'TYPO3\\CMS\\Workspaces\\ExtDirect\\MassActionHandler', + 'web_WorkspacesWorkspaces', + 'user,group' + ); + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::registerExtDirectComponent( + 'TYPO3.Ajax.ExtDirect.ToolbarMenu', + 'TYPO3\\CMS\\Workspaces\\ExtDirect\\ToolbarMenu' + ); +} + +// todo move icons to Core sprite or keep them here and remove the todo note ;) +$icons = array( + 'sendtonextstage' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath($_EXTKEY) . 'Resources/Public/Images/version-workspace-sendtonextstage.png', + 'sendtoprevstage' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath($_EXTKEY) . 'Resources/Public/Images/version-workspace-sendtoprevstage.png', + 'generatepreviewlink' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath($_EXTKEY) . 'Resources/Public/Images/generate-ws-preview-link.png' +); +\TYPO3\CMS\Backend\Sprite\SpriteManager::addSingleIcons($icons, $_EXTKEY); +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addLLrefForTCAdescr('sys_workspace_stage', 'EXT:workspaces/Resources/Private/Language/locallang_csh_sysws_stage.xml'); +?> \ No newline at end of file diff --git a/typo3/sysext/workspaces/ext_tables.sql b/typo3/sysext/workspaces/ext_tables.sql new file mode 100644 index 0000000000000000000000000000000000000000..1f7709ee127449dd5a49d62156a878df7a8d608c --- /dev/null +++ b/typo3/sysext/workspaces/ext_tables.sql @@ -0,0 +1,58 @@ +# +# Table structure for table 'sys_workspace' +# +CREATE TABLE sys_workspace ( + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + tstamp int(11) DEFAULT '0' NOT NULL, + deleted tinyint(1) DEFAULT '0' NOT NULL, + title varchar(30) DEFAULT '' NOT NULL, + description varchar(255) DEFAULT '' NOT NULL, + adminusers varchar(4000) DEFAULT '' NOT NULL, + members varchar(4000) DEFAULT '' NOT NULL, + reviewers varchar(4000) DEFAULT '' NOT NULL, + db_mountpoints varchar(255) DEFAULT '' NOT NULL, + file_mountpoints varchar(255) DEFAULT '' NOT NULL, + publish_time int(11) DEFAULT '0' NOT NULL, + unpublish_time int(11) DEFAULT '0' NOT NULL, + freeze tinyint(3) DEFAULT '0' NOT NULL, + live_edit tinyint(3) DEFAULT '0' NOT NULL, + vtypes tinyint(3) DEFAULT '0' NOT NULL, + disable_autocreate tinyint(1) DEFAULT '0' NOT NULL, + swap_modes tinyint(3) DEFAULT '0' NOT NULL, + publish_access tinyint(3) DEFAULT '0' NOT NULL, + custom_stages int(11) DEFAULT '0' NOT NULL, + stagechg_notification tinyint(3) DEFAULT '0' NOT NULL, + edit_notification_mode tinyint(3) DEFAULT '0' NOT NULL, + edit_notification_defaults varchar(255) DEFAULT '' NOT NULL, + edit_allow_notificaton_settings tinyint(3) DEFAULT '0' NOT NULL, + publish_notification_mode tinyint(3) DEFAULT '0' NOT NULL, + publish_notification_defaults varchar(255) DEFAULT '' NOT NULL, + publish_allow_notificaton_settings tinyint(3) DEFAULT '0' NOT NULL, + + PRIMARY KEY (uid), + KEY parent (pid) +); + + +# +# Table structure for table 'sys_workspace_stage' +# +CREATE TABLE sys_workspace_stage ( + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + tstamp int(11) DEFAULT '0' NOT NULL, + deleted tinyint(1) DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + title varchar(30) DEFAULT '' NOT NULL, + responsible_persons varchar(255) DEFAULT '' NOT NULL, + default_mailcomment text, + parentid int(11) DEFAULT '0' NOT NULL, + parenttable tinytext NOT NULL, + notification_mode tinyint(3) DEFAULT '0' NOT NULL, + notification_defaults varchar(255) DEFAULT '' NOT NULL, + allow_notificaton_settings tinyint(3) DEFAULT '0' NOT NULL, + + PRIMARY KEY (uid), + KEY parent (pid) +);