Added feature #16426: ExtJS based Pagetree (Thanks to Stefan Galinski and Peter Foerger)
authorSteffen Kamper <info@sk-typo3.de>
Tue, 16 Nov 2010 22:59:26 +0000 (22:59 +0000)
committerSteffen Kamper <info@sk-typo3.de>
Tue, 16 Nov 2010 22:59:26 +0000 (22:59 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@9416 709f56b5-9817-0410-a4d7-c38de5d9e867

21 files changed:
ChangeLog
typo3/sysext/pagetree/classes/class.tx_contextmenu_contextmenu.php [new file with mode: 0644]
typo3/sysext/pagetree/classes/class.tx_pagetree_abstracttree.php [new file with mode: 0644]
typo3/sysext/pagetree/classes/class.tx_pagetree_pagetree.php [new file with mode: 0644]
typo3/sysext/pagetree/components/pagetree/css/pagetree.css [new file with mode: 0644]
typo3/sysext/pagetree/components/pagetree/javascript/contextmenu.js [new file with mode: 0644]
typo3/sysext/pagetree/components/pagetree/javascript/contextmenuactions.js [new file with mode: 0644]
typo3/sysext/pagetree/components/pagetree/javascript/deletiondropzone.js [new file with mode: 0644]
typo3/sysext/pagetree/components/pagetree/javascript/featurepanel.js [new file with mode: 0644]
typo3/sysext/pagetree/components/pagetree/javascript/pageactions.js [new file with mode: 0644]
typo3/sysext/pagetree/components/pagetree/javascript/pagetree.js [new file with mode: 0644]
typo3/sysext/pagetree/components/pagetree/javascript/tree.js [new file with mode: 0644]
typo3/sysext/pagetree/ext_autoload.php [new file with mode: 0644]
typo3/sysext/pagetree/ext_emconf.php [new file with mode: 0644]
typo3/sysext/pagetree/ext_icon.gif [new file with mode: 0644]
typo3/sysext/pagetree/ext_localconf.php [new file with mode: 0644]
typo3/sysext/pagetree/ext_tables.php [new file with mode: 0644]
typo3/sysext/pagetree/extdirect/dataprovider/class.tx_pagetree_dataprovider_abstracttree.php [new file with mode: 0644]
typo3/sysext/pagetree/extdirect/dataprovider/class.tx_pagetree_dataprovider_pagetree.php [new file with mode: 0644]
typo3/sysext/pagetree/locallang_contextmenu.xml [new file with mode: 0644]
typo3/sysext/pagetree/locallang_pagetree.xml [new file with mode: 0644]

index 149b948..10de006 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -31,6 +31,7 @@
 
 2010-11-16  Steffen Kamper  <steffen@typo3.org>
 
+       * Added feature #16426: ExtJS based Pagetree (Thanks to Stefan Galinski and Peter Foerger)
        * Fixed bug #16310: Migrate regular workspaces to custom-stage workspaces (Thanks to Tolleiv Nietsch)
        * Fixed bug #16421: missing CSS style for pressed buttons in ExtJS / enable qtips / remove toolbar margin (Thanks to Fabien Udriot)
        * Fixed bug #16405: pageRenderer call addJsFile with leaving out 2nd parameter
diff --git a/typo3/sysext/pagetree/classes/class.tx_contextmenu_contextmenu.php b/typo3/sysext/pagetree/classes/class.tx_contextmenu_contextmenu.php
new file mode 100644 (file)
index 0000000..270f9fd
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Susanne Moog (s.moog@neusta.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!
+***************************************************************/
+
+/**
+ * Class with helperfunctions for backendtrees
+ *
+ * $Id: $
+ *
+ * @author     Susanne Moog
+ * @package TYPO3
+ */
+class tx_contextmenu_Contextmenu {
+       /**
+        * Fetches the available context menu actions configured in TS config and merges
+        * those with the ones available after the access checks
+        * 
+        * @param array $availableActions The actions that were already "access checked" and approved
+        * @param string $tskey the tskey holding the context menu configuration options. you only need the 
+        *      individual part, like options.contextMenu.[$tskey].items
+        * 
+        * @return array An array of the allowed and configured actions
+        */
+       public function getTsConfigActions($availableActions, $tskey='records.pages') {
+               return $availableActions;
+
+//             $allowedAndConfiguredActions = array();
+//             $contextMenuItemTsConfig = $GLOBALS['BE_USER']->getTSConfig('options.contextMenu.' . $tskey . '.items');
+//
+//             // we only need the action configuration
+//             $actions = t3lib_div::multi_array_key_exists('action', $contextMenuItemTsConfig['properties']);
+//
+//             // flatten the tsconfig actions array and intersect it with the avilableactions
+//             return array_intersect($availableActions, array_values(t3lib_div::array_flatten('-', $actions)));;
+       }
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/sysext/contextmenu/classes/tx_contextmenu_contextmenu.php'])        {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/sysext/contextmenu/classes/tx_contextmenu_contextmenu.php']);
+}
+
+?>
diff --git a/typo3/sysext/pagetree/classes/class.tx_pagetree_abstracttree.php b/typo3/sysext/pagetree/classes/class.tx_pagetree_abstracttree.php
new file mode 100644 (file)
index 0000000..8ecb61c
--- /dev/null
@@ -0,0 +1,253 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Susanne Moog <s.moog@neusta.de>
+*              Bodo Eichstaedt <bodo.eichstaedt@wmdb.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!
+***************************************************************/
+
+/**
+ * Abstract class for page trees
+ *
+ * $Id: $
+ *
+ * @author     Susanne Moog
+ * @author  Bodo Eichstaedt
+ * @package TYPO3
+ */
+abstract class tx_pagetree_AbstractTree {
+       protected $table = 'pages';
+       protected $fields = 'uid';
+
+       /**
+        * Fetches the subpages to a given id. Uses the class variables $table and $pages
+        * to determine what to fetch from where. Calls getFilterClause($id) to add a where
+        * clause to the query.
+        *
+        * @param int $id The parent ID of the subpages to fetch
+        * @return array The subpages as array
+        */
+       public function getSubPages($id) {
+               $where = 'pid= ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($id, $this->table) .
+                       $this->getFilterClause($id);
+
+               $resultResourceSubpages = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
+                       $this->fields,
+                       $this->table,
+                       $where,
+                       '',
+                       'sorting'
+               );
+
+               if ($GLOBALS['TYPO3_DB']->sql_num_rows($resultResourceSubpages)) {
+                       while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($resultResourceSubpages)) {
+                               $row = t3lib_befunc::getRecordWSOL(
+                                       'pages',
+                                       $row['uid'], 
+                                       '*',
+                                       '',
+                                       TRUE, 
+                                       $GLOBALS['BE_USER']->uc['currentPageTreeLanguage']
+                               );
+                               $this->addMetaInformationToPage($row['uid'], $row);
+                               $subpages[$row['uid']] = $row;
+                       }
+               }
+
+               return $subpages;
+       }
+
+
+       /**
+        * Gets information about the root page. Because the root page isn't fetched from DB
+        * we add infos here statically, like the sitename, the subpages and the id.
+        *
+        * @return array Information about the root page (structure like addMetaInformationToPage)
+        */
+       public function addRootPageInformation() {
+               $rootPageInfo = array(
+                       'uid' => 0,
+                       'title' => !empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']) ?
+                                                               $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] : 'TYPO3',
+                       '_subpages' => 1,
+                       '_actions' => false);
+               return $rootPageInfo;
+       }
+
+       /**
+        * Fetches a where clause to narrow the resulting list of tree elements
+        * Mostly this should contain access/permission checks
+        *
+        * @param int $id The page Id to select
+        */
+       abstract protected function getFilterClause($id);
+
+       /**
+        * Creates a new page
+        *
+        * @param int $parentId The ID of the future parent
+        * @param int $doktype The doctype of the new page
+        */
+       abstract public function create($parentId, $targetId, $doktype);
+       /**
+        * Moves a page
+        *
+        * @param int $sourceId The page to move
+        * @param int $targetId The page to move the page into
+        */
+       abstract public function move($sourceId, $targetId);
+
+       /**
+        * Copies a page
+        *
+        * @param int $sourceId The page to copy
+        * @param int $targetId The page to copy the page into
+        */
+       abstract public function copy($sourceId, $targetId);
+
+       /**
+        * Shows a page
+        *
+        * @param array $row The result row of the page to show
+        */
+       abstract public function show($row);
+
+       /**
+        * Hides a page
+        *
+        * @param array $row The result row of the page to hide
+        */
+       abstract public function disable($id);
+
+       /**
+        * Deletes a page
+        *
+        * @param array $row The result row of the page to delete
+        */
+       abstract public function remove($id);
+
+       /**
+        * Restores (undeletes) a page
+        *
+        * @param array $row The result row of the page to restore
+        */
+       abstract public function restore($id);
+       
+       /**
+        * Check if a page can be edited
+        *
+        * @param array $row The result row of the page to be edited
+        */
+       abstract public function canBeEdited($row);
+
+               
+       /**
+        * Check if new pages can be created
+        *
+        * @param array $row The result row of the page to be created
+        */
+       abstract public function canCreateNewPages($row);
+
+               
+       /**
+        * Check if a page can be removed
+        *
+        * @param array $row The result row of the page to be removed
+        */
+       abstract public function canBeRemoved($row);
+       
+               
+       /**
+        * Check if a page can be viewed
+        *
+        * @param array $row The result row of the page to be viewed
+        */
+       abstract public function canBeViewed($row);
+       
+               
+       /**
+        * Check if a page can be moved
+        *
+        * @param array $row The result row of the page to be moved
+        */
+       abstract public function canBeMoved($row);
+               
+       /**
+        * Check if a page can have subpages
+        *
+        * @param array $row The result row of the page to have subpages
+        */
+       abstract public function canHaveSubpages($row);
+       
+               
+       /**
+        * Check if a page can be copied
+        *
+        * @param array $row The result row of the page to be copied
+        */
+       abstract public function canBeCopied($row);
+       
+               
+       /**
+        * Check if a page can be disabled
+        *
+        * @param array $row The result row of the page to be disabled
+        */
+       abstract public function canBeDisabled($row);
+       
+               
+       /**
+        * Check if a page can show info
+        * 
+        */
+       abstract public function canShowInfo();
+       
+               
+       /**
+        * Check if a page can be cut
+        *
+        */
+       abstract public function canBeCut();
+       
+               
+       /**
+        * Check if a page can be pasted
+        *
+        */
+       abstract public function canBePasted();
+
+       /**
+        * Check if a page can show history
+        *
+        */
+       abstract public function canShowHistory();
+
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/sysext/pagetree/classes/class.tx_pagetree_abstracttree.php'])       {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/sysext/pagetree/classes/class.tx_pagetree_abstracttree.php']);
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/classes/class.tx_pagetree_pagetree.php b/typo3/sysext/pagetree/classes/class.tx_pagetree_pagetree.php
new file mode 100644 (file)
index 0000000..b12ed3d
--- /dev/null
@@ -0,0 +1,633 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Bodo Eichstaedt <bodo.eichstaedt@wmdb.de>
+*                      Susanne Moog <s.moog@neusta.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!
+***************************************************************/
+
+/**
+ * pagetree implementation for the backend
+ *
+ * $Id: $
+ *
+ * @author     Susanne Moog
+ * @package TYPO3
+ */
+class tx_pagetree_Pagetree extends tx_pagetree_AbstractTree {
+       protected $contextMenuMapping = array(
+               'view' => 'canBeViewed',
+               'edit' => 'canBeEdited',
+               'new' => 'canCreateNewPages',
+               'info' => 'canShowInfo',
+               'copy' => 'canBeCopied',
+               'cut' => 'canBeCut',
+               'paste' => 'canBePasted',
+               'move_wizard' => 'canBeMoved',
+               'new_wizard' => 'dsfdsfdsf',
+               'mount_as_treeroot' => 'canBeTemporaryMountPoint',
+               'hide' => 'canBeDisabled',
+               'delete' => 'canBeRemoved',
+               'history' => 'canShowHistory',
+       );
+
+       /**
+        * initializes the backendtreehelper and calls a method to set a class
+        * variable which holds the ts config for the context menu
+        *
+        */
+       public function __construct() {
+               $this->backendTreeHelper = t3lib_div::makeInstance('tx_contextmenu_Contextmenu');
+               $this->fetchTsConfigForContextMenuItems();
+       }
+
+       /**
+        * Gets the clause to filter the page tree elements by.
+        *
+        * @param int $id - The page ID
+        */
+       protected function getFilterClause($id) {
+               return ' AND ' . $GLOBALS['BE_USER']->getPagePermsClause(1)
+                       . t3lib_BEfunc::deleteClause($this->table)
+                       . t3lib_BEfunc::versioningPlaceholderClause($this->table);
+       }
+
+       /**
+        * Checks if the user may create pages below the given page
+        *
+        * @param array $row The targets parent page
+        */
+       public  function canCreate($row) {
+               return $GLOBALS['BE_USER']->doesUserHaveAccess($row,8);
+       }
+
+       /**
+        * Checks if the user has editing rights
+        *
+        * @param array $row The result row for the corresponding page
+        */
+       public  function canEdit($row) {
+               return $GLOBALS['BE_USER']->doesUserHaveAccess($row,2);
+       }
+
+       /**
+        * Checks if the user has the right to delete the page
+        *
+        * @param array $row The result row for the corresponding page
+        */
+       public  function canRemove($row)        {
+               return $GLOBALS['BE_USER']->doesUserHaveAccess($row,4);
+       }
+
+       /**
+        *
+        *
+        * @param int $id
+        * @param string $workspacePreview
+        */
+       public static function getViewLink($id, $workspacePreview) {
+//             $viewScriptPreviewEnabled  = '/' . TYPO3_mainDir . 'mod/user/ws/wsol_preview.php?id=';
+//             $viewScriptPreviewDisabled = '/index.php?id=';
+//
+//                     // check alternate Domains
+//             $rootLine = t3lib_BEfunc::BEgetRootLine($id);
+//             if ($rootLine) {
+//                     $parts = parse_url(t3lib_div::getIndpEnv('TYPO3_SITE_URL'));
+//                     if (t3lib_BEfunc::getDomainStartPage($parts['host'], $parts['path'])) {
+//                             $preUrl_temp = t3lib_BEfunc::firstDomainRecord($rootLine);
+//                     }
+//             }
+//             $preUrl = ($preUrl_temp ? (t3lib_div::getIndpEnv('TYPO3_SSL') ?
+//                     'https://' : 'http://') . $preUrl_temp : '' . '..');
+//
+//                     // Look if a fixed preview language should be added:
+//             $viewLanguageOrder = $GLOBALS['BE_USER']->getTSConfigVal('options.view.languageOrder');
+//             if (strlen($viewLanguageOrder)) {
+//                     $suffix = '';
+//
+//                             // Find allowed languages (if none, all are allowed!)
+//                     if (!$GLOBALS['BE_USER']->user['admin'] &&
+//                             strlen($GLOBALS['BE_USER']->groupData['allowed_languages'])) {
+//                             $allowed_languages = array_flip(explode(',', $GLOBALS['BE_USER']->groupData['allowed_languages']));
+//                     }
+//
+//                             // Traverse the view order, match first occurrence
+//                     $lOrder = t3lib_div::intExplode(',',$viewLanguageOrder);
+//                     foreach($lOrder as $langUid)    {
+//                             if (is_array($allowed_languages) && count($allowed_languages)) {
+//                                     if (isset($allowed_languages[$langUid])) {      // Choose if set.
+//                                             $suffix = '&L='.$langUid;
+//                                             break;
+//                                     }
+//                             } else {        // All allowed since no lang. are listed.
+//                                     $suffix = '&L='.$langUid;
+//                                     break;
+//                             }
+//                     }
+//
+//                             // Add it:
+//                     $addGetVars.= $suffix;
+//             }
+//
+//             $urlPreviewEnabled  = $preUrl . $viewScriptPreviewEnabled . $id . $addGetVars;
+//             $urlPreviewDisabled = $preUrl . $viewScriptPreviewDisabled . $id . $addGetVars;
+//
+//             if ($workspacePreview) {
+//                     return $urlPreviewEnabled;
+//             } else {
+//                     return $urlPreviewDisabled;
+//             }
+
+//             $javascriptLink = t3lib_BEfunc::viewOnClick($id);
+//             debug($javascriptLink);
+
+               return 'http://linux-schmie.de/wp-content/uploads/2010/07/Baustelle.png';
+       }
+
+       /**
+        * Shows the page (unhide)
+        *
+        * @param int $id The page Id
+        */
+       public  function show($id) {
+               $data['pages'][$id]['hidden'] = 0;
+               $this->processTceCmdAndDataMap(array(), $data);
+       }
+
+       /**
+        * Hides the page
+        *
+        * @param int $id The page Id
+        */
+       public  function disable($id)   {
+               $data['pages'][$id]['hidden'] = 1;
+               $this->processTceCmdAndDataMap(array(), $data);
+       }
+
+       /**
+        * Delete the page
+        *
+        * @param int $id The page Id
+        */
+       public  function remove($id) {
+               $cmd['pages'][$id]['delete'] = 1;
+               $this->processTceCmdAndDataMap($cmd);
+       }
+
+       /**
+        * Restore the page ("undelete")
+        *
+        * @param int $id
+        */
+       public  function restore($id) {
+               $cmd['pages'][$id]['undelete'] = 1;
+               $this->processTceCmdAndDataMap($cmd);
+       }
+
+       /**
+        * Copies a page. Use a negative target ID to specify a sibling target, else a parent is used
+        *
+        * @param int $sourceId The element to copy
+        * @param int $targetId The element to copy into (if you use a negative value: the element to copy after)
+        */
+       public function copy($sourceId, $targetId) {
+               $cmd['pages'][$sourceId]['copy'] = $targetId;
+               $returnValue = $this->processTceCmdAndDataMap($cmd);
+               return $returnValue['copy']['pages'][$sourceId];
+       }
+
+       /**
+        * Moves a page. Use a negative target ID to specify a sibling target, else a parent is used
+        *
+        * @param int $sourceId The element to move
+        * @param int $targetId The element to move into (if you use a negative value: the element to copy after)
+        */
+       public function move($sourceId, $targetId) {
+               $cmd['pages'][$sourceId]['move'] = $targetId;
+               $this->processTceCmdAndDataMap($cmd);
+       }
+
+       /**
+        * Creates a page of the given doktype
+        *
+        * @param int $parentId The ID of the parent page
+        * @param int $doktype The doktype for the new page
+        */
+       public function create($parentId, $targetId, $doktype) {
+               $placeholder = 'NEW12345';
+               $data['pages'][$placeholder] = array(
+                       'pid' => $parentId,
+                       'doktype' => $doktype,
+                       'title' => '[Default Title]'
+               );
+               $tceResultArr = $this->processTceCmdAndDataMap(array(), $data);
+               if($parentId != $targetId) {
+                       $this->move($tceResultArr['new'][$placeholder], $targetId);
+               }
+               return $tceResultArr['new'][$placeholder];
+       }
+
+       /**
+        * fetches the tsconfig options for the context menu
+        *
+        */
+       public function fetchTsConfigForContextMenuItems() {
+               $this->contextMenuConfiguration = t3lib_div::trimExplode(',',$GLOBALS['BE_USER']->getTSConfigVal('options.contextMenu.pageTree.disableItems'),1);
+       }
+
+       /**
+        * Checks if the page is allowed to have subpages
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canHaveSubpages($row) {
+               return $this->getDoktypeDependentConfigOptions($row['doktype'], 'canHaveSubpages');
+       }
+
+       /**
+        * Checks if the page can be disabled
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canBeDisabled($row) {
+               if($this->canEdit($row) && !in_array('hide', $this->contextMenuConfiguration)) {
+                               return true;
+               } else {
+                               return false;
+               }
+       }
+
+       /**
+        * Checks if the page is allowed to show info
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canShowInfo() {
+               if(!in_array('info', $this->contextMenuConfiguration)) {
+                               return true;
+               } else {
+                               return false;
+               }
+       }
+
+       /**
+        * Checks if the page is allowed to be a temporary mountpoint
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canBeTemporaryMountPoint() {
+               if(!in_array('mount_as_treeroot', $this->contextMenuConfiguration)) {
+                               return true;
+               } else {
+                               return false;
+               }
+       }
+
+       /**
+        * Checks if the page is allowed to can be cut
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canBeCut() {
+               if(!in_array('cut', $this->contextMenuConfiguration)) {
+                               return true;
+               } else {
+                               return false;
+               }
+       }
+
+       /**
+        * Checks if the page is allowed to be pasted
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canBePasted() {
+               if(!in_array('paste', $this->contextMenuConfiguration)) {
+                               return true;
+               } else {
+                               return false;
+               }
+       }
+
+       /**
+        * Checks if the page is allowed to show history
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canShowHistory() {
+               if(!in_array('history', $this->contextMenuConfiguration)) {
+                               return true;
+               } else {
+                               return false;
+               }
+       }
+
+       /**
+        * Checks if the page is allowed to be edited
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canBeEdited($row) {
+               if($this->canEdit($row)
+                       && $this->getDoktypeDependentConfigOptions($row['doktype'], 'canBeEdited')
+                       && !in_array('edit', $this->contextMenuConfiguration)) {
+                               return true;
+               } else {
+                               return false;
+               }
+       }
+
+       /**
+        * Checks if the page is allowed to be moved
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canBeMoved($row) {
+               if($this->canEdit($row)
+                       && $this->getDoktypeDependentConfigOptions($row['doktype'], 'canBeEdited')
+                       && !in_array('move_wizard', $this->contextMenuConfiguration)){
+                       return true;
+               } else {
+                       return false;
+               };
+       }
+
+       /**
+        * Checks if the page is allowed to be copied
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canBeCopied($row) {
+               if($this->canEdit($row)
+                       && $this->getDoktypeDependentConfigOptions($row['doktype'], 'canBeCopied')
+                       && !in_array('copy', $this->contextMenuConfiguration)){
+                       return true;
+               } else {
+                       return false;
+               };
+       }
+
+       /**
+        * Checks if there can be new pages created
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canCreateNewPages($row) {
+               return $this->canCreate($row) && !in_array('new', $this->contextMenuConfiguration) ? true : false;
+       }
+
+       /**
+        * Checks if the page is allowed to be removed
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canBeRemoved($row) {
+               if($this->canRemove($row)
+                       && $this->getDoktypeDependentConfigOptions($row['doktype'], 'canBeDeleted')
+                       && !in_array('delete', $this->contextMenuConfiguration)) {
+                               return true;
+               } else {
+                               return false;
+               }
+       }
+
+       /**
+        * Checks if the page is allowed to be viewed
+        *
+        * @param array $row The page result row to check
+        * @param boolean true if allowed
+        */
+       public function canBeViewed($row) {
+               if($this->getDoktypeDependentConfigOptions($row['doktype'], 'canBeViewed')
+                       && !in_array('view', $this->contextMenuConfiguration)) {
+                               return true;
+               } else {
+                               return false;
+               }
+       }
+
+       /**
+        * Gets configuration options for the clickmenu depending on the current doctype
+        * For Example: No "view" link for sysfolders
+        *
+        * Possible actions are: isViewable, isDeletable, isEditable,isMovable,
+        * canHoldSubpages, canBeCopied
+        *
+        * @param int $doktype The doctype to check
+        * @param string $action The action to check
+        * @return boolean true if the action is allowed for the doctype, else false
+        */
+       public function getDoktypeDependentConfigOptions($doktype, $action) {
+               //@TODO method was dropped (replacement needed)
+//             return t3lib_pageSelect::pageTypeHasCapability($doktype, $action);
+       }
+
+       /**
+        * Process TCEMAIN Commands and Datamaps
+        *
+        * @param array $cmd - The command array - processed by process_cmdmap()
+        * @param array $data - The data array - processed by process_datamap()
+        */
+       protected function processTceCmdAndDataMap(array $cmd, array $data=array()) {
+               /** @var $tce t3lib_TCEmain */
+               $tce = t3lib_div::makeInstance('t3lib_TCEmain');
+               $tce->stripslashes_values = 0;
+               $tce->start($data,$cmd);
+               if($cmd) {
+                       $tce->process_cmdmap();
+                       $returnValues['copy'] = $tce->copyMappingArray_merged;
+               } else if ($data) {
+                       $tce->process_datamap();
+                       $returnValues['new'] = $tce->substNEWwithIDs;
+               }
+               return $returnValues;
+       }
+
+       /**
+        * returns the pagetree mounts for the current user
+        * @return array
+        */
+       public function getTreeMounts() {
+               $records = array();
+               $webmountIds = $GLOBALS['BE_USER']->returnWebmounts();
+               if(!empty($webmountIds)){
+                       foreach ($webmountIds as $webmount) {
+                               if ($webmount == 0) {
+                                       $records[] = $this->addRootPageInformation();
+                               } else {
+                                       $record = t3lib_BEfunc::getRecordWSOL(
+                                               $this->table,
+                                               $webmount,
+                                               $fields = '*',
+                                               $where = '',
+                                               $useDeleteClause = TRUE,
+                                               $GLOBALS['BE_USER']->uc['currentPageTreeLanguage']
+                                       );
+                                       $this->addMetaInformationToPage($record['uid'], $record);
+                                       $records[] = $record;
+                               }
+                       }
+               }
+               return $records;
+       }
+
+       /**
+        * Updates the given field with a new text value, may be used to inline update
+        * the title field in the new page tree
+        *
+        * @param int $pageId
+        * @param string $newString
+        * @param string $field
+        */
+       public function updateTextInputField($pageId, $newString, $field) {
+               $data[$this->table][$pageId][$field] = $newString;
+               $this->processTceCmdAndDataMap(array(), $data);
+       }
+
+       /**
+        * Helper function for fetching ts config options with "options.pagetree.[key]"
+        *
+        * @param string $optionName the string to fetch
+        * @param string the ts config value
+        */
+       public function getTsConfigOptionForPagetree($optionName) {
+               return $GLOBALS['BE_USER']->getTSConfigVal('options.pageTree.' . $optionName);
+       }
+
+       /**
+        * Fetches domain records from site root
+        * Needed for "displayDomainNameWithTitle"
+        *
+        * @param int $pageId The page id of the page with the website_root flag
+        * @param string returns the domain
+        */
+       public function getDomainRecordFromSiteRoot($pageId) {
+               $domainRecord = t3lib_BEfunc::getRecordsByField('sys_domain', 'pid', $pageId, ' AND redirectTo=\'\' AND hidden=0', '', 'sorting');
+               if (is_array($domainRecord)) {
+                       reset($domainRecord);
+                       $firstDomainRecord = current($domainRecord);
+                       return rtrim($firstDomainRecord['domainName'], '/');
+               }
+       }
+
+       /**
+        * Fetches a page result record for given fields
+        *
+        * @param int $pageId The page id to fetch infos for
+        * @param array $fields a field array with the fields to fetch
+        */
+       public function getPageInformationForGivenFields($pageId, $fields){
+               return t3lib_BEfunc::getRecordWSOL(
+                       'pages',
+                       $pageId,
+                       implode(',', $fields),
+                       $where = '',
+                       $useDeleteClause = TRUE,
+                       $GLOBALS['BE_USER']->uc['currentPageTreeLanguage']
+               );
+       }
+
+       /**
+        * Adds meta data to a page
+        * Data:
+        * - if it has sub-pages
+        * - if the user may edit/create/delete the page
+        * - if the page may be viewed, deleted, edited, moved, copied, or can contain subpages
+        *
+        * @param int $id The ID of the page
+        * @param array $row the SQL result row with the db related information for the element
+        */
+       public function addMetaInformationToPage($id, &$row) {
+               $numRows = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
+                       'uid',
+                       $this->table,
+                       'pid = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($id, $this->table)
+               );
+
+               if ($numRows) {
+                       $row['_subpages'] = $numRows;
+               }
+
+               $row['_actions'] = array(
+                       'canBeEdited' => $this->canBeEdited($row),
+                       'canCreateNewPages' => $this->canCreateNewPages($row),
+                       'canBeRemoved' => $this->canBeRemoved($row),
+                       'canBeViewed' => $this->canBeViewed($row),
+                       'canBeMoved' => $this->canBeMoved($row),
+                       'canHaveSubpages' => $this->canHaveSubpages($row),
+                       'canBeCopied' => $this->canBeCopied($row),
+                       'canBeDisabled' => $this->canBeDisabled($row),
+                       'canShowInfo' => $this->canShowInfo(),
+                       'canBeCut' => $this->canBeCut(),
+                       'canBePasted' => $this->canBePasted(),
+                       'canShowHistory' => $this->canShowHistory(),
+                       'canBeTemporaryMountPoint' => $this->canBeTemporaryMountPoint()
+               );
+
+               foreach ($row['_actions'] as $action => $state) {
+                       if (!$state) {
+                               unset($row['_actions'][$action]);
+                       }
+               }
+               $actions = $row['_actions'];
+
+               $localContextMenuMapping = array_flip($this->contextMenuMapping);
+
+               $availableActions = array();
+               foreach($localContextMenuMapping as $contextActionKey => $contextActionValue) {
+                       if(array_key_exists($contextActionKey, $actions)) {
+                               $availableActions[$contextActionValue] = $contextActionKey;
+                       }
+               }
+
+               if(in_array('canBePasted', $availableActions)){
+                       $availableActions[] = 'canBePastedAfter';
+                       $availableActions[] = 'canBePastedInto';
+               }
+               if(in_array('canBeDisabled', $availableActions)){
+                       $availableActions[] = 'canBeEnabled';
+               }
+
+               $availableAndConfiguredOptions = array_values($this->backendTreeHelper->getTsConfigActions($availableActions));
+
+               $row['_actions'] = $availableAndConfiguredOptions;
+       }
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/sysext/pagetree/classes/class.tx_pagetree_pagetree.php'])   {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/sysext/pagetree/classes/class.tx_pagetree_pagetree.php']);
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/components/pagetree/css/pagetree.css b/typo3/sysext/pagetree/components/pagetree/css/pagetree.css
new file mode 100644 (file)
index 0000000..2f8f2ea
--- /dev/null
@@ -0,0 +1,105 @@
+
+#typo3-pagetree-treeContainer {
+       margin-bottom: 60px;
+       padding-bottom: 10px;
+       height: 100%;
+}
+
+#typo3-pagetree .topPanel-button {
+       margin: 0 5px 0 0;
+       display: inline-block;
+       height: 16px;
+       overflow: hidden;
+       position: relative;
+       width: 16px;
+}
+
+#typo3-pagetree #topPanel .x-btn,
+#typo3-pagetree #topPanel .x-btn-over {
+       background: none;
+       border: none;
+}
+
+#typo3-pagetree .x-tree .x-panel-body {
+       background: none;
+}
+
+/*#typo3-navigationContainer .x-panel-body {
+       -moz-box-shadow: inset -2px 0 1px #C4C4C4;
+       -ms-box-shadow: inset -2px 0 1px #C4C4C4;
+       -webkit-box-shadow: inset -2px 0 1px #C4C4C4;
+       box-shadow: inset -2px 0 1px #C4C4C4;
+}
+
+#typo3-pagetree #topPanel {
+       -moz-box-shadow: inset -2px 0 0 #414141;
+       -ms-box-shadow: inset -2px 0 0 #414141;
+       -webkit-box-shadow: inset -2px 0 0 #414141;
+       box-shadow: inset -2px 0 0 #414141;
+}*/
+
+#typo3-pagetree .x-tree-node .x-tree-node-el {
+       border-bottom: 1px solid transparent;
+       border-left: 1px solid transparent;
+       border-top: 1px solid transparent;
+       margin-right: 0;
+}
+
+#typo3-pagetree .x-tree-node .x-tree-node-over {
+       background-color: #F8F8F8;
+       margin-right: 4px;
+}
+
+#typo3-pagetree .x-tree-node .x-tree-node-over,
+#typo3-pagetree .x-tree-node .x-tree-selected {
+       background-color: #F8F8F8;
+       border-bottom: 1px solid #D7D7D7;
+       border-left: 1px solid #D7D7D7;
+       border-top: 1px solid #D7D7D7;
+}
+
+#typo3-pagetree .x-tree-node .x-tree-drag-insert-above {
+       border-top-color: #686868;
+}
+
+#typo3-pagetree .x-tree-node .x-tree-drag-insert-below {
+       border-bottom-color: #686868;
+}
+
+#typo3-pagetree .x-panel-tbar {
+       padding: 0;
+       margin: 0;
+       background-color: #585858;
+       border: none;
+}
+
+#typo3-pagetree .x-toolbar {
+       background: none;
+       border: none;
+}
+
+#typo3-pagetree .typo3-pagetree-topbar-item,
+#typo3-pagetree .typo3-pagetree-topbar-item div {
+       background-color: #DADADA;
+}
+
+#typo3-pagetree .typo3-pagetree-topbar-item {
+       padding: 5px 3px 0 3px;
+       height: 22px;
+}
+
+#typo3-pagetree-topPanel-filter {
+       height: 14px;
+}
+
+#typo3-pagetree-deletionDropZone {
+       padding: 5px;
+       width: 100%;
+       position: fixed;
+       bottom: 0;
+}
+
+#typo3-pagetree-deletionDropZone,
+#typo3-pagetree-deletionDropZone .x-panel-body {
+       background-color: #DADADA;
+}
diff --git a/typo3/sysext/pagetree/components/pagetree/javascript/contextmenu.js b/typo3/sysext/pagetree/components/pagetree/javascript/contextmenu.js
new file mode 100644 (file)
index 0000000..e6e57b2
--- /dev/null
@@ -0,0 +1,118 @@
+Ext.namespace('TYPO3.Components.PageTree');
+
+TYPO3.Components.PageTree.ContextMenu = Ext.extend(Ext.menu.Menu, {
+       dataRecord: null,
+       id: 'typo3-pagetree-contextmenu',
+       listeners: {
+               itemclick: {
+                       scope: this,
+                       fn: function (item) {
+                               if (item.callbackAction != undefined) {
+                                       eval(item.callbackAction + '(item.parentMenu.dataRecord, item.attributes)');
+                               }
+                       }
+               }
+       },
+
+
+       /**
+        * Fill menu with menu items.
+        *
+        * @param dataRecord
+        * The data record to bind to the menu.
+        * MUST contain "attributes.actions" as an array defining the allowed actions for the current item.
+        *
+        * @param contextMenuConfiguration
+        * Context menu configuration. See Ext.MenuItem for properties.
+        * Additionally, the following two properties have to exist:
+        * - callback: Callback method to be called when the click happens. Gets two parameters: the dataRecord, and item.attributes.
+        * - action: The name of the action
+        *
+        * @return the number of menu items in the first level.
+        */
+       fillWithMenuItems: function(dataRecord, contextMenuConfiguration) {
+               this.dataRecord = dataRecord;
+               this.dataRecord.attributes.actions = Ext.toArray(this.dataRecord.attributes.actions);
+               //
+               var components = this.preProcessContextMenuConfiguration(contextMenuConfiguration);
+
+               if (components.length) {
+                       for (var component in components) {
+                               if (components[component] == '-') {
+                                       this.addSeparator();
+                               } else {
+                                       if (typeof components[component] == 'object') {
+                                               this.addItem(components[component]);
+                                       }
+                               }
+                       }
+               }
+               return components.length;
+       },
+
+       // Private
+       // recursively parses the context menu actions array and generates the
+       // components for the context menu including separators/dividers and sub menus
+       preProcessContextMenuConfiguration: function(contextMenuConfiguration, level) {
+               level = level || 0;
+               if (level > 5) {
+                       return [];
+               }
+
+               var components = [];
+               var index = 0;
+
+               var modulesInsideGroup = false;
+               var subMenus = 0;
+               for (var singleAction in contextMenuConfiguration) {
+                       if (singleAction.indexOf('--submenu') != -1) {
+                               var subMenuComponents = this.preProcessContextMenuConfiguration(
+                                               contextMenuConfiguration[singleAction],
+                                               level + 1
+                                               );
+
+                               if (subMenuComponents.length) {
+                                       var subMenu = new TYPO3.Components.PageTree.ContextMenu({
+                                               id: this.id + '-sub' + ++subMenus,
+                                               items: subMenuComponents,
+                                               dataRecord: this.dataRecord
+                                       });
+
+                                       components[index++] = {
+                                               text: contextMenuConfiguration[singleAction]['text'],
+                                               cls: 'contextMenu-subMenu',
+                                               menu: subMenu
+                                       };
+                               }
+                       } else {
+                               if (singleAction.indexOf('--divider') != -1) {
+                                       if (modulesInsideGroup) {
+                                               components[index++] = '-';
+                                               modulesInsideGroup = false;
+                                       }
+                               } else {
+                                       modulesInsideGroup = true;
+
+                                       if (typeof contextMenuConfiguration[singleAction] == 'object') {
+                                               var component = contextMenuConfiguration[singleAction];
+                                               component.itemTpl = Ext.menu.Item.prototype.itemTpl = new Ext.XTemplate(
+                                                               '<a id="{id}" class="{cls}" hidefocus="true" unselectable="on" href="{href}">',
+                                                               '<span class="{hrefTarget}">',
+                                                               '<img src="{icon}" class="x-menu-item-icon {iconCls}" unselectable="on" />',
+                                                               '</span>',
+                                                               '<span class="x-menu-item-text">{text}</span>',
+                                                               '</a>'
+                                                               );
+
+                                               components[index++] = component;
+                                       }
+                               }
+                       }
+               }
+
+               return components;
+       }
+});
+
+// XTYPE Registration
+Ext.reg('TYPO3.Components.PageTree.ContextMenu', TYPO3.Components.PageTree.ContextMenu);
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/components/pagetree/javascript/contextmenuactions.js b/typo3/sysext/pagetree/components/pagetree/javascript/contextmenuactions.js
new file mode 100644 (file)
index 0000000..f53dc68
--- /dev/null
@@ -0,0 +1,65 @@
+Ext.namespace('TYPO3.Components.PageTree');
+
+TYPO3.Components.PageTree.ContextMenuActions = {
+       removeNode: function(node) {
+               if (node.parentNode) {
+                       node.remove();
+               }
+       },
+
+       stub: function() {
+               alert('Just a Stub! Don\'t Panic!');
+       },
+
+       viewPage: function(node) {
+               TYPO3.Components.PageTree.DataProvider.getViewLink(
+                               node.id,
+                               (TYPO3.configuration.workspaceFrontendPreviewEnabled && TYPO3.configuration.currentWorkspace != 0),
+                                 function(result) {
+                                         openUrlInWindow(result, 'typo3-contextMenu-view');
+                                 }
+                               );
+       },
+
+       editPageProperties: function(node) {
+               TYPO3.Backend.ContentContainer.setUrl(
+                               'alt_doc.php?edit[pages][' + node.attributes.properties.realId + ']=edit'
+                               );
+       },
+
+       newPageWizard: function(node) {
+               TYPO3.Backend.ContentContainer.setUrl(
+                               'db_new.php?id=' + node.attributes.properties.realId + '&pagesOnly=1'
+                               );
+       },
+
+       openInfoPopUp: function(node) {
+               launchView('pages', node.attributes.properties.realId);
+       },
+
+       openHistoryPopUp: function(node) {
+               TYPO3.Backend.ContentContainer.setUrl(
+                               'show_rechis.php?element=pages:' + node.attributes.properties.realId
+                               );
+       },
+
+       enablePage: function(node) {
+               this.tooglePageVisibility(node, false);
+       },
+
+       disablePage: function(node) {
+               this.tooglePageVisibility(node, true);
+       },
+
+       tooglePageVisibility: function(node, enabled) {
+               TYPO3.Components.PageTree.DataProvider.tooglePageVisibility(
+                               node.id,
+                               enabled,
+                                  function(updatedNodeFromServer) {
+                                          updatedNodeFromServer.uiProvider = TYPO3.Components.PageTree.PageTreeUI;
+                                          var newTreeNode = new Ext.tree.TreeNode(updatedNodeFromServer);
+                                          node.parentNode.replaceChild(newTreeNode, node);
+                                  }
+                               );
+       }
+};
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/components/pagetree/javascript/deletiondropzone.js b/typo3/sysext/pagetree/components/pagetree/javascript/deletiondropzone.js
new file mode 100644 (file)
index 0000000..3c0c833
--- /dev/null
@@ -0,0 +1,54 @@
+Ext.namespace('TYPO3.Components.PageTree');
+
+/**
+ * Main filtering tree widget
+ */
+TYPO3.Components.PageTree.DeletionDropZone = Ext.extend(Ext.Panel, {
+       id: 'typo3-pagetree-deletionDropZone',
+       border: true,
+       height: 50,
+       html: '<strong>Drag a page into this drop zone to delete it</strong>',
+
+       pageTree: null,
+
+       listeners: {
+               afterrender: {
+                       fn: function() {
+                               var filteringTree = this.pageTree;
+                               (new Ext.dd.DropTarget(this.getEl(), {
+                                       notifyDrop: function(ddProxy, e, n) {
+                                               var node = n.node;
+                                               // TODO: Permission checking again
+
+                                               var dropZoneBody = Ext.get(this.el.query('.x-panel-body')[0]);
+                                               dropZoneBody.update('You just deleted "' + node.text + '"<span class="undelete" style="text-decoration: underline;">Undelete</span>');
+                                               Ext.get(dropZoneBody.query('.undelete')[0]).on('click', function() {
+                                                       filteringTree.dataProvider.undeleteNode(node.id);
+                                                       filteringTree.refreshTree();
+                                                       this.update('<strong>Restored</strong>');  // TODO: LOCALIZE
+
+                                               }.createDelegate(dropZoneBody, [node.id, filteringTree]));
+
+                                               var fadeOutFn = function() {
+                                                       this.animate({opacity: {to: 0}}, 1, function(dropZoneBody) {
+                                                               dropZoneBody.update('<strong>DropZone</strong>'); // TODO
+                                                               dropZoneBody.setStyle('opacity', 1);
+                                                       });
+                                               };
+                                               fadeOutFn.defer(5000, dropZoneBody);
+
+                                               filteringTree.dataProvider.deleteNode(node.id);
+                                               node.remove();
+                                       },
+                                       ddGroup: 'TreeDD',
+                                       overClass: 'dd-delete-over'
+                               }));
+
+                               // TODO: Show drop zone only if currently dragging
+                       }
+               }
+       }
+});
+
+// XTYPE Registration
+Ext.reg('TYPO3.Components.PageTree.DeletionDropZone', TYPO3.Components.PageTree.DeletionDropZone);
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/components/pagetree/javascript/featurepanel.js b/typo3/sysext/pagetree/components/pagetree/javascript/featurepanel.js
new file mode 100644 (file)
index 0000000..825a8fb
--- /dev/null
@@ -0,0 +1,233 @@
+Ext.namespace('TYPO3.Components.PageTree');
+
+TYPO3.Components.PageTree.FeaturePanel = Ext.extend(Ext.Panel, {
+       id: 'topPanel',
+       border: false,
+       height: 60,
+
+       currentlyClickedButton: null,
+       currentlyShownPanel: null,
+
+       tbar: new Ext.Toolbar(),
+
+       pageTree: null,
+       filterTree: null,
+
+       initComponent: function() {
+               this.filterTree = this.addFilterFeature();
+               this.addDragDropNodeInsertionFeature();
+               this.addRefreshTreeFeature();
+               this.addLanguageSelection();
+
+               TYPO3.Components.PageTree.FeaturePanel.superclass.initComponent.apply(this, arguments);
+       },
+
+       // This is the callback method which toggles the sub-menu if you click
+       // on a top-bar item.
+       topbarButtonCallback: function() {
+               if (this.ownerCt.ownerCt.currentlyClickedButton === null) {
+                       // first click, nothing selected yet
+                       this.toggle(true);
+                       this.connectedWidget.show();
+
+                       this.ownerCt.ownerCt.currentlyClickedButton = this;
+                       this.ownerCt.ownerCt.currentlyShownPanel = this.connectedWidget;
+               } else {
+                       if (this.ownerCt.ownerCt.currentlyClickedButton === this) {
+                               // second click onto currently clicked button
+                               this.ownerCt.ownerCt.currentlyClickedButton.toggle(false);
+                               this.ownerCt.ownerCt.currentlyShownPanel.hide();
+                               this.ownerCt.ownerCt.currentlyClickedButton = null;
+                               this.ownerCt.ownerCt.currentlyShownPanel = null;
+                       } else {
+                               // toggling a view
+                               this.ownerCt.ownerCt.currentlyClickedButton.toggle(false);
+                               this.ownerCt.ownerCt.currentlyShownPanel.hide();
+
+                               this.toggle(true);
+                               this.connectedWidget.show();
+                               this.ownerCt.ownerCt.currentlyClickedButton = this;
+                               this.ownerCt.ownerCt.currentlyShownPanel = this.connectedWidget;
+                       }
+               }
+       },
+
+       addWidget: function(button, connectedWidget) {
+               button.connectedWidget = connectedWidget;
+               if (!button.hasListener('click')) {
+                       button.addListener('click', this.topbarButtonCallback);
+               }
+
+               this.getTopToolbar().addItem(button);
+               this.add(connectedWidget);
+               this.doLayout();
+       },
+
+       /**
+        * Add the "Filter" feature to the top panel and the panel.
+        */
+       addFilterFeature: function() {
+               // Callback method displaying the results
+               var filterCallback = function(textField) {
+                       var filterString = textField.getValue();
+                       if (filterString != '') {
+                               this.pageTree.dataProvider.getFilteredTree(filterString, function(results) {
+                                       this.filterTree.setRootNode({
+                                               id: 'root',
+                                               children: results
+                                       });
+                                       this.pageTree.tree.hide();
+                                       this.filterTree.expandAll();
+                                       this.filterTree.show();
+                                       this.doLayout();
+                               }.createDelegate(this));
+                       } else {
+                               this.filterTree.hide();
+                               this.pageTree.tree.show();
+                               this.doLayout();
+                       }
+               };
+
+               this.pageTree.dataProvider.getSpriteIconClasses('actions-system-tree-search-open', function(result) {
+                       // Top Panel
+                       var topPanelButton = new Ext.Button({
+                               //text: 'filter',
+                               cls: 'topPanel-button ' + result
+                       });
+
+                       var topPanelWidget = new Ext.Panel({
+                               border: false,
+                               hidden: true,
+                               cls: 'typo3-pagetree-topbar-item',
+                               items: [
+                                       new Ext.form.TextField({
+                                               id: 'typo3-pagetree-topPanel-filter',
+                                               enableKeyEvents: true,
+                                               listeners: {
+                                                       keypress: {
+                                                               fn: filterCallback,
+                                                               scope: this,
+                                                               buffer: 250
+                                                       }
+                                               }
+                                       })
+                               ]
+                       });
+                       this.addWidget(topPanelButton, topPanelWidget);
+               }, this);
+
+               // Tree initialization
+               return new Ext.tree.TreePanel({
+                       anchor: '100% 100%',
+                       border: false,
+                       autoScroll: true,
+                       animate: false,
+                       id: 'typo3-pagetree-filterTree',
+                       rootVisible: false,
+                       hidden:true,
+                       root: {
+                               id: 'root',
+                               text: 'Root',
+                               expanded: true
+                       }
+               });
+       },
+
+       /**
+        * Add drag and drop node insertion.
+        * @internal
+        */
+       addDragDropNodeInsertionFeature: function() {
+               // Initialization of the "new node" toolbar, via a dataProvider.
+               var newNodeToolbar = new Ext.Toolbar({
+                       border: false,
+                       id: 'typo3-pagetree-topbar-new',
+                       cls: 'typo3-pagetree-topbar-item',
+                       hidden: true,
+                       anchor: '100% 100%',
+                       autoWidth: true,
+                       listeners: {
+                               render: function() {
+                                       new Ext.dd.DragZone(newNodeToolbar.getEl(), {
+                                               ddGroup: 'TreeDD',
+                                               getDragData: function(e) {
+                                                       var clickedButton = Ext.ComponentMgr.get(e.getTarget('.x-btn').id);
+                                                       clickedButton.shouldCreateNewNode = true;
+
+                                                       this.ddel = document.createElement('div');
+                                                       return {ddel: this.ddel, item: clickedButton}
+                                               },
+                                               onInitDrag: function() {
+                                                       var clickedButton = this.dragData.item;
+                                                       this.ddel.innerHTML = '<span class="' + clickedButton.initialConfig.cls + '"></span>' + clickedButton.title;
+                                                       this.ddel.style.width = '150px';
+                                                       this.proxy.update(this.ddel);
+                                               }
+                                       });
+                               }
+                       }
+               });
+
+               // Load data from server
+               if (this.pageTree.dataProvider.getNodeTypes) {
+                       // Only call the server if the server implements getNodeTypes();
+                       this.pageTree.dataProvider.getNodeTypes(function(response) {
+                               var length = response.length;
+                               for (var i = 0; i < length; ++i) {
+                                       newNodeToolbar.addItem(response[i]);
+                               }
+                               newNodeToolbar.doLayout();
+                       });
+               }
+
+               this.pageTree.dataProvider.getSpriteIconClasses('actions-page-new', function(result) {
+                       var topPanelButton = new Ext.Button({
+                               //text: 'filter',
+                               cls: 'topPanel-button ' + result
+                       });
+
+                       this.addWidget(topPanelButton, newNodeToolbar);
+               }, this);
+       },
+
+       /**
+        * Adds a language selection menu to the topbar
+        * @internal
+        */
+       addLanguageSelection: function() {
+               // Initialization of the "new node" toolbar, via a dataProvider.
+               (new Ext.Toolbar({
+                       border: false,
+                       id: this.id + '-topbar-languageSelection',
+                       cls: this.id + '-topbar-item',
+                       hidden: true,
+                       anchor: '100% 100%',
+                       autoWidth: true
+               }));
+
+
+       },
+
+       /**
+        * Add the "Refresh Tree" feature to the top panel
+        */
+       addRefreshTreeFeature: function() {
+               this.pageTree.dataProvider.getSpriteIconClasses('actions-system-refresh', function(result) {
+                       // Top Panel
+                       var topPanelButton = new Ext.Button({
+                               cls: 'topPanel-button ' + result,
+                               listeners: {
+                                       scope: this.pageTree,
+                                       'click': {
+                                               fn: this.pageTree.refreshTree
+                                       }
+                               }
+                       });
+
+                       this.getTopToolbar().addItem(topPanelButton);
+               }, this);
+       }
+});
+
+// XTYPE Registration
+Ext.reg('TYPO3.Components.PageTree.FeaturePanel', TYPO3.Components.PageTree.FeaturePanel);
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/components/pagetree/javascript/pageactions.js b/typo3/sysext/pagetree/components/pagetree/javascript/pageactions.js
new file mode 100644 (file)
index 0000000..7c16b08
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * This is a library of callback actions for the page tree.
+ */
+
+Ext.namespace('TYPO3.Components.PageTree');
+
+TYPO3.Components.PageTree.PageActions = {
+       singleClick: function(node) {
+               TYPO3.Backend.ContentContainer.setUrl(
+                       TS.PATH_typo3 + currentSubScript + '?id=' + node.attributes.properties.realId
+               );
+       },
+
+       saveTitle: function(node, newText, oldText) {
+               if (newText == oldText) {
+                       return;
+               }
+
+               node = node.editNode;
+               TYPO3.Components.PageTree.DataProvider.setPageTitle(
+                       node.id,
+                       newText,
+                       node.attributes.properties.textSourceField,
+                       Ext.emptyFn
+               );
+       }
+};
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/components/pagetree/javascript/pagetree.js b/typo3/sysext/pagetree/components/pagetree/javascript/pagetree.js
new file mode 100644 (file)
index 0000000..2412aad
--- /dev/null
@@ -0,0 +1,283 @@
+Ext.namespace('TYPO3.Components.PageTree');
+
+/**
+ * This is the TreeNodeUI for the FilteringTree. This is the class which renders
+ * the tree nodes.
+ * The below modifications add another span tag around the icon for better skinning,
+ * and a prefix text which is displayed in front of the real "text" contents.
+ * Because the "text" property can be edited inline, the "prefixText" is used to
+ * prepend the editable text.
+ */
+TYPO3.Components.PageTree.PageTreeUI = function() {
+       TYPO3.Components.PageTree.PageTreeUI.superclass.constructor.apply(this, arguments);
+};
+Ext.extend(TYPO3.Components.PageTree.PageTreeUI, Ext.tree.TreeNodeUI, {
+       // private
+       // This method is taken from ExtJS sources. Modifications are marked with // START TYPO3-MODIFICATION
+       renderElements : function(n, a, targetNode, bulkRender) {
+               // add some indent caching, this helps performance when rendering a large tree
+               this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
+               var cb = typeof a.checked == 'boolean';
+               var href = a.href ? a.href : Ext.isGecko ? "" : "#";
+               var buf = ['<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">',
+                       '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
+                       '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
+                       // START TYPO3-MODIFICATION
+                       a.spriteIconCode,
+                       '<span class="prefixText">', a.prefixText, '</span>',
+                       // END TYPO3-MODIFICATION
+                       cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
+                       '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
+                       a.hrefTarget ? ' target="' + a.hrefTarget + '"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
+                       '<ul class="x-tree-node-ct" style="display:none;"></ul>',
+                       "</li>"].join('');
+
+               var nel;
+               if (bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())) {
+                       this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
+               } else {
+                       this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
+               }
+
+               this.elNode = this.wrap.childNodes[0];
+               this.ctNode = this.wrap.childNodes[1];
+               var cs = this.elNode.childNodes;
+               this.indentNode = cs[0];
+               this.ecNode = cs[1];
+               this.iconNode = cs[2];
+               // START TYPO3-MODIFICATION
+               Ext.fly(this.iconNode).on('click', function(event) {
+                       this.getOwnerTree().openContextMenu(this, event); // calling the context-menu event doesn't work!'
+                       event.stopEvent();
+               }, n);
+               // Index from 3 to 4 incremented!
+               var index = 4;
+               // STOP TYPO3-MODIFICATION
+               if (cb) {
+                       this.checkbox = cs[3];
+                       // fix for IE6
+                       this.checkbox.defaultChecked = this.checkbox.checked;
+                       index++;
+               }
+               this.anchor = cs[index];
+               this.textNode = cs[index].firstChild;
+       },
+
+       // private
+       // Overwriting the double click event, because we don't want to collapse or expand nodes
+       // by this event
+       onDblClick : function(e) {
+               if (this.disabled) {
+                       return;
+               }
+
+               if (this.fireEvent('beforedblclick', this.node, e) !== false) {
+                       this.fireEvent('dblclick', this.node, e);
+               }
+       }
+});
+
+TYPO3.Components.PageTree.App = Ext.extend(Ext.Panel, {
+       id: 'typo3-pagetree',
+       border: false,
+
+       tree: null,
+       topPanel: null,
+
+       dataProvider: null,
+
+       contextMenuConfiguration: null,
+
+       isControlPressed: false,
+
+       initComponent: function() {
+               this.dataProvider = TYPO3.Components.PageTree.DataProvider;
+
+               this.tree = new TYPO3.Components.PageTree.Tree({
+                       pageTree: this
+               });
+
+               this.topPanel = new TYPO3.Components.PageTree.FeaturePanel({
+                       pageTree: this
+               });
+
+               this.deletionDropZone = new TYPO3.Components.PageTree.DeletionDropZone({
+                       pageTree: this
+               });
+
+               this.addInlineEditorFeature(this.tree);
+               this.addNodeCopyPasteFeature(this.tree);
+
+               this.items = [
+                       this.topPanel, {
+                               border: false,
+                               id: 'typo3-pagetree-treeContainer',
+                               items: [
+                                       this.tree,
+                                       this.topPanel.filterTree
+                               ]
+                       },
+                       this.deletionDropZone
+               ];
+
+               TYPO3.Components.PageTree.App.superclass.initComponent.apply(this, arguments);
+       },
+
+       refreshTree: function() {
+               this.tree.root.reload();
+       },
+
+       /**
+        * Initialize the inline editor for the given tree.
+        *
+        * @param tree The Ext.tree.TreePanel where the Inline Editor should be added.
+        * @internal
+        */
+       addInlineEditorFeature: function(tree) {
+               var treeEditor = new Ext.tree.TreeEditor(
+                               tree, {
+                       cancelOnEsc: true,
+                       completeOnEnter: true,
+                       ignoreNoChange: true,
+                       editDelay: 250,
+                       shadow: false
+               }
+                               );
+
+               treeEditor.addListener('complete', TYPO3.Components.PageTree.PageActions.saveTitle, this);
+       },
+
+       addNodeCopyPasteFeature: function(tree) {
+               // When dragging starts, we need to add the explanation to the tool-tip
+               tree.addListener('startdrag', function(tree) {
+                       var explanationNode = document.createElement('div');
+                       Ext.fly(explanationNode).addClass('copyHelp');
+                       explanationNode.appendChild(document.createTextNode('Press Ctrl to copy.'));
+
+                       tree.explanationTooltip = explanationNode;
+                       tree.dragZone.proxy.ghost.dom.appendChild(explanationNode);
+                       this.deletionDropZone.setHeight(30);
+                       this.doLayout();
+               }, this);
+
+               // SECTION: move
+               // When a node has been moved via drag and drop, this is called.
+               // This event is ONLY called on move, NOT on copy, insert or delete.
+               tree.addListener('movenode', function(tree, movedNode, oldParent, newParent, position) {
+                       if (position == 0) {
+                               this.dataProvider.moveNodeToFirstChildOfDestination(movedNode.id, newParent.id);
+                       } else {
+                               var previousSibling = newParent.childNodes[position - 1];
+                               this.dataProvider.moveNodeAfterDestination(movedNode.id, previousSibling.id);
+                       }
+               }, this);
+
+               // SECTION: copy / create
+               // The following two event handlers deal with the node copying.
+               // The first one is called because we need to copy the node, and replace it by a dummy,
+               // and the second one disables the node, does the ajax request and un-hides the node again.
+               tree.addListener('beforenodedrop', function(de) {
+                       /*this.deletionDropZone.setHeight(0);
+                        this.doLayout();*/
+
+                       if (de.data && de.data.item && de.data.item.shouldCreateNewNode) {
+                               // Insertion - part 1
+                               var nodeType = de.data.item.nodeType;
+                               de.dropNode = new Ext.tree.TreeNode({
+                                       text: 'New...',
+                                       leaf: true
+                               });
+                               de.cancel = false; // Somehow, "cancel" is currently set to "true" - but do not know why.
+                               de.dropNode.uiProvider = TYPO3.Components.PageTree.PageTreeUI;
+                               de.dropNode.isInsertedNode = true;
+                               de.dropNode.serverNodeType = nodeType;
+                       } else {
+                               if (this.isControlPressed) {
+                                       // Copying - part 1
+                                       de.dropNode = new Ext.tree.TreeNode(de.dropNode.attributes);
+                                       de.dropNode.uiProvider = TYPO3.Components.PageTree.PageTreeUI;
+                                       de.dropNode.isCopiedNode = true;
+                               }
+                       }
+                       return true;
+               }, this);
+
+               tree.addListener('nodedrop', function(de) {
+                       // This callback method replaces the current node with the
+                       // one transmitted from the server.
+                       var callback = function(updatedNodeFromServer) {
+                               // We need to make sure that the UI Provider is correctly set, so that the rendering works for the new node.
+                               updatedNodeFromServer.uiProvider = TYPO3.Components.PageTree.PageTreeUI;
+                               var newTreeNode = new Ext.tree.TreeNode(updatedNodeFromServer);
+                               this.parentNode.replaceChild(newTreeNode, this);
+                       };
+
+                       if (de.dropNode.isInsertedNode) {
+                               // Insertion: - part 2
+                               de.dropNode.disable();
+                               if (de.dropNode.previousSibling) {
+                                       // We have previous sibling, so we want to add the record AFTER the previous sibling
+                                       this.dataProvider.insertNodeAfterDestination(de.dropNode.parentNode.id, de.dropNode.previousSibling.id, de.dropNode.serverNodeType, callback.createDelegate(de.dropNode));
+                               } else {
+                                       if (de.dropNode.parentNode) {
+                                               // We do not have a previous sibling, but a parent node. Thus, we add the node as the first child
+                                               // of the parent.
+                                               this.dataProvider.insertNodeToFirstChildOfDestination(de.dropNode.parentNode.id, de.dropNode.serverNodeType, callback.createDelegate(de.dropNode));
+                                       } else {
+                                               // Should not happen!
+                                       }
+                               }
+                       } else {
+                               if (de.dropNode.isCopiedNode) {
+                                       // Copying - part 2
+                                       de.dropNode.disable();
+                                       if (de.dropNode.previousSibling) {
+                                               // We have previous sibling, so we want to add the record AFTER the previous sibling
+                                               this.dataProvider.copyNodeAfterDestination(de.dropNode.id, de.dropNode.previousSibling.id, callback.createDelegate(de.dropNode));
+                                       } else {
+                                               if (de.dropNode.parentNode) {
+                                                       // We do not have a previous sibling, but a parent node. Thus, we add the node as the first child
+                                                       // of the parent.
+                                                       this.dataProvider.copyNodeToFirstChildOfDestination(de.dropNode.id, de.dropNode.parentNode, callback.createDelegate(de.dropNode));
+                                               } else {
+                                                       // Should not happen!
+                                               }
+                                       }
+                               }
+                       }
+               }, this);
+
+               // SECTION: Key Handlers
+               new Ext.KeyMap(document, {
+                       key: Ext.EventObject.CONTROL,
+                       fn: function() {
+                               this.isControlPressed = true;
+                               var copyHelpDiv = Ext.fly(tree.explanationTooltip);
+                               if (copyHelpDiv) {
+                                       copyHelpDiv.setVisibilityMode(Ext.Element.DISPLAY);
+                                       copyHelpDiv.hide();
+                               }
+                       },
+                       scope: this
+               }, 'keydown');
+
+               new Ext.KeyMap(document, {
+                       key: Ext.EventObject.CONTROL,
+                       fn: function() {
+                               this.isControlPressed = false;
+                               var copyHelpDiv = Ext.fly(tree.explanationTooltip);
+                               if (copyHelpDiv) {
+                                       copyHelpDiv.show();
+                               }
+                       },
+                       scope: this
+               }, 'keyup');
+       }
+});
+
+TYPO3.ModuleMenu.App.registerNavigationComponent('typo3-pagetree', function() {
+       return new TYPO3.Components.PageTree.App();
+});
+
+// XTYPE Registration
+Ext.reg('TYPO3.Components.PageTree.App', TYPO3.Components.PageTree.App);
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/components/pagetree/javascript/tree.js b/typo3/sysext/pagetree/components/pagetree/javascript/tree.js
new file mode 100644 (file)
index 0000000..e5d6adb
--- /dev/null
@@ -0,0 +1,147 @@
+Ext.namespace('TYPO3.Components.PageTree');
+
+TYPO3.Components.PageTree.Tree = Ext.extend(Ext.tree.TreePanel, {
+       id: 'typo3-pagetree-tree',
+       border: false,
+
+       enableDD: true,
+       dragConfig: {
+               ddGroup: 'TreeDD'
+       },
+
+       rootVisible: false,
+       pageTree: null,
+       contextMenuConfiguration: null,
+
+       initComponent: function() {
+               this.contextMenu = new TYPO3.Components.PageTree.ContextMenu({});
+
+               this.root = new Ext.tree.AsyncTreeNode({
+                       expanded: true,
+                       id: 'root'
+               });
+
+               this.loader = new Ext.tree.TreeLoader({
+                       directFn: this.pageTree.dataProvider.getNextTreeLevel,
+                       paramOrder: 'rootline',
+
+                       baseAttrs: {
+                               uiProvider: 't3'
+                       },
+
+                       uiProviders: {
+                               t3: TYPO3.Components.PageTree.PageTreeUI,
+                               rootNodeProvider: Ext.tree.TreeNodeUI
+                       },
+
+                       // The below method fixes a stupid bug of ExtJS / PHP JSON:
+                       // ExtJS expects the "expanded" attribute to be "true", and
+                       // it compares it with ===.
+                       // PHP json_encode submits a "1" if the value is true - thus,
+                       // the expanded property is not correctly evaluated by ExtJS.
+                       // Below, we do a loose type checking, and if it matches, we
+                       // set the JavaScript value "true". This circumvents the bug.
+                       createNode: function(attr) {
+                               if (attr.expanded) {
+                                       attr.expanded = true;
+                               }
+                               return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
+                       },
+
+                       listeners: {
+                               // We always have to transmit the rootline to the server.
+                               beforeload: function(treeLoader, node) {
+                                       treeLoader.baseParams.rootline = node.getPath();
+                               },
+                               load: function(treeLoader, node) {
+                                       // Helper function
+                                       var expandTransmittedNodesRecursively = function(node) {
+                                               var numberOfSubNodes = node.childNodes.length;
+                                               if (numberOfSubNodes > 0) {
+                                                       node.expand(false, false);
+                                               }
+                                               for (var i = 0; i < numberOfSubNodes; i++) {
+                                                       expandTransmittedNodesRecursively(node.childNodes[i]);
+                                               }
+                                       };
+                                       expandTransmittedNodesRecursively(node);
+                               }
+                       }
+               });
+
+               TYPO3.Components.PageTree.Tree.superclass.initComponent.apply(this, arguments);
+       },
+
+       // shows the context menu and creates it if it's not already done
+       openContextMenu: function(node, event) {
+               node.select();
+               var contextMenu = node.getOwnerTree().contextMenu;
+               contextMenu.removeAll();
+
+               var numberOfElementsInside = contextMenu.fillWithMenuItems(node, this.contextMenuConfiguration);
+               if (numberOfElementsInside > 0) {
+                       contextMenu.showAt(event.getXY());
+               }
+       },
+
+       listeners: {
+               // SECTION Contextmenu
+               // After rendering of the tree, we start the preloading of the context
+               // menu configuration
+               afterrender: {
+                       fn: function(tree) {
+                               if (tree.contextMenuConfiguration == null) {
+                                       this.pageTree.dataProvider.getContextMenuConfiguration(
+                                               function(result) {
+                                                       tree.contextMenuConfiguration = result;
+                                               }
+                                       );
+                               }
+                       }
+               },
+
+               // this event triggers the context menu
+               contextmenu: {
+                       fn: function(node, event) {
+                               node.getOwnerTree().openContextMenu(node, event);
+                       }
+               },
+
+               // SECTION Tree State Remember
+               expandnode: {
+                       fn: function (node) {
+                               this.pageTree.dataProvider.registerExpandedNode(node.getPath());
+                       }
+               },
+
+               collapsenode: {
+                       fn: function(node) {
+                               this.pageTree.dataProvider.registerCollapsedNode(node.getPath());
+                       }
+               },
+
+               // calls a given single click callback for the tree
+               click: {
+                       fn: function (node, event) {
+                               if (this.doubleClickEventActive) {
+                                       this.doubleClickEventActive = false;
+                                       event.stopEvent();
+                               } else {
+                                       eval(node.attributes.properties.clickCallback + '(node)');
+                               }
+                       },
+                       delay: 400
+               },
+
+               // seems to prevent some internal issues with the double-click for the tree editor
+               dblclick: {
+                       fn: function() {
+                               this.doubleClickEventActive = true;
+                               return false;
+                       }
+               }
+       }
+});
+
+// XTYPE Registration
+Ext.reg('TYPO3.Components.PageTree.Tree', TYPO3.Components.PageTree.Tree);
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/ext_autoload.php b/typo3/sysext/pagetree/ext_autoload.php
new file mode 100644 (file)
index 0000000..402ccf3
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+$extensionPath = t3lib_extMgm::extPath('pagetree');
+return array(
+       'tx_pagetree_abstracttree' => $extensionPath . 'classes/class.tx_pagetree_abstracttree.php',
+       'tx_pagetree_pagetree' => $extensionPath . 'classes/class.tx_pagetree_pagetree.php',
+       'tx_contextmenu_contextmenu' => $extensionPath . 'classes/class.tx_contextmenu_contextmenu.php',
+       'tx_pagetree_dataprovider_pagetree' => $extensionPath . 'extdirect/dataproviderclass.tx_pagetree_dataprovider_pagetree.php',
+       'tx_pagetree_dataprovider_abstracttree' => $extensionPath . 'extdirect/dataprovider/class.tx_pagetree_dataprovider_abstracttree.php',
+);
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/ext_emconf.php b/typo3/sysext/pagetree/ext_emconf.php
new file mode 100644 (file)
index 0000000..a8cb002
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+########################################################################
+# Extension Manager/Repository config file for ext "pagetree".
+#
+# Auto generated 16-11-2010 23:35
+#
+# 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' => 'Pagetree',
+       'description' => 'Pagetree based on ExtJS',
+       'category' => 'be',
+       'dependencies' => 'cms',
+       'conflicts' => '',
+       'priority' => '',
+       'loadOrder' => '',
+       'module' => '',
+       'doNotLoadInFE' => 1,
+       'state' => 'stable',
+       'internal' => 0,
+       'uploadfolder' => 0,
+       'createDirs' => '',
+       'modify_tables' => '',
+       'clearCacheOnLoad' => 0,
+       'lockType' => '',
+       'author' => 'TYPO3 Core Team',
+       'author_email' => '',
+       'author_company' => '',
+       'CGLcompliance' => '',
+       'CGLcompliance_note' => '',
+       'version' => '1.0.0',
+       '_md5_values_when_last_written' => 'a:19:{s:16:"ext_autoload.php";s:4:"c92c";s:12:"ext_icon.gif";s:4:"39bd";s:17:"ext_localconf.php";s:4:"a736";s:14:"ext_tables.php";s:4:"1e9f";s:25:"locallang_contextmenu.xml";s:4:"72dd";s:22:"locallang_pagetree.xml";s:4:"cd10";s:44:"classes/class.tx_contextmenu_contextmenu.php";s:4:"d8ab";s:42:"classes/class.tx_pagetree_abstracttree.php";s:4:"fc36";s:38:"classes/class.tx_pagetree_pagetree.php";s:4:"a142";s:36:"components/pagetree/css/pagetree.css";s:4:"9764";s:45:"components/pagetree/javascript/contextmenu.js";s:4:"3661";s:52:"components/pagetree/javascript/contextmenuactions.js";s:4:"89bd";s:50:"components/pagetree/javascript/deletiondropzone.js";s:4:"a924";s:46:"components/pagetree/javascript/featurepanel.js";s:4:"7c58";s:45:"components/pagetree/javascript/pageactions.js";s:4:"83f9";s:42:"components/pagetree/javascript/pagetree.js";s:4:"0786";s:38:"components/pagetree/javascript/tree.js";s:4:"4a3e";s:70:"extdirect/dataprovider/class.tx_pagetree_dataprovider_abstracttree.php";s:4:"bfef";s:66:"extdirect/dataprovider/class.tx_pagetree_dataprovider_pagetree.php";s:4:"3eb3";}',
+       'constraints' => array(
+               'depends' => array(
+                       'cms' => '',
+               ),
+               'conflicts' => array(
+               ),
+               'suggests' => array(
+               ),
+       ),
+       'suggests' => array(
+       ),
+);
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/ext_icon.gif b/typo3/sysext/pagetree/ext_icon.gif
new file mode 100644 (file)
index 0000000..4b7b783
Binary files /dev/null and b/typo3/sysext/pagetree/ext_icon.gif differ
diff --git a/typo3/sysext/pagetree/ext_localconf.php b/typo3/sysext/pagetree/ext_localconf.php
new file mode 100644 (file)
index 0000000..eb0f715
--- /dev/null
@@ -0,0 +1,217 @@
+<?php
+if (!defined('TYPO3_MODE')) {
+       die ('Access denied.');
+}
+
+       // pagetree user default configuration
+$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] .= '
+       options.pageTree.doktypesToShowInNewPageDragArea = 1,3,4,6,7,199,254
+';
+
+       // contextmenu user default configuration
+$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] .= '
+       options.contextMenu {
+               defaults {
+               }
+
+               table {
+                       pages.items {
+                               100 = ITEM
+                               100 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.viewPage
+                                       outerIcon = actions-document-view
+                                       icon =
+                                       callbackAction = TYPO3.Components.PageTree.ContextMenuActions.viewPage
+                               }
+
+                               200 = DIVIDER
+
+                               300 = ITEM
+                               300 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.disablePage
+                                       outerIcon = actions-edit-hide
+                                       icon =
+                                       callbackAction = TYPO3.Components.PageTree.ContextMenuActions.disablePage
+                               }
+
+                               400 = ITEM
+                               400 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.enablePage
+                                       outerIcon = actions-edit-unhide
+                                       icon =
+                                       callbackAction = TYPO3.Components.PageTree.ContextMenuActions.enablePage
+                               }
+
+                               500 = ITEM
+                               500 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.editPageProperties
+                                       outerIcon = actions-document-open
+                                       icon =
+                                       callbackAction = TYPO3.Components.PageTree.ContextMenuActions.editPageProperties
+                               }
+
+                               600 = ITEM
+                               600 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.info
+                                       outerIcon = actions-document-info
+                                       icon =
+                                       callbackAction = TYPO3.Components.PageTree.ContextMenuActions.openInfoPopUp
+                               }
+
+                               700 = ITEM
+                               700 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.history
+                                       outerIcon = apps-pagetree-page-default+status-overlay-timing
+                                       icon =
+                                       callbackAction = TYPO3.Components.PageTree.ContextMenuActions.openHistoryPopUp
+                               }
+
+                               800 = DIVIDER
+
+                               900 = SUBMENU
+                               900 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.copyPasteActions
+
+                                       100 = ITEM
+                                       100 {
+                                               label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.newPage
+                                               outerIcon = actions-document-new
+                                               icon =
+                                               callbackAction = TYPO3.Components.PageTree.ContextMenuActions.newPageWizard
+                                       }
+
+                                       200 = DIVIDER
+
+                                       300 = ITEM
+                                       300 {
+                                               label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.cutPage
+                                               outerIcon = actions-edit-cut
+                                               icon =
+                                               callbackAction = TYPO3.Components.PageTree.ContextMenuActions.stub
+                                       }
+
+                                       400 = ITEM
+                                       400 {
+                                               label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.copyPage
+                                               outerIcon = actions-edit-copy
+                                               icon =
+                                               callbackAction = TYPO3.Components.PageTree.ContextMenuActions.stub
+                                       }
+
+                                       500 = ITEM
+                                       500 {
+                                               label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.pasteIntoPage
+                                               outerIcon = actions-document-paste-after
+                                               icon =
+                                               callbackAction = TYPO3.Components.PageTree.ContextMenuActions.stub
+                                       }
+
+                                       600 = ITEM
+                                       600 {
+                                               label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.pasteAfterPage
+                                               outerIcon = actions-document-paste-into
+                                               icon =
+                                               callbackAction = TYPO3.Components.PageTree.ContextMenuActions.stub
+                                       }
+
+                                       700 = DIVIDER
+
+                                       800 = ITEM
+                                       800 {
+                                               label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.deletePage
+                                               outerIcon = actions-edit-delete
+                                               icon =
+                                               callbackAction = TYPO3.Components.PageTree.ContextMenuActions.stub
+                                       }
+                               }
+
+                               1000 = SUBMENU
+                               1000 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.branchActions
+
+                                       100 = ITEM
+                                       100 {
+                                               label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.tempMountPoint
+                                               outerIcon = actions-system-extension-documentation
+                                               icon =
+                                               callbackAction = TYPO3.Components.PageTree.ContextMenuActions.stub
+                                       }
+                               }
+                       }
+               }
+
+               files {
+                       items {
+                               100 = ITEM
+                               100 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.renameFolder
+                                       outerIcon = actions-edit-rename
+                                       icon =
+                                       callbackAction = TYPO3.Widget.ContextMenu.FolderActions.renameFolder
+                               }
+
+                               200 = ITEM
+                               200 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.uploadFilesToFolder
+                                       outerIcon = actions-edit-upload
+                                       icon =
+                                       callbackAction = TYPO3.Widget.ContextMenu.FolderActions.uploadFilesToFolder
+                               }
+
+                               300 = ITEM
+                               300 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.createFolder
+                                       outerIcon = actions-edit-add
+                                       icon =
+                                       callbackAction = TYPO3.Components.PageTree.ContextMenuActions.createFolder
+                               }
+
+                               400 = ITEM
+                               400 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.folderInfo
+                                       outerIcon = actions-document-info
+                                       icon =
+                                       callbackAction = TYPO3.Widget.ContextMenu.FolderActions.openInfoPopUp
+                               }
+
+                               500 = DIVIDER
+
+                               600 = ITEM
+                               600 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.copyFolder
+                                       outerIcon = actions-edit-copy
+                                       icon =
+                                       callbackAction = TYPO3.Widget.ContextMenu.FolderActions.copyFolder
+                               }
+
+                               700 = ITEM
+                               700 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.cutFolder
+                                       outerIcon = actions-edit-cut
+                                       icon =
+                                       callbackAction = TYPO3.Widget.ContextMenu.FolderActions.cutFolder
+                               }
+
+                               800 = ITEM
+                               800 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.pasteIntoFolder
+                                       outerIcon = actions-document-paste-after
+                                       icon =
+                                       callbackAction = TYPO3.Components.PageTree.ContextMenuActions.pasteIntoFolder
+                               }
+
+                               900 = DIVIDER
+
+                               1000 = ITEM
+                               1000 {
+                                       label = LLL:EXT:pagetree/locallang_contextmenu.xml:cm.deleteFolder
+                                       outerIcon = actions-edit-delete
+                                       icon =
+                                       callbackAction = TYPO3.Components.PageTree.ContextMenuActions.deleteFolder
+                               }
+                       }
+               }
+       }
+';
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/ext_tables.php b/typo3/sysext/pagetree/ext_tables.php
new file mode 100644 (file)
index 0000000..1919394
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+if (!defined('TYPO3_MODE')) {
+       die ('Access denied.');
+}
+
+if (TYPO3_MODE === 'BE') {
+       $modules = array(
+               'web_layout', 'web_view', 'web_list', 'web_info', 'web_perm', 'web_func', 'web_ts',
+               'web_WorkspacesWorkspaces', 'web_txrecyclerM1', 'web_txversionM1'
+       );
+       foreach ($modules as $module) {
+               t3lib_extMgm::addNavigationComponent($module, 'typo3-pagetree', array(
+                       'TYPO3.Components.PageTree'
+               ));
+       }
+
+       $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect']['TYPO3.Components.PageTree.DataProvider'] =
+               t3lib_extMgm::extPath($_EXTKEY) . 'extdirect/dataprovider/class.tx_pagetree_dataprovider_pagetree.php:tx_pagetree_dataprovider_Pagetree';
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/extdirect/dataprovider/class.tx_pagetree_dataprovider_abstracttree.php b/typo3/sysext/pagetree/extdirect/dataprovider/class.tx_pagetree_dataprovider_abstracttree.php
new file mode 100644 (file)
index 0000000..c1d2782
--- /dev/null
@@ -0,0 +1,224 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2009 Sebastian Kurfuerst
+ *  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 tree dataprovider
+ *
+ * @author     Sebastian Kurfuerst <sebastian@typo3.org>
+ * @package    TYPO3
+ */
+abstract class tx_pagetree_dataprovider_AbstractTree {
+
+       protected $treeName;
+
+       /**
+        * Fetches the Items for the next level of the tree
+        *
+        * @param mixed The ID of the tree node
+        * @param string The rootline of the element
+        * @return array The next level tree elements
+        */
+       public function getNextTreeLevel($id, $rootline) {
+               // Load UC
+               $GLOBALS['BE_USER']->backendSetUC();
+               $treeState =& $GLOBALS['BE_USER']->uc['treeState'][$this->treeName];
+
+               $records = $this->getRecordsForNextTreeLevel($this->decodeTreeNodeId($id));
+               $results = array();
+               foreach ($records as $record) {
+                       $singleResult = $this->prepareRecordForResultList($record, $rootline);
+                       $rootlineOfSubElement = $rootline;
+                       // the rootline functionality is completely nonsense and should be replaced completely
+                       if ($singleResult['id']) {
+                               $rootlineOfSubElement = $rootline . '/' . $singleResult['id'];
+                       }
+                       if (isset($treeState[$rootlineOfSubElement])) {
+                               $singleResult['expanded'] = true;
+                               $singleResult['children'] = $this->getNextTreeLevel($singleResult['id'], $rootlineOfSubElement);
+                       }
+
+                       $results[] = $singleResult;
+               }
+               return $results;
+       }
+
+       /**
+        * Prepares the records for the result list & fetches the actions
+        *
+        * @param array $record The record to be prepared
+        * @param string $rootline The rootline of the record
+        * @return array The prepared result
+        */
+       protected function prepareRecordForResultList($record, $rootline = '') {
+               $currentResult = $this->getTreeNodePropertiesForRecord($record, $rootline);
+               $currentResult['actions'] = $this->getActionsForRecord($record);
+               return $currentResult;
+       }
+
+       /**
+        * Saves the current expanded state of the tree to the user settings
+        *
+        * @param string $rootline The current rootline
+        */
+       public function registerExpandedNode($rootline) {
+               $GLOBALS['BE_USER']->backendSetUC();
+               $treeState =& $GLOBALS['BE_USER']->uc['treeState'][$this->treeName];
+               $treeState[$rootline] = TRUE;
+               $GLOBALS['BE_USER']->writeUC();
+       }
+
+       /**
+        * Saves the current collapsed state of the tree to the user settings
+        *
+        * @param string $rootline The current rootline
+        */
+       public function registerCollapsedNode($rootline) {
+               $GLOBALS['BE_USER']->backendSetUC();
+               $treeState =& $GLOBALS['BE_USER']->uc['treeState'][$this->treeName];
+               if (isset($treeState[$rootline])) {
+                       unset($treeState[$rootline]);
+               }
+               $GLOBALS['BE_USER']->writeUC();
+       }
+
+
+       /**
+        * Encode node id and its rootline into an global unique identifier even if the same
+        * node id is used a couple of times (webmounts, filemount, ...)
+        *
+        * @param string $id node id
+        * @param string $rootline rootline / path include base-node like webmount-id/filemount-name
+        */
+       protected function encodeTreeNodeId($id, $rootline) {
+               if ($rootline == '/root') {
+                       return $this->idEncode($id);
+               } else {
+                       list(, , $mountpoint) = explode('/', $rootline);
+                       return 'mp' . $this->idEncode($mountpoint) . '-' . $this->idEncode($id);
+               }
+       }
+
+       /**
+        * Decodes the id of the tree node
+        *
+        * @param string $id The ID string
+        * @return int
+        */
+       protected function decodeTreeNodeId($id) {
+               if (strpos($id, 'mp') === 0) {
+                       // everything _INSIDE_ a mountpage
+                       // mp is in there, extract the ID!
+                       list(, $id) = explode('-', $id);
+                       return $this->idDecode($id);
+               } else {
+                       // /root, and /root/XXX (mountpage)
+                       return $id;
+               }
+       }
+
+       /**
+        * Encodes an id, this one just does an intval, if you need
+        * something more implement your own method
+        * @return the encoded ID
+        */
+       protected function idEncode($id) {
+               return intval($id);
+       }
+
+       /**
+        * Decodes an id, this one just does an intval, if you need
+        * something more implement your own method
+        * @return the decoded ID
+        */
+       protected function idDecode($id) {
+               return intval($id);
+       }
+
+       /**
+        * Get Label
+        *
+        * @param string $label label name
+        * @return string the localized label string
+        */
+       protected function getLabel($label) {
+               return $GLOBALS['LANG']->sL($label, TRUE);
+       }
+
+       /**
+        * Get Icon for Context Menu
+        *
+        * @param string $icon Icon name
+        * @return string Returns part of the img tag like ' src="[backPath][src]" [wHattribs]'
+        */
+       protected function getIcon($icon) {
+
+               return $icon;
+       }
+
+       /**
+        * Fetches a filtered tree
+        *
+        * @param string $searchString The string to search for
+        *
+        */
+       abstract public function getFilteredTree($searchString);
+
+       /**
+        * Fetches the records for the next tree level (subpages or subdirectories f.e.)
+        *
+        * @return array a multidimensional array of records to be displayed in the tree on the next level, including the metainformation for the record
+        */
+       abstract protected function getRecordsForNextTreeLevel($id);
+
+       /**
+        * gets a record, and MUST return an array with the following properties:
+        * - id (UNIQUE id)
+        * - leaf (boolean) - FALSE, if it has child records
+        * - text - the title of the node in the tree
+        * additionally, you can specify any of the Ext.tree.TreeNode properties, except "attributes".
+        */
+       abstract protected function getTreeNodePropertiesForRecord($record, $rootline);
+
+
+       /**
+        * Get an array of actions which can be performed with this single record. Used for the context menu.
+        */
+       abstract protected function getActionsForRecord($record);
+
+       /**
+        * Generates the context menu items which can be toogled on/off by the record actions.
+        * You need to define them inside a hash array with the fields "text" and "callback".
+        * @see getActionsForRecord
+        */
+       abstract public function getContextMenuConfiguration();
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/sysext/pagetree/extdirect/dataprovider/class.tx_pagetree_dataprovider_abstracttree.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/sysext/pagetree/extdirect/dataprovider/class.tx_pagetree_dataprovider_abstracttree.php']);
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/pagetree/extdirect/dataprovider/class.tx_pagetree_dataprovider_pagetree.php b/typo3/sysext/pagetree/extdirect/dataprovider/class.tx_pagetree_dataprovider_pagetree.php
new file mode 100644 (file)
index 0000000..e507308
--- /dev/null
@@ -0,0 +1,530 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2009 Sebastian Kurfuerst
+ *  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 tx_pagetree_dataprovider_Pagetree extends tx_pagetree_dataprovider_AbstractTree {
+       public $enableIcons = TRUE;
+       public $backPath = '';
+       protected $treeName = 'pages';
+
+       /**
+        * PageTree
+        *
+        * @var tx_pagetree_Pagetree
+        */
+       protected $pageTree;
+
+       public function __construct() {
+               $this->pageTree = t3lib_div::makeInstance('tx_pagetree_Pagetree');
+
+               if ($GLOBALS['BE_USER']->uc['noMenuMode']
+                       && strcmp($GLOBALS['BE_USER']->uc['noMenuMode'], 'icons')
+               ) {
+                       $this->enableIcons = FALSE;
+               }
+       }
+
+       public function moveNodeToFirstChildOfDestination($movedNode, $destination) {
+               $movedNode = $this->decodeTreeNodeId($movedNode);
+               $destination = $this->decodeTreeNodeId($destination);
+
+               $uid = intval($movedNode);
+               $destination = intval($destination);
+               $this->pageTree->move($uid, $destination);
+       }
+
+       public function moveNodeAfterDestination($movedNode, $destination) {
+               $movedNode = $this->decodeTreeNodeId($movedNode);
+               $destination = $this->decodeTreeNodeId($destination);
+
+               $uid = intval($movedNode);
+               $destination = intval($destination);
+               $this->pageTree->move($uid, -$destination);
+       }
+
+       public function copyNodeToFirstChildOfDestination($copiedNode, $destination) {
+               $copiedNode = $this->decodeTreeNodeId($copiedNode);
+               $destination = $this->decodeTreeNodeId($destination);
+
+               $uid = intval($copiedNode);
+               $destination = intval($destination);
+               $newPageId = $this->pageTree->copy($uid, $destination);
+
+               return $this->getWholeRecordForIdAndPrepareItForResultList($newPageId);
+       }
+
+       public function copyNodeAfterDestination($copiedNode, $destination) {
+               $copiedNode = $this->decodeTreeNodeId($copiedNode);
+               $destination = $this->decodeTreeNodeId($destination);
+
+               $uid = intval($copiedNode);
+               $destination = intval($destination);
+
+               $newPageId = $this->pageTree->copy($uid, -$destination);
+
+               return $this->getWholeRecordForIdAndPrepareItForResultList($newPageId);
+       }
+
+       public function insertNodeToFirstChildOfDestination($parentNode, $pageType) {
+               $parentNode = intval($this->decodeTreeNodeId($parentNode));
+
+               $newPageId = $this->pageTree->create($parentNode, $parentNode, $pageType);
+               return $this->getWholeRecordForIdAndPrepareItForResultList($newPageId);
+       }
+
+       public function insertNodeAfterDestination($parentNode, $destination, $pageType) {
+               $parentNode = intval($this->decodeTreeNodeId($parentNode));
+               $destination = intval($this->decodeTreeNodeId($destination));
+
+               $newPageId = $this->pageTree->create($parentNode, -$destination, $pageType);
+               return $this->getWholeRecordForIdAndPrepareItForResultList($newPageId);
+       }
+
+
+       public function deleteNode($nodeId) {
+               $id = $this->decodeTreeNodeId($nodeId);
+               $this->pageTree->remove($id);
+       }
+
+       public function undeleteNode($nodeId) {
+               $id = $this->decodeTreeNodeId($nodeId);
+               $this->pageTree->restore($id);
+       }
+
+       protected function getWholeRecordForIdAndPrepareItForResultList($newNode) {
+               $resultRow = t3lib_BEfunc::getRecordWSOL(
+                       'pages',
+                       $newNode,
+                       $fields = '*',
+                       $where = '',
+                       $useDeleteClause = TRUE,
+                       $GLOBALS['BE_USER']->uc['currentPageTreeLanguage']
+               );
+               return $this->prepareRecordForResultList($resultRow);
+       }
+
+       public function getFilteredTree($searchString) {
+               $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                       'uid',
+                       'pages',
+                               'title LIKE ' . $GLOBALS['TYPO3_DB']->fullQuoteStr('%' . $searchString . '%', 'pages') .
+                                               t3lib_BEfunc::deleteClause('pages') .
+                                               t3lib_BEfunc::versioningPlaceholderClause('pages') . ' AND ' .
+                                               $GLOBALS['BE_USER']->getPagePermsClause(1)
+               );
+
+               $result = array();
+               foreach ($records as $singleRecord) {
+                       $rootline = t3lib_BEfunc::BEgetRootLine($singleRecord['uid']);
+                       $rootline = array_reverse($rootline);
+                       array_shift($rootline);
+                       $currentNode = &$result; // what the fuck? Who codes such unmaintainable stuff?
+
+                       foreach ($rootline as $rootlineElement) {
+                               $rootlineElement['_subpages'] = 1;
+                               if (isset($currentNode[$rootlineElement['uid']])) {
+                                       $currentNode = &$currentNode[$rootlineElement['uid']]['children'];
+                               }
+                               else {
+                                       $currentNode[$rootlineElement['uid']] = $this->prepareRecordForResultList($rootlineElement);
+                                       $currentNode[$rootlineElement['uid']]['text'] = preg_replace('/(' .
+                                                       preg_quote($searchString, '/') . ')/i', '<strong class="highlight">$1</strong>',
+                                               $currentNode[$rootlineElement['uid']]['text']);
+
+
+                                       $currentNode[$rootlineElement['uid']]['children'] = array();
+                                       $currentNode = &$currentNode[$rootlineElement['uid']]['children'];
+                               }
+                       }
+               }
+               $this->convertChildrenToUnnamedArray($result);
+               return $result;
+       }
+
+       private function convertChildrenToUnnamedArray(&$array) {
+               $array = array_values($array);
+               foreach ($array as &$value) {
+                       if (isset($value['children']) && is_array($value['children'])) {
+                               $this->convertChildrenToUnnamedArray($value['children']);
+                       }
+               }
+       }
+
+       protected function getRecordsForNextTreeLevel($id) {
+               if ($id === 'root') {
+                       return $this->pageTree->getTreeMounts();
+               } else {
+                       return $this->pageTree->getSubPages($id);
+               }
+       }
+
+       protected function getTreeNodePropertiesForRecord($record, $rootline) {
+               if ($this->pageTree->getTsConfigOptionForPagetree('showNavTitle')) {
+                       $fields = array('nav_title', 'title');
+               } else {
+                       $fields = array('title', 'nav_title');
+               }
+
+               $textSourceField = '';
+               foreach ($fields as $field) {
+                       if (!empty($record[$field])) {
+                               $text = $record[$field];
+                               $textSourceField = $field;
+                               break;
+                       }
+               }
+
+               $languageOverlayIcon = '';
+               if ($record['_PAGES_OVERLAY']) {
+                       $currentTreeLanguage = intval($GLOBALS['BE_USER']->uc['currentPageTreeLanguage']);
+                       $languageRecord = $this->getLanguage($currentTreeLanguage);
+                       $languageShortcut = $this->getLanguageShortcutFromFile($languageRecord['flag']);
+                       $languageOverlayIcon = t3lib_iconWorks::getSpriteIcon(
+                               'flags-' . $languageShortcut . '-overlay'
+                       );
+                       unset($languageRecord, $languageShortcut);
+               }
+
+               if ($record['uid'] !== 0) {
+                       $spriteIconCode = t3lib_iconWorks::getSpriteIconForRecord(
+                               'pages',
+                               $record,
+                               array(
+                                       'html' => $languageOverlayIcon,
+                               )
+                       );
+               } else {
+                       $spriteIconCode = t3lib_iconWorks::getSpriteIcon('apps-pagetree-root');
+               }
+               return array(
+                       'record' => $record,
+                       'id' => $this->encodeTreeNodeId($record['uid'], $rootline),
+                       'qtip' => 'ID: ' . $record['uid'],
+                       'leaf' => $record['_subpages'] == 0,
+                       'text' => $text,
+                       'prefixText' => $this->getPrefixForDisplayedTitle($record), // This is the prefix before the title string
+                       'spriteIconCode' => $spriteIconCode,
+                       '_meta' => array('numSubPages' => $record['_subpages']),
+                       'properties' => array(
+                               'clickCallback' => 'TYPO3.Components.PageTree.PageActions.singleClick',
+                               'textSourceField' => $textSourceField,
+                               'realId' => $record['uid']
+                       )
+               );
+       }
+
+       public function getSpriteIconClasses($icon) {
+               return t3lib_iconWorks::getSpriteIconClasses($icon);
+       }
+
+       // @todo respect tsconfig options
+       protected function getActionsForRecord($record) {
+               return $record['_actions'];
+       }
+
+       protected function getPrefixForDisplayedTitle($row) {
+               $prefix = '';
+
+               if ($this->pageTree->getTsConfigOptionForPagetree('showPageIdWithTitle')) {
+                       $prefix .= $row['uid'];
+               }
+               return $prefix;
+       }
+
+       public function getPageInformation($pageId, $fields) {
+               return $this->pageTree->getPageInformationForGivenFields($pageId, $fields);
+       }
+
+       protected function getNextContextMenuLevel($typoScriptConfiguration, $level) {
+               if ($level > 5) {
+                       return array();
+               }
+
+               $type = '';
+               $contextMenuItems = array();
+               foreach ($typoScriptConfiguration as $index => $typoScriptItem) {
+                       $hash = md5(microtime());
+
+                       if (substr($index, -1) === '.') {
+                               switch ($type) {
+                                       case 'SUBMENU':
+                                               $contextMenuItems['--submenu-' . $hash . '--'] =
+                                                               $this->getNextContextMenuLevel($typoScriptItem, ++$level);
+
+                                               $contextMenuItems['--submenu-' . $hash . '--']['text'] =
+                                                               $this->getLabel($typoScriptItem['label']);
+                                               break;
+
+                                       case 'ITEM':
+                                               // transform icon and text
+                                               $contextMenuItems[$index] = array(
+                                                       'text' => $this->getLabel($typoScriptItem['label'])
+                                               );
+
+                                               // push additional attributes
+                                               $contextMenuItems[$index] = array_merge(
+                                                       $typoScriptItem,
+                                                       $contextMenuItems[$index]
+                                               );
+
+                                               if ($this->enableIcons) {
+                                                       if (!empty($typoScriptItem['icon'])) {
+                                                               $contextMenuItems[$index]['icon'] =
+                                                                               $this->getIcon($typoScriptItem['icon']);
+                                                       } elseif (isset($typoScriptItem['outerIcon'])) {
+                                                               $contextMenuItems[$index] = array_merge(
+                                                                       $contextMenuItems[$index],
+                                                                       $this->getIconClassFromIcon($typoScriptItem['outerIcon'])
+                                                               );
+                                                       }
+                                               } else {
+                                                       $contextMenuItems[$index]['icon'] = '';
+                                               }
+                                               break;
+
+                                       default:
+                                               break;
+                               }
+                       } else {
+                               $type = $typoScriptItem;
+
+                               // add divider
+                               if ($type === 'DIVIDER') {
+                                       $contextMenuItems['--divider-' . $hash . '--'] = array(
+                                               'action' => 'divider'
+                                       );
+                               }
+                       }
+               }
+
+               return $contextMenuItems;
+       }
+
+       public function getContextMenuConfiguration() {
+               // @TODO defaults must be considered
+               $contextMenuItemTypoScriptConfiguration = $GLOBALS['BE_USER']->getTSConfig(
+                       'options.contextMenu.table.pages.items'
+               );
+               $contextMenuItemTypoScriptConfiguration = $contextMenuItemTypoScriptConfiguration['properties'];
+
+               $contextMenuItems = $this->getNextContextMenuLevel(
+                       $contextMenuItemTypoScriptConfiguration,
+                       0
+               );
+               return $contextMenuItems;
+       }
+
+       /**
+        * Fetches the attributes for an action inside the context menu.
+        * @todo optimize to prevent much useless ajax calls...
+        *
+        * @param int $id page uid
+        * @param string $menuItemId clickmenu item identifier
+        */
+       public function getAttributesForContextMenu($id, $menuItemId) {
+               $this->decodeTreeNodeId($id);
+
+               switch ($menuItemId) {
+                       default:
+                               $attributes = array();
+                               break;
+               }
+
+               return $attributes;
+       }
+
+       /**
+        * Helper function to generate the neede css classes for img display with overlay
+        *
+        * @param string $icon icon identifier
+        */
+       protected function getIconClassFromIcon($icon) {
+               return array(
+                       //                      'hrefTarget' => $iconClass[0], // href target is used as a small hack for the template function of the menu.Item
+                       'iconCls' => $this->getSpriteIconClasses($icon)
+               );
+       }
+
+       /**
+        * Returns the page view link
+        *
+        * @param int $id page id
+        * @param unknown $workspacePreview ???
+        * @return string
+        */
+       public function getViewLink($id, $workspacePreview) {
+               $id = $this->decodeTreeNodeId($id);
+               return $this->pageTree->getViewLink($id, $workspacePreview);
+       }
+
+       /**
+        *
+        * @param string $id page id (can be numerical or like "mp-12" in case of mount-points ,...)
+        * @param string $title
+        * @param string $textSourceField
+        */
+       public function setPageTitle($id, $title, $textSourceField) {
+               $id = $this->decodeTreeNodeId($id);
+               $this->pageTree->updateTextInputField($id, $title, $textSourceField);
+       }
+
+       /**
+        * Enables or disables a page and returns the new node
+        *
+        * @param string $id page id (can be numerical or like "mp-12" in case of mount-points ,...)
+        * @param boolean $enabled true for enabling and false for disabling
+        * @return array new node
+        */
+       public function tooglePageVisibility($id, $enabled) {
+               $id = $this->decodeTreeNodeId($id);
+               $this->pageTree->updateTextInputField($id, $enabled, 'hidden');
+
+               $node = $this->getWholeRecordForIdAndPrepareItForResultList($id);
+               return $node;
+       }
+
+       /**
+        * Returns the localized list of doktypes to display
+        *
+        * see User TSconfig: options.pageTree.doktypesToShowInNewPageDragArea
+        */
+       public function getNodeTypes() {
+               $doktypes = t3lib_div::trimExplode(
+                       ',',
+                       $this->pageTree->getTsConfigOptionForPagetree('doktypesToShowInNewPageDragArea')
+               );
+
+               $output = array();
+               foreach ($doktypes as $doktype) {
+                       $label = $this->getLabel('LLL:EXT:pagetree/locallang_pagetree.xml:page.doktype.' . $doktype);
+                       $output[] = array(
+                               'nodeType' => $doktype,
+                               'title' => $label,
+                               'cls' => 'topPanel-button ' . $this->getSpriteIconClasses(
+                                       $GLOBALS['TCA']['pages']['ctrl']['typeicon_classes'][$doktype]
+                               ),
+                       );
+               }
+               return $output;
+       }
+
+       /**
+        * Returns the language shortcut from a language file
+        *
+        * @param string $file language flag with or without the related directory
+        * @return mixed language shortcut or boolean false
+        */
+       protected function getLanguageShortcutFromFile($file) {
+               if (strpos($file, '/') !== FALSE) {
+                       $file = basename($file);
+               }
+
+               return substr($file, 0, strrpos($file, '.'));
+       }
+
+       /**
+        * Returns a language record defined by the id parameter
+        *
+        * @param int $uid language id
+        * @return array
+        */
+       protected function getLanguage($uid) {
+               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
+                       'flag',
+                       'sys_language',
+                               'pid=0 AND uid=' . $uid .
+                                               t3lib_BEfunc::deleteClause('sys_language')
+               );
+
+               $record = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
+               $GLOBALS['TYPO3_DB']->sql_free_result($res);
+
+               return $record;
+       }
+
+       /**
+        * Returns an array of system languages with parameters based on the properties
+        * of Ext.Button.
+        *
+        * @return array
+        */
+       public function getLanguages() {
+               $languageRecords = t3lib_befunc::getSystemLanguages();
+
+               $output = array();
+               foreach ($languageRecords as $languageRecord) {
+                       $languageShortcut = $this->getLanguageShortcutFromFile($languageRecord[2]);
+                       if ($languageShortcut === FALSE) {
+                               $languageShortcut = 'europeanunion';
+                       }
+
+                       $output[] = array(
+                               'language' => $languageRecord[1],
+                               'languageShortcut' => $languageShortcut,
+                               'cls' => 'topPanel-button ' .
+                                               $this->getSpriteIconClasses('flags-' . $languageShortcut),
+                       );
+               }
+
+               return $output;
+       }
+
+       /**
+        * Returns the european flag sprite icon css classes
+        *
+        * @TODO What if a flag is added to the core, but isn't inside the sprite?
+        * @return string
+        */
+       public function getIconForCurrentLanguage() {
+               $currentTreeLanguage = intval($GLOBALS['BE_USER']->uc['currentPageTreeLanguage']);
+
+               $icon = 'flags-europeanunion';
+               if ($currentTreeLanguage !== 0) {
+                       $languageRecord = $this->getLanguage($currentTreeLanguage);
+                       $icon = 'flags-' . $this->getLanguageShortcutFromFile($languageRecord['flag']);
+               }
+
+               return $this->getSpriteIconClasses($icon);
+       }
+
+       /**
+        * Saves the given language id into the backend user configuration array
+        *
+        * @param int $languageId
+        * @return void
+        */
+       public function saveCurrentTreeLanguage($languageId) {
+               $GLOBALS['BE_USER']->backendSetUC();
+               $GLOBALS['BE_USER']->uc['currentPageTreeLanguage'] = intval($languageId);
+               $GLOBALS['BE_USER']->writeUC();
+       }
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/sysext/pagetree/extdirect/dataprovider/class.t3lib_tree_dataprovider_pagetree.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/sysext/pagetree/extdirect/dataprovider/class.t3lib_tree_dataprovider_pagetree.php']);
+}
+
+?>
diff --git a/typo3/sysext/pagetree/locallang_contextmenu.xml b/typo3/sysext/pagetree/locallang_contextmenu.xml
new file mode 100644 (file)
index 0000000..6c87d29
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+       <meta type="array">
+               <description>TYPO3 core labels for the clickmenus</description>
+               <type>module</type>
+       </meta>
+       <data type="array">
+               <languageKey index="default" type="array">
+                       <label index="cm.copyPage">Copy page</label>
+                       <label index="cm.cutPage">Cut page</label>
+                       <label index="cm.viewPage">View live page</label>
+                       <label index="cm.editPageProperties">Edit page properties</label>
+                       <label index="cm.newPage">Add page</label>
+                       <label index="cm.pasteIntoPage">Paste into</label>
+                       <label index="cm.pasteAfterPage">Paste after</label>
+                       <label index="cm.deletePage">Delete page</label>
+                       <label index="cm.disablePage">Disable page</label>
+                       <label index="cm.enablePage">Enable page</label>
+                       <label index="cm.info">Page infos</label>
+                       <label index="cm.history">Page history</label>
+                       <label index="cm.tempMountPoint">Mount as treeroot</label>
+                       <label index="cm.branchActions">Branch Actions</label>
+                       <label index="cm.copyPasteActions">Page Actions</label>
+                       
+                       <label index="cm.renameFolder">Rename</label>
+                       <label index="cm.uploadFilesToFolder">Upload Files</label>
+                       <label index="cm.createFolder">Create New Folder</label>
+                       <label index="cm.folderInfo">Folder Info</label>
+                       <label index="cm.copyFolder">Copy</label>
+                       <label index="cm.cutFolder">Cut</label>
+                       <label index="cm.pasteIntoFolder">Paste</label>
+                       <label index="cm.deleteFolder">Delete</label>
+               </languageKey>
+       </data>
+</T3locallang>
diff --git a/typo3/sysext/pagetree/locallang_pagetree.xml b/typo3/sysext/pagetree/locallang_pagetree.xml
new file mode 100644 (file)
index 0000000..4033063
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+       <meta type="array">
+               <description>TYPO3 core labels the page tree</description>
+               <type>module</type>
+       </meta>
+       <data type="array">
+               <languageKey index="default" type="array">
+                       <label index="page.doktype.1">Default</label>
+                       <label index="page.doktype.3">External Link</label>
+                       <label index="page.doktype.4">Shortcut</label>
+                       <label index="page.doktype.5">Not in Menu</label>
+                       <label index="page.doktype.6">BE user Section</label>
+                       <label index="page.doktype.7">Mountpoint</label>
+                       <label index="page.doktype.199">Spacer</label>
+                       <label index="page.doktype.254">Sysfolder</label>
+               </languageKey>
+       </data>
+</T3locallang>