diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js b/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
index 21e9f2db8a45eec8488b4241a0be91f9f9a65195..3ae3685b40783413576d9581984024712aa2d422 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
@@ -89,7 +89,7 @@ define('TYPO3/CMS/Backend/Modal', ['jquery', 'TYPO3/CMS/Backend/FlashMessages'],
 			Modal.currentModal.trigger('modal-dismiss');
 		});
 
-		if (content instanceof $) {
+		if (typeof content === 'object') {
 			Modal.currentModal.find('.modal-body').append(content);
 		} else {
 			// we need html, check if we have to wrap content in <p>
diff --git a/typo3/sysext/recycler/Classes/Controller/DeletedRecordsController.php b/typo3/sysext/recycler/Classes/Controller/DeletedRecordsController.php
index 690b7fcf69fcf2b2d1431b6aa2e47694d2328fdc..9b41de009896b03178fffe8bebdc801d27cc4cd4 100644
--- a/typo3/sysext/recycler/Classes/Controller/DeletedRecordsController.php
+++ b/typo3/sysext/recycler/Classes/Controller/DeletedRecordsController.php
@@ -15,6 +15,9 @@ namespace TYPO3\CMS\Recycler\Controller;
  */
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Backend\Utility\IconUtility;
+use TYPO3\CMS\Core\DataHandling\DataHandler;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Recycler\Utility\RecyclerUtility;
 
 /**
@@ -26,22 +29,25 @@ use TYPO3\CMS\Recycler\Utility\RecyclerUtility;
 class DeletedRecordsController {
 
 	/**
-	 * @var \TYPO3\CMS\Lang\LanguageService
+	 * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
 	 */
-	protected $languageService;
+	protected $runtimeCache = NULL;
 
 	/**
-	 * Constructor
+	 * @var DataHandler
 	 */
+	protected $tce;
+
 	public function __construct() {
-		$this->languageService = $GLOBALS['LANG'];
+		$this->runtimeCache = $this->getMemoryCache();
+		$this->tce = GeneralUtility::makeInstance(DataHandler::class);
 	}
 
 	/**
-	 * Transforms the rows for the deleted Records into the Array View necessary for ExtJS Ext.data.ArrayReader
+	 * Transforms the rows for the deleted records
 	 *
 	 * @param array $deletedRowsArray Array with table as key and array with all deleted rows
-	 * @param int $totalDeleted Number of deleted records in total, for PagingToolbar
+	 * @param int $totalDeleted Number of deleted records in total
 	 * @return string JSON array
 	 */
 	public function transform($deletedRowsArray, $totalDeleted) {
@@ -49,30 +55,93 @@ class DeletedRecordsController {
 		$jsonArray = array(
 			'rows' => array()
 		);
-		// iterate
-		if (is_array($deletedRowsArray) && count($deletedRowsArray) > 0) {
+
+		if (is_array($deletedRowsArray)) {
+			$lang = $this->getLanguageService();
+			$backendUser = $this->getBackendUser();
+
 			foreach ($deletedRowsArray as $table => $rows) {
 				$total += count($deletedRowsArray[$table]);
 				foreach ($rows as $row) {
+					$pageTitle = $this->getPageTitle((int)$row['pid']);
 					$backendUser = BackendUtility::getRecord('be_users', $row[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']], 'username', '', FALSE);
 					$jsonArray['rows'][] = array(
 						'uid' => $row['uid'],
 						'pid' => $row['pid'],
+						'icon' => IconUtility::getSpriteIconForRecord($table, $row),
+						'pageTitle' => RecyclerUtility::getUtf8String($pageTitle),
 						'table' => $table,
 						'crdate' => BackendUtility::datetime($row[$GLOBALS['TCA'][$table]['ctrl']['crdate']]),
 						'tstamp' => BackendUtility::datetime($row[$GLOBALS['TCA'][$table]['ctrl']['tstamp']]),
 						'owner' => htmlspecialchars($backendUser['username']),
 						'owner_uid' => $row[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']],
-						'tableTitle' => RecyclerUtility::getUtf8String($this->languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title'])),
-						'title' => htmlspecialchars(RecyclerUtility::getUtf8String(
-								BackendUtility::getRecordTitle($table, $row))),
+						'tableTitle' => RecyclerUtility::getUtf8String($lang->sL($GLOBALS['TCA'][$table]['ctrl']['title'])),
+						'title' => htmlspecialchars(RecyclerUtility::getUtf8String(BackendUtility::getRecordTitle($table, $row))),
 						'path' => RecyclerUtility::getRecordPath($row['pid'])
 					);
 				}
 			}
 		}
 		$jsonArray['total'] = $totalDeleted;
-		return json_encode($jsonArray);
+		return $jsonArray;
+	}
+
+	/**
+	 * Gets the page title of the given page id
+	 *
+	 * @param int $pageId
+	 * @return string
+	 */
+	protected function getPageTitle($pageId) {
+		$cacheId = 'recycler-pagetitle-' . $pageId;
+		if ($this->runtimeCache->has($cacheId)) {
+			$pageTitle = $this->runtimeCache->get($cacheId);
+		} else {
+			if ($pageId === 0) {
+				$pageTitle = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
+			} else {
+				$recordInfo = $this->tce->recordInfo('pages', $pageId, 'title');
+				$pageTitle = $recordInfo['title'];
+			}
+			$this->runtimeCache->set($cacheId, $pageTitle);
+		}
+		return $pageTitle;
+	}
+
+	/**
+	 * Returns an instance of LanguageService
+	 *
+	 * @return \TYPO3\CMS\Lang\LanguageService
+	 */
+	protected function getLanguageService() {
+		return $GLOBALS['LANG'];
+	}
+
+	/**
+	 * Returns the current BE user.
+	 *
+	 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+	 */
+	protected function getBackendUser() {
+		return $GLOBALS['BE_USER'];
+	}
+
+	/**
+	 * Create and returns an instance of the CacheManager
+	 *
+	 * @return \TYPO3\CMS\Core\Cache\CacheManager
+	 */
+	protected function getCacheManager() {
+		return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class);
+	}
+
+	/**
+	 * Gets an instance of the memory cache.
+	 *
+	 * @return \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
+	 */
+	protected function getMemoryCache() {
+		return $this->getCacheManager()->getCache('cache_runtime');
 	}
 
 }
\ No newline at end of file
diff --git a/typo3/sysext/recycler/Classes/Controller/RecyclerAjaxController.php b/typo3/sysext/recycler/Classes/Controller/RecyclerAjaxController.php
index 00277e53bf99a3919318fd6dd1c7f09c8a09f2fa..16e65cad431cf26a6b2aa8c76c154c1c495ea224 100644
--- a/typo3/sysext/recycler/Classes/Controller/RecyclerAjaxController.php
+++ b/typo3/sysext/recycler/Classes/Controller/RecyclerAjaxController.php
@@ -14,7 +14,14 @@ namespace TYPO3\CMS\Recycler\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Http\AjaxRequestHandler;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+use TYPO3\CMS\Recycler\Domain\Model\Tables;
+use TYPO3\CMS\Recycler\Domain\Model\DeletedRecords;
+use TYPO3\CMS\Recycler\Controller\DeletedRecordsController;
 
 /**
  * Controller class for the 'recycler' extension. Handles the AJAX Requests
@@ -25,133 +32,149 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class RecyclerAjaxController {
 
 	/**
-	 * Stores the content for the ajax output
+	 * The local configuration array
 	 *
-	 * @var string
+	 * @var array
 	 */
-	protected $content;
+	protected $conf = array();
 
 	/**
-	 * Command to be processed
-	 *
-	 * @var string
-	 */
-	protected $command;
-
-	/**
-	 * Stores relevant data from extJS
-	 * Example: Json format
-	 * [ ["pages",1],["pages",2],["tt_content",34] ]
-	 *
-	 * @var string
+	 * The constructor of this class
 	 */
-	protected $data;
-
-	/**
-	 * Initialize method
-	 *
-	 * @return void
-	 */
-	public function init() {
-		$this->mapCommand();
-		$this->getContent();
+	public function __construct() {
+		// Configuration, variable assignment
+		$this->conf['action'] = GeneralUtility::_GP('action');
+		$this->conf['table'] = GeneralUtility::_GP('table') ? GeneralUtility::_GP('table') : '';
+		$this->conf['limit'] = GeneralUtility::_GP('limit') ? (int)GeneralUtility::_GP('limit') : 25;
+		$this->conf['start'] = GeneralUtility::_GP('start') ? (int)GeneralUtility::_GP('limit') : 0;
+		$this->conf['filterTxt'] = GeneralUtility::_GP('filterTxt') ? GeneralUtility::_GP('filterTxt') : '';
+		$this->conf['startUid'] = GeneralUtility::_GP('startUid') ? (int)GeneralUtility::_GP('startUid') : 0;
+		$this->conf['depth'] = GeneralUtility::_GP('depth') ? (int)GeneralUtility::_GP('depth') : 0;
+		$this->conf['records'] = GeneralUtility::_GP('records') ? GeneralUtility::_GP('records') : NULL;
+		$this->conf['recursive'] = GeneralUtility::_GP('recursive') ? (bool)(int)GeneralUtility::_GP('recursive') : FALSE;
 	}
 
 	/**
-	 * Maps the command to the correct Model and View
+	 * The main dispatcher function. Collect data and prepare HTML output.
 	 *
+	 * @param array $params array of parameters from the AJAX interface, currently unused
+	 * @param AjaxRequestHandler $ajaxObj object of type AjaxRequestHandler
 	 * @return void
 	 */
-	public function mapCommand() {
-		$this->command = GeneralUtility::_GP('cmd');
-		$this->data = GeneralUtility::_GP('data');
-		// check params
-		if (!is_string($this->command)) {
-			// @TODO make devlog output
-			return;
-		}
-		// Create content
-		$this->createContent();
-	}
+	public function dispatch($params = array(), AjaxRequestHandler $ajaxObj = NULL) {
+		$extPath = ExtensionManagementUtility::extPath('recycler');
+		$view = GeneralUtility::makeInstance(StandaloneView::class);
+		$view->setPartialRootPaths(array('default' => $extPath . 'Resources/Private/Partials'));
 
-	/**
-	 * Creates the content
-	 *
-	 * @return void
-	 */
-	protected function createContent() {
-		switch ($this->command) {
+		$content = '';
+		// Determine the scripts to execute
+		switch ($this->conf['action']) {
+			case 'getTables':
+				$this->setDataInSession('depthSelection', $this->conf['depth']);
+
+				/* @var $model Tables */
+				$model = GeneralUtility::makeInstance(Tables::class);
+				$content = $model->getTables($this->conf['startUid'], $this->conf['depth']);
+				break;
 			case 'getDeletedRecords':
-				$table = GeneralUtility::_GP('table') ? GeneralUtility::_GP('table') : GeneralUtility::_GP('tableDefault');
-				$limit = GeneralUtility::_GP('limit') ? (int)GeneralUtility::_GP('limit') : (int)GeneralUtility::_GP('pagingSizeDefault');
-				$start = GeneralUtility::_GP('start') ? (int)GeneralUtility::_GP('start') : 0;
-				$filter = GeneralUtility::_GP('filterTxt') ? GeneralUtility::_GP('filterTxt') : '';
-				$startUid = GeneralUtility::_GP('startUid') ? GeneralUtility::_GP('startUid') : '';
-				$depth = GeneralUtility::_GP('depth') ? GeneralUtility::_GP('depth') : '';
-				$this->setDataInSession('tableSelection', $table);
-				/* @var $model \TYPO3\CMS\Recycler\Domain\Model\DeletedRecords */
-				$model = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\DeletedRecords::class);
-				$model->loadData($startUid, $table, $depth, $start . ',' . $limit, $filter);
+				$this->setDataInSession('tableSelection', $this->conf['table']);
+				$this->setDataInSession('depthSelection', $this->conf['depth']);
+				$this->setDataInSession('resultLimit', $this->conf['limit']);
+
+				/* @var $model DeletedRecords */
+				$model = GeneralUtility::makeInstance(DeletedRecords::class);
+				$model->loadData($this->conf['startUid'], $this->conf['table'], $this->conf['depth'], $this->conf['start'] . ',' . $this->conf['limit'], $this->conf['filterTxt']);
 				$deletedRowsArray = $model->getDeletedRows();
-				$model = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\DeletedRecords::class);
-				$totalDeleted = $model->getTotalCount($startUid, $table, $depth, $filter);
-				// load view
-				/* @var $view \TYPO3\CMS\Recycler\Controller\DeletedRecordsController */
-				$view = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Controller\DeletedRecordsController::class);
-				$str = $view->transform($deletedRowsArray, $totalDeleted);
+
+				/* @var $model DeletedRecords */
+				$model = GeneralUtility::makeInstance(DeletedRecords::class);
+				$totalDeleted = $model->getTotalCount($this->conf['startUid'], $this->conf['table'], $this->conf['depth'], $this->conf['filter']);
+
+				/* @var $view DeletedRecordsController */
+				$controller = GeneralUtility::makeInstance(DeletedRecordsController::class);
+				$recordsArray = $controller->transform($deletedRowsArray, $totalDeleted);
+
+				$modTS = $this->getBackendUser()->getTSConfig('mod.recycler');
+				$allowDelete = (bool)$this->getBackendUser()->user['admin'] ? TRUE : (bool)$modTS['properties']['allowDelete'];
+
+				$view->setTemplatePathAndFilename($extPath . 'Resources/Private/Templates/Ajax/RecordsTable.html');
+				$view->assign('records', $recordsArray['rows']);
+				$view->assign('allowDelete', $allowDelete);
+				$view->assign('total', $recordsArray['total']);
+				$content = json_encode(array(
+					'rows' => $view->render(),
+					'totalItems' => $recordsArray['total']
+				));
 				break;
-			case 'doDelete':
-				$str = FALSE;
-				/* @var $model \TYPO3\CMS\Recycler\Domain\Model\DeletedRecords */
-				$model = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\DeletedRecords::class);
-				if ($model->deleteData($this->data)) {
-					$str = TRUE;
+			case 'undoRecords':
+				if (empty($this->conf['records']) || !is_array($this->conf['records'])) {
+					$content = json_encode(array(
+						'success' => FALSE,
+						'message' => LocalizationUtility::translate('flashmessage.delete.norecordsselected', 'recycler')
+					));
+					break;
 				}
+
+				/* @var $model DeletedRecords */
+				$model = GeneralUtility::makeInstance(DeletedRecords::class);
+				$success = $model->undeleteData($this->conf['records'], $this->conf['recursive']);
+				$affectedRecords = count($this->conf['records']);
+				$messageKey = 'flashmessage.undo.' . ($success ? 'success' : 'failure') . '.' . ($affectedRecords === 1 ? 'singular' : 'plural');
+				$content = json_encode(array(
+					'success' => TRUE,
+					'message' => sprintf(LocalizationUtility::translate($messageKey, 'recycler'), $affectedRecords)
+				));
 				break;
-			case 'doUndelete':
-				$str = FALSE;
-				$recursive = GeneralUtility::_GP('recursive');
-				/* @var $model \TYPO3\CMS\Recycler\Domain\Model\DeletedRecords */
-				$model = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\DeletedRecords::class);
-				if ($model->undeleteData($this->data, $recursive)) {
-					$str = TRUE;
+			case 'deleteRecords':
+				if (empty($this->conf['records']) || !is_array($this->conf['records'])) {
+					$content = json_encode(array(
+						'success' => FALSE,
+						'message' => LocalizationUtility::translate('flashmessage.delete.norecordsselected', 'recycler')
+					));
+					break;
 				}
+
+				/* @var $model DeletedRecords */
+				$model = GeneralUtility::makeInstance(DeletedRecords::class);
+				$success = $model->deleteData($this->conf['records']);
+				$affectedRecords = count($this->conf['records']);
+				$messageKey = 'flashmessage.delete.' . ($success ? 'success' : 'failure') . '.' . ($affectedRecords === 1 ? 'singular' : 'plural');
+				$content = json_encode(array(
+					'success' => TRUE,
+					'message' => sprintf(LocalizationUtility::translate($messageKey, 'recycler'), $affectedRecords)
+				));
 				break;
-			case 'getTables':
-				$depth = GeneralUtility::_GP('depth') ? GeneralUtility::_GP('depth') : 0;
-				$startUid = GeneralUtility::_GP('startUid') ? GeneralUtility::_GP('startUid') : '';
-				$this->setDataInSession('depthSelection', $depth);
-				/* @var $model \TYPO3\CMS\Recycler\Domain\Model\Tables */
-				$model = GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\Tables::class);
-				$str = $model->getTables('json', TRUE, $startUid, $depth);
-				break;
-			default:
-				$str = 'No command was recognized.';
 		}
-		$this->content = $str;
-	}
-
-	/**
-	 * Returns the content that was created in the mapCommand method
-	 *
-	 * @return string
-	 */
-	public function getContent() {
-		echo $this->content;
+		$ajaxObj->addContent($this->conf['table'] . '_' . $this->conf['start'], $content);
 	}
 
 	/**
 	 * Sets data in the session of the current backend user.
 	 *
-	 * @param string $identifier: The identifier to be used to set the data
-	 * @param string $data: The data to be stored in the session
+	 * @param string $identifier The identifier to be used to set the data
+	 * @param string $data The data to be stored in the session
 	 * @return void
 	 */
 	protected function setDataInSession($identifier, $data) {
-		/* @var $beUser \TYPO3\CMS\Core\Authentication\BackendUserAuthentication */
-		$beUser = $GLOBALS['BE_USER'];
+		$beUser = $this->getBackendUser();
 		$beUser->uc['tx_recycler'][$identifier] = $data;
 		$beUser->writeUC();
 	}
 
+	/**
+	 * Returns the BackendUser
+	 *
+	 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+	 */
+	protected function getBackendUser() {
+		return $GLOBALS['BE_USER'];
+	}
+
+	/**
+	 * @return \TYPO3\CMS\Lang\LanguageService
+	 */
+	protected function getLanguageService() {
+		return $GLOBALS['LANG'];
+	}
+
 }
diff --git a/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php b/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php
index 6c2cf346fb0446b2fe131aad22251b2fe274a8f5..13bd3a5ae9941d3da7fd4f4347c7671556c1b64f 100644
--- a/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php
+++ b/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php
@@ -14,26 +14,22 @@ namespace TYPO3\CMS\Recycler\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Module 'Recycler' for the 'recycler' extension.
- *
- * @author Julian Kleinhans <typo3@kj187.de>
  */
-class RecyclerModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass {
+class RecyclerModuleController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
 
 	/**
-	 * @var \TYPO3\CMS\Backend\Template\DocumentTemplate
+	 * @var string
 	 */
-	public $doc;
+	protected $relativePath;
 
 	/**
 	 * @var string
 	 */
-	protected $relativePath;
+	public $perms_clause;
 
 	/**
 	 * @var array
@@ -55,70 +51,33 @@ class RecyclerModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass
 	 */
 	protected $recordsPageLimit = 50;
 
-	/**
-	 * @var \TYPO3\CMS\Core\Page\PageRenderer
-	 */
-	protected $pageRenderer;
-
-	/**
-	 * @var \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
-	 */
-	protected $backendUser;
-
-	/**
-	 * @var \TYPO3\CMS\Lang\LanguageService
-	 */
-	protected $languageService;
-
-	/**
-	 * The name of the module
-	 *
-	 * @var string
-	 */
-	protected $moduleName = 'web_txrecyclerM1';
-
-	/**
-	 * Constructor
-	 */
-	public function __construct() {
-		$this->languageService = $GLOBALS['LANG'];
-		$this->languageService->includeLLFile('EXT:recycler/mod1/locallang.xlf');
-
-		$this->backendUser = $GLOBALS['BE_USER'];
-
-		$this->MCONF = array(
-			'name' => $this->moduleName,
-		);
-	}
-
 	/**
 	 * Initializes the Module
 	 *
 	 * @return void
 	 */
-	public function initialize() {
-		parent::init();
-		$this->doc = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Template\DocumentTemplate::class);
-		$this->doc->setModuleTemplate(ExtensionManagementUtility::extPath('recycler') . 'mod1/mod_template.html');
-		$this->doc->backPath = $GLOBALS['BACK_PATH'];
-		$this->doc->setExtDirectStateProvider();
-		$this->pageRenderer = $this->doc->getPageRenderer();
-		$this->relativePath = ExtensionManagementUtility::extRelPath('recycler');
-		$this->pageRecord = BackendUtility::readPageAccess($this->id, $this->perms_clause);
+	public function initializeAction() {
+		$this->id = GeneralUtility::_GP('id');
+		$backendUser = $this->getBackendUser();
+		$this->perms_clause = $backendUser->getPagePermsClause(1);
+		$this->pageRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::readPageAccess($this->id, $this->perms_clause);
 		$this->isAccessibleForCurrentUser = $this->id && is_array($this->pageRecord) || !$this->id && $this->isCurrentUserAdmin();
-		//don't access in workspace
-		if ($this->backendUser->workspace !== 0) {
+
+		// don't access in workspace
+		if ($backendUser->workspace !== 0) {
 			$this->isAccessibleForCurrentUser = FALSE;
 		}
-		//read configuration
-		$modTS = $this->backendUser->getTSConfig('mod.recycler');
+
+		// read configuration
+		$modTS = $backendUser->getTSConfig('mod.recycler');
 		if ($this->isCurrentUserAdmin()) {
 			$this->allowDelete = TRUE;
 		} else {
-			$this->allowDelete = $modTS['properties']['allowDelete'] == '1';
+			$this->allowDelete = (bool)$modTS['properties']['allowDelete'];
 		}
-		if (isset($modTS['properties']['recordsPageLimit']) && (int)$modTS['properties']['recordsPageLimit'] > 0) {
-			$this->recordsPageLimit = (int)$modTS['properties']['recordsPageLimit'];
+
+		if (isset($modTS['properties']['recordsPageLimit']) && intval($modTS['properties']['recordsPageLimit']) > 0) {
+			$this->recordsPageLimit = intval($modTS['properties']['recordsPageLimit']);
 		}
 	}
 
@@ -127,31 +86,15 @@ class RecyclerModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass
 	 *
 	 * @return void
 	 */
-	public function render() {
-		$this->content .= $this->doc->header($this->languageService->getLL('title'));
-		$this->content .= '<p class="lead">' . $this->languageService->getLL('description') . '</p>';
-		if ($this->isAccessibleForCurrentUser) {
-			$this->loadHeaderData();
-			// div container for renderTo
-			$this->content .= '<div id="recyclerContent"></div>';
-		} else {
-			// If no access or if ID == zero
-			$this->content .= $this->doc->spacer(10);
-		}
-	}
+	public function indexAction() {
+		// Integrate dynamic JavaScript such as configuration or lables:
+		$jsConfiguration = $this->getJavaScriptConfiguration();
+        $this->getPageRenderer()->addInlineSettingArray('Recycler', $jsConfiguration);
+		$this->getPageRenderer()->addInlineLanguageLabelFile('EXT:recycler/Resources/Private/Language/locallang.xlf');
 
-	/**
-	 * Flushes the rendered content to browser.
-	 *
-	 * @return void
-	 */
-	public function flush() {
-		$content = $this->doc->moduleBody($this->pageRecord, $this->getDocHeaderButtons(), $this->getTemplateMarkers());
-		// Renders the module page
-		$content = $this->doc->render($this->languageService->getLL('title'), $content);
-		$this->content = NULL;
-		$this->doc = NULL;
-		echo $content;
+		$this->view->assign('title', $this->getLanguageService()->getLL('title'));
+		$this->view->assign('content', $this->content);
+		$this->view->assign('allowDelete', $this->allowDelete);
 	}
 
 	/**
@@ -160,31 +103,7 @@ class RecyclerModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass
 	 * @return bool Whether the current user is admin
 	 */
 	protected function isCurrentUserAdmin() {
-		return (bool)$this->backendUser->user['admin'];
-	}
-
-	/**
-	 * Loads data in the HTML head section (e.g. JavaScript or stylesheet information).
-	 *
-	 * @return void
-	 */
-	protected function loadHeaderData() {
-		// Load CSS Stylesheets:
-		$this->pageRenderer->addCssFile($this->relativePath . 'res/css/customExtJs.css');
-		// Load Ext JS:
-		$this->pageRenderer->loadExtJS();
-		$this->pageRenderer->enableExtJSQuickTips();
-		// Integrate dynamic JavaScript such as configuration or lables:
-		$this->pageRenderer->addInlineSettingArray('Recycler', $this->getJavaScriptConfiguration());
-		$this->pageRenderer->addInlineLanguageLabelArray($this->getJavaScriptLabels());
-		// Load Recycler JavaScript:
-		// Load Plugins
-		$uxPath = $this->doc->backPath . 'js/extjs/ux/';
-		$this->pageRenderer->addJsFile($uxPath . 'Ext.grid.RowExpander.js');
-		$this->pageRenderer->addJsFile($uxPath . 'Ext.app.SearchField.js');
-		$this->pageRenderer->addJsFile($uxPath . 'Ext.ux.FitToParent.js');
-		// Load main script
-		$this->pageRenderer->addJsFile($this->relativePath . 'res/js/t3_recycler.js');
+		return (bool)$this->getBackendUser()->user['admin'];
 	}
 
 	/**
@@ -196,108 +115,68 @@ class RecyclerModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass
 		$configuration = array(
 			'pagingSize' => $this->recordsPageLimit,
 			'showDepthMenu' => 1,
-			'startUid' => $this->id,
-			'tableDefault' => 'pages',
-			'renderTo' => 'recyclerContent',
+			'startUid' => (int)GeneralUtility::_GP('id'),
 			'isSSL' => GeneralUtility::getIndpEnv('TYPO3_SSL'),
-			'deleteDisable' => $this->allowDelete ? 0 : 1,
+			'deleteDisable' => !$this->allowDelete,
 			'depthSelection' => $this->getDataFromSession('depthSelection', 0),
-			'tableSelection' => $this->getDataFromSession('tableSelection', 'pages'),
-			'States' => $this->backendUser->uc['moduleData']['web_recycler']['States']
+			'tableSelection' => $this->getDataFromSession('tableSelection', ''),
+			'States' => $this->getBackendUser()->uc['moduleData']['web_recycler']['States']
 		);
 		return $configuration;
 	}
 
 	/**
-	 * Gets the labels to be used in JavaScript in the Ext JS interface.
-	 *
-	 * @return array The labels to be used in JavaScript
-	 */
-	protected function getJavaScriptLabels() {
-		$coreLabels = array(
-			'title' => $this->languageService->getLL('title'),
-			'path' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.path'),
-			'table' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.table'),
-			'depth' => $this->languageService->sL('LLL:EXT:lang/locallang_mod_web_perm.xlf:Depth'),
-			'depth_0' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_0'),
-			'depth_1' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_1'),
-			'depth_2' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_2'),
-			'depth_3' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_3'),
-			'depth_4' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_4'),
-			'depth_infi' => $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_infi')
-		);
-		$extensionLabels = $this->languageService->getLabelsWithPrefix('js.', 'label_');
-		$javaScriptLabels = array_merge($coreLabels, $extensionLabels);
-		return $javaScriptLabels;
-	}
-
-	/**
-	 * Gets the buttons that shall be rendered in the docHeader.
+	 * Gets data from the session of the current backend user.
 	 *
-	 * @return array Available buttons for the docHeader
+	 * @param string $identifier The identifier to be used to get the data
+	 * @param string $default The default date to be used if nothing was found in the session
+	 * @return string The accordant data in the session of the current backend user
 	 */
-	protected function getDocHeaderButtons() {
-		$buttons = array(
-			'csh' => BackendUtility::cshItem('_MOD_web_func', ''),
-			'shortcut' => $this->getShortcutButton(),
-			'save' => ''
-		);
-		// SAVE button
-		$buttons['save'] = '';
-		return $buttons;
+	protected function getDataFromSession($identifier, $default = NULL) {
+		$sessionData = &$this->getBackendUser()->uc['tx_recycler'];
+		if (isset($sessionData[$identifier]) && $sessionData[$identifier]) {
+			$data = $sessionData[$identifier];
+		} else {
+			$data = $default;
+		}
+		return $data;
 	}
 
 	/**
-	 * Gets the button to set a new shortcut in the backend (if current user is allowed to).
+	 * Returns the current BE user.
 	 *
-	 * @return string HTML representation of the shortcut button
+	 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
 	 */
-	protected function getShortcutButton() {
-		$result = '';
-		if ($this->backendUser->mayMakeShortcut()) {
-			$result = $this->doc->makeShortcutIcon('', 'function', $this->moduleName);
-		}
-		return $result;
+	protected function getBackendUser() {
+		return $GLOBALS['BE_USER'];
 	}
 
 	/**
-	 * Gets the filled markers that are used in the HTML template.
+	 * Returns an instance of DocumentTemplate
 	 *
-	 * @return array The filled marker array
+	 * @return TYPO3\CMS\Backend\Template\DocumentTemplate
 	 */
-	protected function getTemplateMarkers() {
-		$markers = array(
-			'FUNC_MENU' => $this->getFunctionMenu(),
-			'CONTENT' => $this->content,
-			'TITLE' => $this->languageService->getLL('title')
-		);
-		return $markers;
+	protected function getDocumentTemplate() {
+		return $GLOBALS['TBE_TEMPLATE'];
 	}
 
 	/**
-	 * Gets the function menu selector for this backend module.
+	 * Returns an instance of LanguageService
 	 *
-	 * @return string The HTML representation of the function menu selector
+	 * @return \TYPO3\CMS\Lang\LanguageService
 	 */
-	protected function getFunctionMenu() {
-		return BackendUtility::getFuncMenu(0, 'SET[function]', $this->MOD_SETTINGS['function'], $this->MOD_MENU['function']);
+	protected function getLanguageService() {
+		return $GLOBALS['LANG'];
 	}
 
 	/**
-	 * Gets data from the session of the current backend user.
+	 * Returns current PageRenderer
 	 *
-	 * @param string $identifier The identifier to be used to get the data
-	 * @param string $default The default date to be used if nothing was found in the session
-	 * @return string The accordant data in the session of the current backend user
+	 * @return \TYPO3\CMS\Core\Page\PageRenderer
 	 */
-	protected function getDataFromSession($identifier, $default = NULL) {
-		$sessionData = &$this->backendUser->uc['tx_recycler'];
-		if (isset($sessionData[$identifier]) && $sessionData[$identifier]) {
-			$data = $sessionData[$identifier];
-		} else {
-			$data = $default;
-		}
-		return $data;
+	protected function getPageRenderer() {
+		/** @var  \TYPO3\CMS\Backend\Template\DocumentTemplate $documentTemplate */
+		$documentTemplate = $GLOBALS['TBE_TEMPLATE'];
+		return $documentTemplate->getPageRenderer();
 	}
-
 }
diff --git a/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php b/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php
index 2820d08df885f2cf8f8d5189588118907135ea04..3cc62b28edccfa72e8e0f21e84b5b4d83950c2ad 100644
--- a/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php
+++ b/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Recycler\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Recycler\Utility\RecyclerUtility;
 
@@ -66,20 +67,6 @@ class DeletedRecords {
 	 */
 	public $title;
 
-	/**
-	 * Database Connection
-	 *
-	 * @var \TYPO3\CMS\Core\Database\DatabaseConnection
-	 */
-	protected $databaseConnection;
-
-	/**
-	 * Constructor
-	 */
-	public function __construct() {
-		$this->databaseConnection = $GLOBALS['TYPO3_DB'];
-	}
-
 	/************************************************************
 	 * GET DATA FUNCTIONS
 	 *
@@ -100,17 +87,17 @@ class DeletedRecords {
 		// set the limit
 		$this->limit = trim($limit);
 		if ($table) {
-			if (array_key_exists($table, $GLOBALS['TCA'])) {
+			if (in_array($table, RecyclerUtility::getModifyableTables())) {
 				$this->table[] = $table;
 				$this->setData($id, $table, $depth, $GLOBALS['TCA'][$table]['ctrl'], $filter);
 			}
 		} else {
 			foreach ($GLOBALS['TCA'] as $tableKey => $tableValue) {
 				// only go into this table if the limit allows it
-				if ($this->limit != '') {
+				if ($this->limit !== '') {
 					$parts = GeneralUtility::trimExplode(',', $this->limit);
 					// abort loop if LIMIT 0,0
-					if ($parts[0] == 0 && $parts[1] == 0) {
+					if ((int)$parts[0] === 0 && (int)$parts[1] === 0) {
 						break;
 					}
 				}
@@ -149,111 +136,113 @@ class DeletedRecords {
 	 * @param string $filter Filter text
 	 * @return void
 	 */
-	protected function setData($id = 0, $table, $depth, $tcaCtrl, $filter) {
+	protected function setData($id, $table, $depth, $tcaCtrl, $filter) {
 		$id = (int)$id;
-		if (array_key_exists('delete', $tcaCtrl)) {
-			// find the 'deleted' field for this table
-			$deletedField = RecyclerUtility::getDeletedField($table);
-			// create the filter WHERE-clause
-			$filterWhere = '';
-			if (trim($filter) != '') {
-				$filterWhere = ' AND (' . (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($filter) ? 'uid = ' . $filter . ' OR pid = ' . $filter . ' OR ' : '') . $tcaCtrl['label'] . ' LIKE "%' . $this->escapeValueForLike($filter, $table) . '%"' . ')';
-			}
+		if (!array_key_exists('delete', $tcaCtrl)) {
+			return;
+		}
+		$db = $this->getDatabaseConnection();
+		// find the 'deleted' field for this table
+		$deletedField = RecyclerUtility::getDeletedField($table);
+		// create the filter WHERE-clause
+		$filterWhere = '';
+		if (trim($filter) != '') {
+			$filterWhere = ' AND (' . (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($filter) ? 'uid = ' . $filter . ' OR pid = ' . $filter . ' OR ' : '') . $tcaCtrl['label'] . ' LIKE "%' . $this->escapeValueForLike($filter, $table) . '%"' . ')';
+		}
 
-			// get the limit
-			if ($this->limit != '') {
-				// count the number of deleted records for this pid
-				$deletedCount = $this->databaseConnection->exec_SELECTcountRows('uid', $table, $deletedField . '<>0 AND pid = ' . $id . $filterWhere);
-				// split the limit
-				$parts = GeneralUtility::trimExplode(',', $this->limit);
-				$offset = $parts[0];
-				$rowCount = $parts[1];
-				// subtract the number of deleted records from the limit's offset
-				$result = $offset - $deletedCount;
-				// if the result is >= 0
-				if ($result >= 0) {
-					// store the new offset in the limit and go into the next depth
-					$offset = $result;
-					$this->limit = implode(',', array($offset, $rowCount));
-					// do NOT query this depth; limit also does not need to be set, we set it anyways
-					$allowQuery = FALSE;
-					$allowDepth = TRUE;
-					$limit = '';
+		// get the limit
+		if (!empty($this->limit)) {
+			// count the number of deleted records for this pid
+			$deletedCount = $db->exec_SELECTcountRows('uid', $table, $deletedField . '<>0 AND pid = ' . $id . $filterWhere);
+			// split the limit
+			$parts = GeneralUtility::trimExplode(',', $this->limit);
+			$offset = $parts[0];
+			$rowCount = $parts[1];
+			// subtract the number of deleted records from the limit's offset
+			$result = $offset - $deletedCount;
+			// if the result is >= 0
+			if ($result >= 0) {
+				// store the new offset in the limit and go into the next depth
+				$offset = $result;
+				$this->limit = implode(',', array($offset, $rowCount));
+				// do NOT query this depth; limit also does not need to be set, we set it anyways
+				$allowQuery = FALSE;
+				$allowDepth = TRUE;
+				$limit = '';
+			} else {
+				// the offset for the temporary limit has to remain like the original offset
+				// in case the original offset was just crossed by the amount of deleted records
+				if ($offset !== 0) {
+					$tempOffset = $offset;
+				} else {
+					$tempOffset = 0;
+				}
+				// set the offset in the limit to 0
+				$newOffset = 0;
+				// convert to negative result to the positive equivalent
+				$absResult = abs($result);
+				// if the result now is > limit's row count
+				if ($absResult > $rowCount) {
+					// use the limit's row count as the temporary limit
+					$limit = implode(',', array($tempOffset, $rowCount));
+					// set the limit's row count to 0
+					$this->limit = implode(',', array($newOffset, 0));
+					// do not go into new depth
+					$allowDepth = FALSE;
 				} else {
-					// the offset for the temporary limit has to remain like the original offset
-					// in case the original offset was just crossed by the amount of deleted records
-					if ($offset != 0) {
-						$tempOffset = $offset;
+					// if the result now is <= limit's row count
+					// use the result as the temporary limit
+					$limit = implode(',', array($tempOffset, $absResult));
+					// subtract the result from the row count
+					$newCount = $rowCount - $absResult;
+					// store the new result in the limit's row count
+					$this->limit = implode(',', array($newOffset, $newCount));
+					// if the new row count is > 0
+					if ($newCount > 0) {
+						// go into new depth
+						$allowDepth = TRUE;
 					} else {
-						$tempOffset = 0;
-					}
-					// set the offset in the limit to 0
-					$newOffset = 0;
-					// convert to negative result to the positive equivalent
-					$absResult = abs($result);
-					// if the result now is > limit's row count
-					if ($absResult > $rowCount) {
-						// use the limit's row count as the temporary limit
-						$limit = implode(',', array($tempOffset, $rowCount));
-						// set the limit's row count to 0
-						$this->limit = implode(',', array($newOffset, 0));
+						// if the new row count is <= 0 (only =0 makes sense though)
 						// do not go into new depth
 						$allowDepth = FALSE;
-					} else {
-						// if the result now is <= limit's row count
-						// use the result as the temporary limit
-						$limit = implode(',', array($tempOffset, $absResult));
-						// subtract the result from the row count
-						$newCount = $rowCount - $absResult;
-						// store the new result in the limit's row count
-						$this->limit = implode(',', array($newOffset, $newCount));
-						// if the new row count is > 0
-						if ($newCount > 0) {
-							// go into new depth
-							$allowDepth = TRUE;
-						} else {
-							// if the new row count is <= 0 (only =0 makes sense though)
-							// do not go into new depth
-							$allowDepth = FALSE;
-						}
 					}
-					// allow query for this depth
-					$allowQuery = TRUE;
 				}
-			} else {
-				$limit = '';
-				$allowDepth = TRUE;
+				// allow query for this depth
 				$allowQuery = TRUE;
 			}
-			// query for actual deleted records
-			if ($allowQuery) {
-				$recordsToCheck = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordsByField($table, $deletedField, '1', ' AND pid = ' . $id . $filterWhere, '', '', $limit, FALSE);
-				if ($recordsToCheck) {
-					$this->checkRecordAccess($table, $recordsToCheck);
-				}
+		} else {
+			$limit = '';
+			$allowDepth = TRUE;
+			$allowQuery = TRUE;
+		}
+		// query for actual deleted records
+		if ($allowQuery) {
+			$recordsToCheck = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordsByField($table, $deletedField, '1', ' AND pid = ' . $id . $filterWhere, '', '', $limit, FALSE);
+			if ($recordsToCheck) {
+				$this->checkRecordAccess($table, $recordsToCheck);
 			}
-			// go into depth
-			if ($allowDepth && $depth >= 1) {
-				// check recursively for elements beneath this page
-				$resPages = $this->databaseConnection->exec_SELECTquery('uid', 'pages', 'pid=' . $id, '', 'sorting');
-				if ($resPages) {
-					while ($rowPages = $this->databaseConnection->sql_fetch_assoc($resPages)) {
-						$this->setData($rowPages['uid'], $table, $depth - 1, $tcaCtrl, $filter);
-						// some records might have been added, check if we still have the limit for further queries
-						if ('' != $this->limit) {
-							$parts = GeneralUtility::trimExplode(',', $this->limit);
-							// abort loop if LIMIT 0,0
-							if ($parts[0] == 0 && $parts[1] == 0) {
-								break;
-							}
+		}
+		// go into depth
+		if ($allowDepth && $depth >= 1) {
+			// check recursively for elements beneath this page
+			$resPages = $db->exec_SELECTquery('uid', 'pages', 'pid=' . $id, '', 'sorting');
+			if ($resPages) {
+				while ($rowPages = $db->sql_fetch_assoc($resPages)) {
+					$this->setData($rowPages['uid'], $table, $depth - 1, $tcaCtrl, $filter);
+					// some records might have been added, check if we still have the limit for further queries
+					if (!empty($this->limit)) {
+						$parts = GeneralUtility::trimExplode(',', $this->limit);
+						// abort loop if LIMIT 0,0
+						if ((int)$parts[0] === 0 && (int)$parts[1] === 0) {
+							break;
 						}
 					}
-					$this->databaseConnection->sql_free_result($resPages);
 				}
+				$db->sql_free_result($resPages);
 			}
-			$this->label[$table] = $tcaCtrl['label'];
-			$this->title[$table] = $tcaCtrl['title'];
 		}
+		$this->label[$table] = $tcaCtrl['label'];
+		$this->title[$table] = $tcaCtrl['title'];
 	}
 
 	/**
@@ -264,7 +253,7 @@ class DeletedRecords {
 	 * @return void
 	 */
 	protected function checkRecordAccess($table, array $rows) {
-		foreach ($rows as $key => $row) {
+		foreach ($rows as $row) {
 			if (RecyclerUtility::checkAccess($table, $row)) {
 				$this->setDeletedRows($table, $row);
 			}
@@ -280,7 +269,8 @@ class DeletedRecords {
 	 * @return string The escaped value to be used for like conditions
 	 */
 	protected function escapeValueForLike($value, $tableName) {
-		return $this->databaseConnection->escapeStrForLike($this->databaseConnection->quoteStr($value, $tableName), $tableName);
+		$db = $this->getDatabaseConnection();
+		return $db->escapeStrForLike($db->quoteStr($value, $tableName), $tableName);
 	}
 
 	/************************************************************
@@ -293,13 +283,14 @@ class DeletedRecords {
 	 * @return bool
 	 */
 	public function deleteData($recordsArray) {
-		$recordsArray = json_decode($recordsArray);
 		if (is_array($recordsArray)) {
-			$tce = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
+			/** @var $tce DataHandler **/
+			$tce = GeneralUtility::makeInstance(DataHandler::class);
 			$tce->start('', '');
 			$tce->disableDeleteClause();
-			foreach ($recordsArray as $key => $record) {
-				$tce->deleteEl($record[0], $record[1], TRUE, TRUE);
+			foreach ($recordsArray as $record) {
+				list($table, $uid) = explode(':', $record);
+				$tce->deleteEl($table, (int)$uid, TRUE, TRUE);
 			}
 			return TRUE;
 		}
@@ -320,26 +311,26 @@ class DeletedRecords {
 	public function undeleteData($recordsArray, $recursive = FALSE) {
 		$result = FALSE;
 		$depth = 999;
-		$recordsArray = json_decode($recordsArray);
 		if (is_array($recordsArray)) {
 			$this->deletedRows = array();
 			$cmd = array();
-			foreach ($recordsArray as $key => $row) {
-				$cmd[$row[0]][$row[1]]['undelete'] = 1;
-				if ($row[0] == 'pages' && $recursive == TRUE) {
-					$this->loadData($row[1], '', $depth, '');
+			foreach ($recordsArray as $record) {
+				list($table, $uid) = explode(':', $record);
+				$cmd[$table][$uid]['undelete'] = 1;
+				if ($table === 'pages' && $recursive) {
+					$this->loadData($uid, '', $depth, '');
 					$childRecords = $this->getDeletedRows();
 					if (count($childRecords) > 0) {
-						foreach ($childRecords as $table => $childRows) {
-							foreach ($childRows as $childKey => $childRow) {
-								$cmd[$table][$childRow['uid']]['undelete'] = 1;
+						foreach ($childRecords as $childTable => $childRows) {
+							foreach ($childRows as $childRow) {
+								$cmd[$childTable][$childRow['uid']]['undelete'] = 1;
 							}
 						}
 					}
 				}
 			}
 			if ($cmd) {
-				$tce = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class);
+				$tce = GeneralUtility::makeInstance(DataHandler::class);
 				$tce->start(array(), $cmd);
 				$tce->process_cmdmap();
 				$result = TRUE;
@@ -383,4 +374,12 @@ class DeletedRecords {
 		return $this->table;
 	}
 
+	/**
+	 * Returns an instance of DatabaseConnection
+	 *
+	 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+	 */
+	protected function getDatabaseConnection() {
+		return $GLOBALS['TYPO3_DB'];
+	}
 }
diff --git a/typo3/sysext/recycler/Classes/Domain/Model/Tables.php b/typo3/sysext/recycler/Classes/Domain/Model/Tables.php
index 59429a5c57ab76df4c0597571f421545534f9c4e..827d9a17c0bc86ef8af6e833a884d52cc4a9b76f 100644
--- a/typo3/sysext/recycler/Classes/Domain/Model/Tables.php
+++ b/typo3/sysext/recycler/Classes/Domain/Model/Tables.php
@@ -23,43 +23,22 @@ use TYPO3\CMS\Recycler\Utility\RecyclerUtility;
  */
 class Tables {
 
-	/**
-	 * @var \TYPO3\CMS\Lang\LanguageService
-	 */
-	protected $languageService;
-
-	/**
-	 * Database Connection
-	 *
-	 * @var \TYPO3\CMS\Core\Database\DatabaseConnection
-	 */
-	protected $databaseConnection;
-
-	/**
-	 * Constructor
-	 */
-	public function __construct() {
-		$this->languageService = $GLOBALS['LANG'];
-		$this->databaseConnection = $GLOBALS['TYPO3_DB'];
-	}
-
 	/**
 	 * Get tables for menu example
 	 *
-	 * @param string $format Return format (example: json) - currently unused
-	 * @param bool $withAllOption FALSE: no, TRUE: return tables with a "all" option
 	 * @param int $startUid UID from selected page
 	 * @param int $depth How many levels recursive
 	 * @return string The tables to be displayed
 	 */
-	public function getTables($format, $withAllOption = TRUE, $startUid, $depth = 0) {
+	public function getTables($startUid, $depth = 0) {
 		$deletedRecordsTotal = 0;
+		$lang = $this->getLanguageService();
 		$tables = array();
-		foreach ($GLOBALS['TCA'] as $tableName => $_) {
+		foreach (RecyclerUtility::getModifyableTables() as $tableName) {
 			$deletedField = RecyclerUtility::getDeletedField($tableName);
 			if ($deletedField) {
 				// Determine whether the table has deleted records:
-				$deletedCount = $this->databaseConnection->exec_SELECTcountRows('uid', $tableName, $deletedField . '<>0');
+				$deletedCount = $this->getDatabaseConnection()->exec_SELECTcountRows('uid', $tableName, $deletedField . '<>0');
 				if ($deletedCount) {
 					/* @var $deletedDataObject \TYPO3\CMS\Recycler\Domain\Model\DeletedRecords */
 					$deletedDataObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Domain\Model\DeletedRecords::class);
@@ -70,8 +49,7 @@ class Tables {
 							$tables[] = array(
 								$tableName,
 								$deletedRecordsInTable,
-								$tableName,
-								RecyclerUtility::getUtf8String($this->languageService->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']))
+								RecyclerUtility::getUtf8String($lang->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']))
 							);
 						}
 					}
@@ -79,16 +57,31 @@ class Tables {
 			}
 		}
 		$jsonArray = $tables;
-		if ($withAllOption) {
-			array_unshift($jsonArray, array(
-				'',
-				$deletedRecordsTotal,
-				'',
-				$this->languageService->sL('LLL:EXT:recycler/mod1/locallang.xlf:label_alltables')
-			));
-		}
+		array_unshift($jsonArray, array(
+			'',
+			$deletedRecordsTotal,
+			$lang->sL('LLL:EXT:recycler/mod1/locallang.xlf:label_alltables')
+		));
 		$output = json_encode($jsonArray);
 		return $output;
 	}
 
+	/**
+	 * Returns an instance of DatabaseConnection
+	 *
+	 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+	 */
+	protected function getDatabaseConnection() {
+		return $GLOBALS['TYPO3_DB'];
+	}
+
+	/**
+	 * Returns an instance of LanguageService
+	 *
+	 * @return \TYPO3\CMS\Lang\LanguageService
+	 */
+	protected function getLanguageService() {
+		return $GLOBALS['LANG'];
+	}
+
 }
diff --git a/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php b/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php
index 8848c8c076c86e1a5f788cabbf9a4818a3133e31..49b1462982468680301209b4415bf21e72caf56c 100644
--- a/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php
+++ b/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php
@@ -38,8 +38,7 @@ class RecyclerUtility {
 	 * @return bool Returns TRUE is the user has access, or FALSE if not
 	 */
 	static public function checkAccess($table, $row) {
-		/* @var $backendUser \TYPO3\CMS\Core\Authentication\BackendUserAuthentication */
-		$backendUser = $GLOBALS['BE_USER'];
+		$backendUser = static::getBackendUser();
 
 		// Checking if the user has permissions? (Only working as a precaution, because the final permission check is always down in TCE. But it's good to notify the user on beforehand...)
 		// First, resetting flags.
@@ -47,7 +46,7 @@ class RecyclerUtility {
 		$calcPRec = $row;
 		BackendUtility::fixVersioningPid($table, $calcPRec);
 		if (is_array($calcPRec)) {
-			if ($table == 'pages') {
+			if ($table === 'pages') {
 				// If pages:
 				$calculatedPermissions = $backendUser->calcPerms($calcPRec);
 				$hasAccess = $calculatedPermissions & 2 ? TRUE : FALSE;
@@ -79,24 +78,27 @@ class RecyclerUtility {
 	 * @return mixed Path of record (string) OR array with short/long title if $fullTitleLimit is set.
 	 */
 	static public function getRecordPath($uid, $clause = '', $titleLimit = 1000, $fullTitleLimit = 0) {
-		/* @var $databaseConnection \TYPO3\CMS\Core\Database\DatabaseConnection */
-		$databaseConnection = $GLOBALS['TYPO3_DB'];
-
-		$loopCheck = 100;
+		$uid = (int)$uid;
 		$output = ($fullOutput = '/');
-		while ($uid != 0 && $loopCheck > 0) {
+		if ($uid === 0) {
+			return $output;
+		}
+		$databaseConnection = static::getDatabaseConnection();
+		$clause = trim($clause) !== '' ? ' AND ' . $clause : '';
+		$loopCheck = 100;
+		while ($loopCheck > 0) {
 			$loopCheck--;
-			$res = $databaseConnection->exec_SELECTquery('uid,pid,title,deleted,t3ver_oid,t3ver_wsid', 'pages', 'uid=' . (int)$uid . (trim($clause) !== '' ? ' AND ' . $clause : ''));
-			if (is_resource($res)) {
+			$res = $databaseConnection->exec_SELECTquery('uid,pid,title,deleted,t3ver_oid,t3ver_wsid', 'pages', 'uid=' . $uid . $clause);
+			if ($res !== FALSE) {
 				$row = $databaseConnection->sql_fetch_assoc($res);
 				$databaseConnection->sql_free_result($res);
 				BackendUtility::workspaceOL('pages', $row);
 				if (is_array($row)) {
 					BackendUtility::fixVersioningPid('pages', $row);
-					$uid = $row['pid'];
+					$uid = (int)$row['pid'];
 					$output = '/' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $titleLimit)) . $output;
 					if ($row['deleted']) {
-						$output = '<span class="deletedPath">' . $output . '</span>';
+						$output = '<span class="text-danger">' . $output . '</span>';
 					}
 					if ($fullTitleLimit) {
 						$fullOutput = '/' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $fullTitleLimit)) . $fullOutput;
@@ -149,7 +151,8 @@ class RecyclerUtility {
 	 * @return string The current backend charset
 	 */
 	static public function getCurrentCharset() {
-		return $GLOBALS['LANG']->csConvObj->parse_charset($GLOBALS['LANG']->charSet);
+		$lang = static::getLanguageService();
+		return $lang->csConvObj->parse_charset($lang->charSet);
 	}
 
 	/**
@@ -169,9 +172,48 @@ class RecyclerUtility {
 	 */
 	static public function getUtf8String($string) {
 		if (self::isNotUtf8Charset()) {
-			$string = $GLOBALS['LANG']->csConvObj->utf8_encode($string, self::getCurrentCharset());
+			$string = static::getLanguageService()->csConvObj->utf8_encode($string, self::getCurrentCharset());
 		}
 		return $string;
 	}
 
+	/**
+	 * Returns an instance of DatabaseConnection
+	 *
+	 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+	 */
+	static protected function getDatabaseConnection() {
+		return $GLOBALS['TYPO3_DB'];
+	}
+
+	/**
+	 * Returns the BackendUser
+	 *
+	 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+	 */
+	static protected function getBackendUser() {
+		return $GLOBALS['BE_USER'];
+	}
+
+	/**
+	 * Returns an instance of LanguageService
+	 *
+	 * @return \TYPO3\CMS\Lang\LanguageService
+	 */
+	static protected function getLanguageService() {
+		return $GLOBALS['LANG'];
+	}
+
+	/**
+	 * Returns the modifyable tables of the current user
+	 */
+	static public function getModifyableTables() {
+		if ((bool)$GLOBALS['BE_USER']->user['admin']) {
+			$tables = array_keys($GLOBALS['TCA']);
+		} else {
+			$tables = explode(',', $GLOBALS['BE_USER']->groupData['tables_modify']);
+		}
+		return $tables;
+	}
+
 }
diff --git a/typo3/sysext/recycler/Resources/Private/Language/locallang.xlf b/typo3/sysext/recycler/Resources/Private/Language/locallang.xlf
new file mode 100644
index 0000000000000000000000000000000000000000..9c786c3bd07357cc02ede357777e98cf1cb54e01
--- /dev/null
+++ b/typo3/sysext/recycler/Resources/Private/Language/locallang.xlf
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
+	<file t3:id="1415814907" source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:35Z" product-name="recycler">
+		<header/>
+		<body>
+			<trans-unit id="title" xml:space="preserve">
+				<source>Recycler</source>
+			</trans-unit>
+			<trans-unit id="description" xml:space="preserve">
+				<source>The recycler allows you to select any deleted data and undelete it. You can undelete recursive if the parent of the element is deleted too.</source>
+			</trans-unit>
+			<trans-unit id="label_alltables" xml:space="preserve">
+				<source>All tables</source>
+			</trans-unit>
+			<trans-unit id="button.cancel" xml:space="preserve">
+				<source>Cancel</source>
+			</trans-unit>
+			<trans-unit id="button.delete" xml:space="preserve">
+				<source>Delete</source>
+			</trans-unit>
+			<trans-unit id="button.deleteselected" xml:space="preserve">
+				<source>Delete {0} records</source>
+			</trans-unit>
+			<trans-unit id="button.undo" xml:space="preserve">
+				<source>Recover</source>
+			</trans-unit>
+			<trans-unit id="button.expand" xml:space="preserve">
+				<source>Expand record</source>
+			</trans-unit>
+			<trans-unit id="button.undoselected" xml:space="preserve">
+				<source>Recover {0} records</source>
+			</trans-unit>
+			<trans-unit id="button.reload" xml:space="preserve">
+				<source>Reload</source>
+			</trans-unit>
+			<trans-unit id="modal.delete.header" xml:space="preserve">
+				<source>Confirm deletion</source>
+			</trans-unit>
+			<trans-unit id="modal.undo.header" xml:space="preserve">
+				<source>Recover records</source>
+			</trans-unit>
+			<trans-unit id="modal.undo.recursive" xml:space="preserve">
+				<source>Recover recursively</source>
+			</trans-unit>
+			<trans-unit id="modal.deletecontent.text" xml:space="preserve">
+				<source>Do you really want to delete the record "{0}" {1} permanently? You cannot revert this action!</source>
+			</trans-unit>
+			<trans-unit id="modal.massdelete.text" xml:space="preserve">
+				<source>Do you really want to delete all selected records and potential subpages? You cannot revert this action!</source>
+			</trans-unit>
+			<trans-unit id="modal.deletepage.text" xml:space="preserve">
+				<source>Do you really want to delete the page "{0}" {1} and all of its contents (records and pages) permanently? You cannot revert this action!</source>
+			</trans-unit>
+			<trans-unit id="modal.undocontent.text" xml:space="preserve">
+				<source>Do you really want to recover the record "{0}" {1}?</source>
+			</trans-unit>
+			<trans-unit id="modal.undopage.text" xml:space="preserve">
+				<source>Do you really want to recover the page "{0}" {1}, its contents and optionally all of its subpages?</source>
+			</trans-unit>
+			<trans-unit id="modal.massundo.text" xml:space="preserve">
+				<source>Do you really want to recover all selected records and optionally potential subpages?</source>
+			</trans-unit>
+			<trans-unit id="flashmessage.delete.norecordsselected" xml:space="preserve">
+				<source>No records set to delete.</source>
+			</trans-unit>
+			<trans-unit id="flashmessage.delete.success.singular" xml:space="preserve">
+				<source>One record was deleted.</source>
+			</trans-unit>
+			<trans-unit id="flashmessage.delete.success.plural" xml:space="preserve">
+				<source>%d records were deleted.</source>
+			</trans-unit>
+			<trans-unit id="flashmessage.delete.failure.singular" xml:space="preserve">
+				<source>Could not delete one record.</source>
+			</trans-unit>
+			<trans-unit id="flashmessage.delete.failure.plural" xml:space="preserve">
+				<source>Could not delete %d records.</source>
+			</trans-unit>
+			<trans-unit id="flashmessage.undo.norecordsselected" xml:space="preserve">
+				<source>No records set to recover.</source>
+			</trans-unit>
+			<trans-unit id="flashmessage.undo.success.singular" xml:space="preserve">
+				<source>One record was recovered.</source>
+			</trans-unit>
+			<trans-unit id="flashmessage.undo.success.plural" xml:space="preserve">
+				<source>%d records were recovered.</source>
+			</trans-unit>
+			<trans-unit id="flashmessage.undo.failure.singular" xml:space="preserve">
+				<source>Could not recover one record.</source>
+			</trans-unit>
+			<trans-unit id="flashmessage.undo.failure.plural" xml:space="preserve">
+				<source>Could not recover %d records.</source>
+			</trans-unit>
+			<trans-unit id="table.header.uid" xml:space="preserve">
+				<source>UID</source>
+			</trans-unit>
+			<trans-unit id="table.header.pid" xml:space="preserve">
+				<source>PID</source>
+			</trans-unit>
+			<trans-unit id="table.header.record" xml:space="preserve">
+				<source>Record</source>
+			</trans-unit>
+			<trans-unit id="table.header.actions" xml:space="preserve">
+				<source>Actions</source>
+			</trans-unit>
+			<trans-unit id="table.header.table" xml:space="preserve">
+				<source>Table</source>
+			</trans-unit>
+			<trans-unit id="table.header.tstamp" xml:space="preserve">
+				<source>Last edit</source>
+			</trans-unit>
+			<trans-unit id="table.header.actions" xml:space="preserve">
+				<source>Actions</source>
+			</trans-unit>
+			<trans-unit id="table.header.crdate" xml:space="preserve">
+				<source>Created on</source>
+			</trans-unit>
+			<trans-unit id="table.header.owner" xml:space="preserve">
+				<source>Owner</source>
+			</trans-unit>
+			<trans-unit id="table.header.path" xml:space="preserve">
+				<source>Path</source>
+			</trans-unit>
+			<trans-unit id="js.label_lostandfound" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Lost and found</source>
+			</trans-unit>
+			<trans-unit id="js.label_deletedTab" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Deleted data</source>
+			</trans-unit>
+			<trans-unit id="js.label_loadMessage" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Please wait...&lt;br/&gt;Records would be loaded!</source>
+			</trans-unit>
+			<trans-unit id="js.label_doDelete_confirmText" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Delete this record AND if page ALL subrecords ? &lt;br/&gt;ATTENTION: Data will be finally deleted from the database</source>
+			</trans-unit>
+			<trans-unit id="js.label_deleteButton_text" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Delete</source>
+			</trans-unit>
+			<trans-unit id="js.label_deleteButton_tooltip" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Delete selected items</source>
+			</trans-unit>
+			<trans-unit id="js.label_undeleteButton_text" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Undelete</source>
+			</trans-unit>
+			<trans-unit id="js.label_undeleteButton_tooltip" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Undelete selected items</source>
+			</trans-unit>
+			<trans-unit id="js.label_error_NoSelectedRows_title" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>No row selected</source>
+			</trans-unit>
+			<trans-unit id="js.label_error_NoSelectedRows_msg" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>You must select a row!</source>
+			</trans-unit>
+			<trans-unit id="js.label_yes" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Yes</source>
+			</trans-unit>
+			<trans-unit id="js.label_no" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>No</source>
+			</trans-unit>
+			<trans-unit id="js.label_sure" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Are you Sure?</source>
+			</trans-unit>
+			<trans-unit id="js.label_crdate" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Created</source>
+			</trans-unit>
+			<trans-unit id="js.label_owner" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Owner</source>
+			</trans-unit>
+			<trans-unit id="js.label_tstamp" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Last edit</source>
+			</trans-unit>
+			<trans-unit id="js.label_clear" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Clear</source>
+			</trans-unit>
+			<trans-unit id="js.label_filter" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Filter</source>
+			</trans-unit>
+			<trans-unit id="js.label_title_undelete" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Undelete?</source>
+			</trans-unit>
+			<trans-unit id="js.label_text_undelete" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Undelete records from tables: </source>
+			</trans-unit>
+			<trans-unit id="js.label_title_delete" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Delete?</source>
+			</trans-unit>
+			<trans-unit id="js.label_text_delete" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Delete records from tables: </source>
+			</trans-unit>
+			<trans-unit id="js.label_boxLabel_undelete_recursive" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Undelete recursively</source>
+			</trans-unit>
+			<trans-unit id="js.label_tableMenu_label" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Table:</source>
+			</trans-unit>
+			<trans-unit id="js.label_tableMenu_emptyText" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Choose a table...</source>
+			</trans-unit>
+			<trans-unit id="js.label_filter_emptyText" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Keyword</source>
+			</trans-unit>
+			<trans-unit id="js.label_search" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Search:</source>
+			</trans-unit>
+			<trans-unit id="js.label_pagingMessage" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Displaying records {0} - {1} of {2}</source>
+			</trans-unit>
+			<trans-unit id="js.label_pagingEmpty" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>No records to display</source>
+			</trans-unit>
+			<trans-unit id="js.label_noValueFound" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>No records for {0}</source>
+			</trans-unit>
+			<trans-unit id="js.label_records" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Records</source>
+			</trans-unit>
+			<trans-unit id="js.label_table" xml:space="preserve" deprecated="Unused since CMS 7">
+				<source>Table</source>
+			</trans-unit>
+		</body>
+	</file>
+</xliff>
diff --git a/typo3/sysext/recycler/mod1/locallang_mod.xlf b/typo3/sysext/recycler/Resources/Private/Language/locallang_mod.xlf
similarity index 100%
rename from typo3/sysext/recycler/mod1/locallang_mod.xlf
rename to typo3/sysext/recycler/Resources/Private/Language/locallang_mod.xlf
diff --git a/typo3/sysext/recycler/Resources/Private/Layouts/Default.html b/typo3/sysext/recycler/Resources/Private/Layouts/Default.html
new file mode 100644
index 0000000000000000000000000000000000000000..11270b3567e370e5a225a1b4fbd902aae37439b9
--- /dev/null
+++ b/typo3/sysext/recycler/Resources/Private/Layouts/Default.html
@@ -0,0 +1,36 @@
+<f:be.container
+	includeRequireJsModules="{
+		0:'TYPO3/CMS/Recycler/Recycler'
+	}"
+>
+	<div class="typo3-fullDoc">
+		<div id="typo3-docheader">
+			<div class="typo3-docheader-functions">
+				<div class="left">
+					<f:be.buttons.csh />
+				</div>
+				<div class="right">
+				</div>
+			</div>
+
+			<div class="typo3-docheader-buttons">
+				<div class="left">
+					<f:render section="iconButtons" />
+				</div>
+				<div class="right">
+					<f:be.buttons.shortcut />
+				</div>
+			</div>
+		</div>
+
+		<div id="typo3-docbody">
+			<div id="typo3-inner-docbody">
+				<h1><f:translate key="title" /></h1>
+
+				<f:flashMessages renderMode="div" />
+
+				<f:render section="content" />
+			</div>
+		</div>
+	</div>
+</f:be.container>
\ No newline at end of file
diff --git a/typo3/sysext/recycler/Resources/Private/Partials/RecordsTable/DeletedRecord.html b/typo3/sysext/recycler/Resources/Private/Partials/RecordsTable/DeletedRecord.html
new file mode 100644
index 0000000000000000000000000000000000000000..0db4ea43f0d3b24ce14d2013ea874d0b649924f6
--- /dev/null
+++ b/typo3/sysext/recycler/Resources/Private/Partials/RecordsTable/DeletedRecord.html
@@ -0,0 +1,50 @@
+<tr data-uid="{record.uid}" data-table="{record.table}" data-recordtitle="{record.title}">
+	<td>
+		<div class="btn-group">
+			<div class="btn-group btn-checkbox-holder">
+				<input type="checkbox" class="smallCheckboxes btn-checkbox">
+				<span class="btn">
+					<span class="t3-icon fa"></span>
+				</span>
+			</div>
+			<a class="btn" data-action="expand" data-toggle="collapse" data-target="#{record.table}_{record.uid}">
+				<f:be.buttons.icon icon="apps-pagetree-collapse" title="{f:translate(key: 'LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.expand')}" />
+			</a>
+			<a class="btn" data-action="undo">
+				<f:be.buttons.icon icon="actions-edit-undo" title="{f:translate(key: 'LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.undo')}" />
+			</a>
+			<f:if condition="{allowDelete}">
+				<a class="btn" data-action="delete">
+					<f:be.buttons.icon icon="actions-edit-delete" title="{f:translate(key: 'LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.delete')}" />
+				</a>
+			</f:if>
+		</div>
+	</td>
+	<td>{record.tableTitle}</td>
+	<td><f:format.html>{record.icon}</f:format.html> {record.title}</td>
+	<td>{record.tstamp}</td>
+	<td>{record.uid}</td>
+	<td>{record.pageTitle} ({record.pid})</td>
+</tr>
+<tr class="collapse" id="{record.table}_{record.uid}">
+	<td colspan="6">
+		<table class="table">
+			<thead>
+				<tr>
+					<th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.table" /></th>
+					<th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.crdate" /></th>
+					<th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.owner" /></th>
+					<th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.path" /></th>
+				</tr>
+			</thead>
+			<tbody>
+				<tr>
+					<td>{record.table}</td>
+					<td>{record.crdate}</td>
+					<td>{record.owner} ({record.owner_uid})</td>
+					<td><f:format.html>{record.path}</f:format.html></td>
+				</tr>
+			</tbody>
+		</table>
+	</td>
+</tr>
\ No newline at end of file
diff --git a/typo3/sysext/recycler/Resources/Private/Templates/Ajax/RecordsTable.html b/typo3/sysext/recycler/Resources/Private/Templates/Ajax/RecordsTable.html
new file mode 100644
index 0000000000000000000000000000000000000000..01ace236a609c8528f7076ceb20bd72b4ee05a91
--- /dev/null
+++ b/typo3/sysext/recycler/Resources/Private/Templates/Ajax/RecordsTable.html
@@ -0,0 +1,9 @@
+<f:for each="{records}" as="record">
+	<f:render
+		partial="RecordsTable/DeletedRecord"
+		arguments="{
+			record: '{record}',
+			allowDelete: '{allowDelete}'
+		}"
+	/>
+</f:for>
diff --git a/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/Index.html b/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/Index.html
new file mode 100644
index 0000000000000000000000000000000000000000..f6ac036e104d23990952f48af4d1055cb5897eaa
--- /dev/null
+++ b/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/Index.html
@@ -0,0 +1,51 @@
+<f:layout name="Default" />
+
+<f:section name="iconButtons">
+	<a data-action="reload"><f:be.buttons.icon icon="actions-system-refresh" title="{f:translate(key:'button.reload')}" /></a>
+</f:section>
+
+<f:section name="content">
+	<div id="recycler-index">
+		<form id="recycler-form" class="form-inline">
+			<div class="input-group">
+				<input type="text" name="search-text" class="form-control">
+				<span class="input-group-btn">
+					<button type="submit" class="btn btn-default disabled"><span class="t3-icon fa fa-search"></span></button>
+				</span>
+			</div>
+			<select name="depth" class="form-control">
+				<option value="0"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_0" /></option>
+				<option value="1"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_1" /></option>
+				<option value="2"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_2" /></option>
+				<option value="3"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_3" /></option>
+				<option value="4"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_4" /></option>
+				<option value="999"><f:translate key="LLL:EXT:lang/locallang_core.xlf:labels.depth_infi" /></option>
+			</select>
+			<select name="pages" class="form-control"></select>
+		</form>
+		<div class="table-fit">
+			<table class="table table-hover" id="itemsInRecycler">
+				<thead>
+					<tr>
+						<th><f:translate key="table.header.actions" /></th>
+						<th><f:translate key="table.header.table" /></th>
+						<th><f:translate key="table.header.record" /></th>
+						<th><f:translate key="table.header.tstamp" /></th>
+						<th><f:translate key="table.header.uid" /></th>
+						<th><f:translate key="table.header.pid" /></th>
+					</tr>
+				</thead>
+				<tbody>
+				</tbody>
+			</table>
+		</div>
+		<div class="text-right">
+			<button class="btn btn-success disabled" data-action="massundo"><f:translate key="button.undo" /></button>
+			<f:if condition="{allowDelete}">
+				<button class="btn btn-danger disabled" data-action="massdelete"><f:translate key="button.delete" /></button>
+			</f:if>
+		</div>
+		<nav>
+		</nav>
+	</div>
+</f:section>
\ No newline at end of file
diff --git a/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/NoAccess.html b/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/NoAccess.html
new file mode 100644
index 0000000000000000000000000000000000000000..920e5a58630ef6e37481b05339d55691e457b405
--- /dev/null
+++ b/typo3/sysext/recycler/Resources/Private/Templates/RecyclerModule/NoAccess.html
@@ -0,0 +1,8 @@
+<f:layout name="Default" />
+
+<f:section name="iconButtons">
+</f:section>
+
+<f:section name="content">
+	--No Access--
+</f:section>
\ No newline at end of file
diff --git a/typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js b/typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee97b44123a1badbef887083823db919de5d9af7
--- /dev/null
+++ b/typo3/sysext/recycler/Resources/Public/JavaScript/Recycler.js
@@ -0,0 +1,539 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * RequireJS module for Recycler
+ */
+define(['jquery', 'nprogress'], function($, NProgress) {
+	var Recycler = {
+		identifiers: {
+			searchForm: '#recycler-form',
+			searchText: '#recycler-form [name=search-text]',
+			searchSubmitBtn: '#recycler-form button[type=submit]',
+			depthSelector: '#recycler-form [name=depth]',
+			tableSelector: '#recycler-form [name=pages]',
+			recyclerTable: '#itemsInRecycler',
+			paginator: '#recycler-index nav',
+			reloadAction: 'a[data-action=reload]',
+			massUndo: 'button[data-action=massundo]',
+			massDelete: 'button[data-action=massdelete]'
+		},
+		elements: {}, // filled in getElements()
+		paging: {
+			currentPage: 1,
+			totalPages: 1,
+			totalItems: 0,
+			itemsPerPage: 20
+		},
+		markedRecordsForMassAction: []
+	};
+
+	/**
+	 * Gets required elements
+	 */
+	Recycler.getElements = function() {
+		Recycler.elements = {
+			$searchForm: $(Recycler.identifiers.searchForm),
+			$searchTextField: $(Recycler.identifiers.searchText),
+			$searchSubmitBtn: $(Recycler.identifiers.searchSubmitBtn),
+			$depthSelector: $(Recycler.identifiers.depthSelector),
+			$tableSelector: $(Recycler.identifiers.tableSelector),
+			$recyclerTable: $(Recycler.identifiers.recyclerTable),
+			$tableBody: $(Recycler.identifiers.recyclerTable).find('tbody'),
+			$paginator: $(Recycler.identifiers.paginator),
+			$reloadAction: $(Recycler.identifiers.reloadAction),
+			$massUndo: $(Recycler.identifiers.massUndo),
+			$massDelete: $(Recycler.identifiers.massDelete)
+		};
+	};
+
+	/**
+	 * Register events
+	 */
+	Recycler.registerEvents = function() {
+		// submitting the form
+		Recycler.elements.$searchForm.on('submit', function(e) {
+			e.preventDefault();
+			if (Recycler.elements.$searchTextField.val() !== '') {
+				Recycler.loadDeletedElements();
+			}
+		});
+
+		// changing the search field
+		Recycler.elements.$searchTextField.on('keyup', function() {
+			var $me = $(this);
+
+			if ($me.val() !== '') {
+				Recycler.elements.$searchSubmitBtn.removeClass('disabled');
+			} else {
+				Recycler.elements.$searchSubmitBtn.addClass('disabled');
+				Recycler.loadDeletedElements();
+			}
+		});
+
+		// changing "depth"
+		Recycler.elements.$depthSelector.on('change', function() {
+			Recycler.loadAvailableTables();
+			Recycler.loadDeletedElements();
+		});
+
+		// changing "table"
+		Recycler.elements.$tableSelector.on('change', function() {
+			Recycler.loadDeletedElements();
+		});
+
+		// clicking "recover" in single row
+		Recycler.elements.$recyclerTable.on('click', '[data-action=undo]', Recycler.undoRecord);
+
+		// clicking "delete" in single row
+		Recycler.elements.$recyclerTable.on('click', '[data-action=delete]', Recycler.deleteRecord);
+
+		Recycler.elements.$reloadAction.on('click', function(e) {
+			e.preventDefault();
+			Recycler.loadDeletedElements();
+		});
+
+		// clicking an action in the paginator
+		Recycler.elements.$paginator.on('click', 'a[data-action]', function(e) {
+			e.preventDefault();
+
+			var $el = $(this),
+				reload = false;
+
+			switch ($el.data('action')) {
+				case 'previous':
+					if (Recycler.paging.currentPage > 1) {
+						Recycler.paging.currentPage--;
+						reload = true;
+					}
+					break;
+				case 'next':
+					if (Recycler.paging.currentPage < Recycler.paging.totalPages) {
+						Recycler.paging.currentPage++;
+						reload = true;
+					}
+					break;
+				case 'page':
+					Recycler.paging.currentPage = parseInt($el.find('span').text());
+					reload = true;
+					break;
+			}
+
+			if (reload) {
+				Recycler.loadDeletedElements();
+			}
+		});
+
+		if (!TYPO3.settings.Recycler.deleteDisable) {
+			Recycler.elements.$massDelete.show();
+		} else {
+			Recycler.elements.$massDelete.remove();
+		}
+
+		Recycler.elements.$recyclerTable.on('show.bs.collapse hide.bs.collapse', 'tr.collapse', function(e) {
+			var $trigger = $(e.currentTarget).prev('tr').find('[data-action=expand]'),
+				$iconEl = $trigger.find('.t3-icon'),
+				removeClass,
+				addClass;
+
+			switch (e.type) {
+				case 'show':
+					removeClass = 't3-icon-pagetree-collapse';
+					addClass = 't3-icon-pagetree-expand';
+					break;
+				case 'hide':
+					removeClass = 't3-icon-pagetree-expand';
+					addClass = 't3-icon-pagetree-collapse';
+					break;
+			}
+
+			$iconEl.removeClass(removeClass).addClass(addClass);
+		});
+
+		// checkboxes in the table
+		Recycler.elements.$recyclerTable.on('click', 'tr input[type=checkbox]', Recycler.handleCheckboxSelects);
+
+		Recycler.elements.$massUndo.on('click', Recycler.undoRecord);
+		Recycler.elements.$massDelete.on('click', Recycler.deleteRecord);
+	};
+
+	/**
+	 * Initialize the recycler module
+	 */
+	Recycler.initialize = function() {
+		NProgress.configure({parent: '#typo3-docheader', showSpinner: false});
+
+		Recycler.getElements();
+		Recycler.registerEvents();
+
+		if (TYPO3.settings.Recycler.depthSelection > 0) {
+			Recycler.elements.$depthSelector.val(TYPO3.settings.Recycler.depthSelection).trigger('change');
+		} else {
+			Recycler.loadAvailableTables();
+			Recycler.loadDeletedElements();
+		}
+	};
+
+	/**
+	 * Handles the clicks on checkboxes in the records table
+	 */
+	Recycler.handleCheckboxSelects = function() {
+		var $checkbox = $(this),
+			$tr = $checkbox.parents('tr'),
+			table = $tr.data('table'),
+			uid = $tr.data('uid'),
+			record = table + ':' + uid;
+
+		if ($checkbox.prop('checked')) {
+			Recycler.markedRecordsForMassAction.push(record);
+			$tr.addClass('warning');
+		} else {
+			var index = Recycler.markedRecordsForMassAction.indexOf(record);
+			if (index > -1) {
+				Recycler.markedRecordsForMassAction.splice(index, 1);
+			}
+			$tr.removeClass('warning');
+		}
+
+		if (Recycler.markedRecordsForMassAction.length > 0) {
+			if (Recycler.elements.$massUndo.hasClass('disabled')) {
+				Recycler.elements.$massUndo.removeClass('disabled');
+			}
+			if (Recycler.elements.$massDelete.hasClass('disabled')) {
+				Recycler.elements.$massDelete.removeClass('disabled');
+			}
+
+			var btnTextUndo = Recycler.createMessage(TYPO3.lang['button.undoselected'], [Recycler.markedRecordsForMassAction.length]),
+				btnTextDelete = Recycler.createMessage(TYPO3.lang['button.deleteselected'], [Recycler.markedRecordsForMassAction.length]);
+
+			Recycler.elements.$massUndo.text(btnTextUndo);
+			Recycler.elements.$massDelete.text(btnTextDelete);
+		} else {
+			Recycler.resetMassActionButtons();
+		}
+	};
+
+	/**
+	 * Resets the mass action state
+	 */
+	Recycler.resetMassActionButtons = function() {
+		Recycler.markedRecordsForMassAction = [];
+		Recycler.elements.$massUndo.addClass('disabled').text(TYPO3.lang['button.undo']);
+		Recycler.elements.$massDelete.addClass('disabled').text(TYPO3.lang['button.delete']);
+	};
+
+	/**
+	 * Loads all tables which contain deleted records. This call is not asynchronous
+	 * due to settings the stored table before loading the deleted records.
+	 */
+	Recycler.loadAvailableTables = function() {
+		$.ajax({
+			url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::dispatch'],
+			dataType: 'json',
+			async: false,
+			data: {
+				action: 'getTables',
+				startUid: TYPO3.settings.Recycler.startUid,
+				depth: Recycler.elements.$depthSelector.find('option:selected').val()
+			},
+			beforeSend: function() {
+				NProgress.start();
+				Recycler.elements.$tableSelector.val('');
+				Recycler.paging.currentPage = 1;
+			},
+			success: function(data) {
+				var tables = [];
+				Recycler.elements.$tableSelector.children().remove();
+				$.each(data, function(_, value) {
+					var tableName = value[0],
+						deletedRecords = value[1],
+						tableDescription = value[2];
+
+					if (tableDescription === '') {
+						tableDescription = TYPO3.lang['label_alltables'];
+					}
+					var optionText = tableDescription + ' (' + deletedRecords + ')';
+					tables.push($('<option />').val(tableName).text(optionText))
+				});
+
+				if (tables.length > 0) {
+					Recycler.elements.$tableSelector.append(tables);
+					if (TYPO3.settings.Recycler.tableSelection !== '') {
+						Recycler.elements.$tableSelector.val(TYPO3.settings.Recycler.tableSelection);
+					}
+				}
+			},
+			complete: function() {
+				NProgress.done();
+			}
+		});
+	};
+
+	/**
+	 * Loads the deleted elements, based on the filters
+	 */
+	Recycler.loadDeletedElements = function() {
+		$.ajax({
+			url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::dispatch'],
+			dataType: 'json',
+			data: {
+				action: 'getDeletedRecords',
+				depth: Recycler.elements.$depthSelector.find('option:selected').val(),
+				startUid: TYPO3.settings.Recycler.startUid,
+				table: Recycler.elements.$tableSelector.find('option:selected').val(),
+				filterTxt: Recycler.elements.$searchTextField.val(),
+				start: (Recycler.paging.currentPage -1) * Recycler.paging.itemsPerPage,
+				limit: Recycler.paging.itemsPerPage
+			},
+			beforeSend: function() {
+				NProgress.start();
+				Recycler.resetMassActionButtons();
+			},
+			success: function(data) {
+				Recycler.elements.$tableBody.html(data.rows);
+				Recycler.buildPaginator(data.totalItems);
+			},
+			complete: function() {
+				NProgress.done();
+			}
+		});
+	};
+
+	Recycler.deleteRecord = function() {
+		if (TYPO3.settings.Recycler.deleteDisable) {
+			return;
+		}
+
+		var $tr = $(this).parents('tr'),
+			isMassDelete = $tr.parent().prop('tagName') !== 'TBODY'; // deleteRecord() was invoked by the mass delete button
+
+		if (isMassDelete) {
+			var records = Recycler.markedRecordsForMassAction,
+				message = TYPO3.lang['modal.massdelete.text'];
+		} else {
+			var uid = $tr.data('uid'),
+				table = $tr.data('table'),
+				records = table + ':' + uid,
+				recordTitle = $tr.data('recordtitle'),
+				message = table === 'pages' ? TYPO3.lang['modal.deletepage.text'] : TYPO3.lang['modal.deletecontent.text'];
+				message = Recycler.createMessage(message, [recordTitle, '[' + records + ']']);
+		}
+
+		top.TYPO3.Modal.confirm(TYPO3.lang['modal.delete.header'], message, top.TYPO3.Severity.error, [
+			{
+				text: TYPO3.lang['button.cancel'],
+				trigger: function() {
+					top.TYPO3.Modal.dismiss();
+				}
+			}, {
+				text: TYPO3.lang['button.delete'],
+				btnClass: 'btn-danger',
+				trigger: function() {
+					Recycler.callAjaxAction('delete', typeof records === 'object' ? records : [records], isMassDelete);
+				}
+			}
+		]);
+	};
+
+	Recycler.undoRecord = function() {
+		var $tr = $(this).parents('tr'),
+			isMassUndo = $tr.parent().prop('tagName') !== 'TBODY'; // undoRecord() was invoked by the mass delete button
+
+		if (isMassUndo) {
+			var records = Recycler.markedRecordsForMassAction,
+				messageText = TYPO3.lang['modal.massundo.text'],
+				recoverPages = true;
+		} else {
+			var uid = $tr.data('uid'),
+				table = $tr.data('table'),
+				records = table + ':' + uid,
+				recordTitle = $tr.data('recordtitle'),
+				$message = null,
+				recoverPages = table === 'pages',
+				messageText = recoverPages ? TYPO3.lang['modal.undopage.text'] : TYPO3.lang['modal.undocontent.text'];
+				messageText = Recycler.createMessage(messageText, [recordTitle, '[' + records + ']']);
+		}
+
+		if (recoverPages) {
+			$message = $('<div />').append(
+				$('<p />').text(messageText),
+				$('<div />', {class: 'checkbox'}).append(
+					$('<label />').append(TYPO3.lang['modal.undo.recursive']).prepend($('<input />', {id: 'undo-recursive', type: 'checkbox'}))
+				)
+			);
+		} else {
+			$message = messageText;
+		}
+
+		top.TYPO3.Modal.confirm(TYPO3.lang['modal.undo.header'], $message, top.TYPO3.Severity.ok, [
+			{
+				text: TYPO3.lang['button.cancel'],
+				trigger: function() {
+					top.TYPO3.Modal.dismiss();
+				}
+			}, {
+				text: TYPO3.lang['button.undo'],
+				btnClass: 'btn-success',
+				trigger: function() {
+					Recycler.callAjaxAction('undo', typeof records === 'object' ? records : [records], isMassUndo);
+				}
+			}
+		]);
+	};
+
+	/**
+	 * Method that really calls the action via AJAX
+	 */
+	Recycler.callAjaxAction = function(action, records, isMassAction) {
+		var data = {
+				records: records,
+				action: ''
+			},
+			reloadPageTree = false;
+		if (action === 'undo') {
+			data.action = 'undoRecords';
+			data.recursive = top.TYPO3.jQuery('#undo-recursive').prop('checked')  ? 1 : 0;
+			reloadPageTree = true;
+		} else if (action === 'delete') {
+			data.action = 'deleteRecords';
+		} else {
+			return;
+		}
+
+		$.ajax({
+			url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::dispatch'],
+			dataType: 'json',
+			data: data,
+			beforeSend: function() {
+				NProgress.start();
+			},
+			success: function(data) {
+				var severity = data.success ? top.TYPO3.Severity.ok : top.TYPO3.Severity.error;
+				top.TYPO3.Flashmessage.display(severity, '', data.message);
+
+				// reload recycler data
+				Recycler.paging.currentPage = 1;
+				Recycler.loadAvailableTables();
+				Recycler.loadDeletedElements();
+
+				if (isMassAction) {
+					Recycler.resetMassActionButtons();
+				}
+
+				if (reloadPageTree) {
+					Recycler.refreshPageTree();
+				}
+			},
+			complete: function() {
+				top.TYPO3.Modal.dismiss();
+				NProgress.done();
+			}
+		});
+	};
+
+	/**
+	 * Replaces the placeholders with actual values
+	 */
+	Recycler.createMessage = function(message, placeholders) {
+		if (typeof message === 'undefined') {
+			return '';
+		}
+
+		return message.replace(
+			/\{([0-9]+)\}/g,
+			function(_, index) {
+				return placeholders[index];
+			}
+		);
+	};
+
+	/**
+	 * Reloads the page tree
+	 */
+	Recycler.refreshPageTree = function() {
+		if (top.TYPO3 && top.TYPO3.Backend && top.TYPO3.Backend.NavigationContainer && top.TYPO3.Backend.NavigationContainer.PageTree) {
+			top.TYPO3.Backend.NavigationContainer.PageTree.refreshTree();
+		}
+	};
+
+	/**
+	 * Build the paginator
+	 */
+	Recycler.buildPaginator = function(totalItems) {
+		if (totalItems === 0) {
+			Recycler.elements.$paginator.contents().remove();
+			return;
+		}
+
+		Recycler.paging.totalItems = totalItems;
+		Recycler.paging.totalPages = Math.ceil(totalItems / Recycler.paging.itemsPerPage);
+
+		if (Recycler.paging.totalPages === 1) {
+			// early abort if only one page is available
+			return;
+		}
+
+		var $ul = $('<ul />', {class: 'pagination pagination-block'}),
+			liElements = [],
+			$controlFirstPage = $('<li />').append(
+				$('<a />', {'data-action': 'previous'}).append(
+					$('<span />', {class: 't3-icon fa fa-arrow-left'})
+				)
+			),
+			$controlLastPage = $('<li />').append(
+				$('<a />', {'data-action': 'next'}).append(
+					$('<span />', {class: 't3-icon fa fa-arrow-right'})
+				)
+			);
+
+		if (Recycler.paging.currentPage === 1) {
+			$controlFirstPage.disablePagingAction();
+		}
+
+		if (Recycler.paging.currentPage === Recycler.paging.totalPages) {
+			$controlLastPage.disablePagingAction();
+		}
+
+		for (var i = 1; i <= Recycler.paging.totalPages; i++) {
+			var $li = $('<li />', {class: Recycler.paging.currentPage === i ? 'active' : ''});
+			$li.append(
+				$('<a />', {'data-action': 'page'}).append(
+					$('<span />').text(i)
+				)
+			);
+			liElements.push($li);
+		}
+
+		$ul.append($controlFirstPage, liElements, $controlLastPage);
+		Recycler.elements.$paginator.contents().replaceWith($ul);
+	};
+
+	/**
+	 * Changes the markup of a pagination action being disabled
+	 */
+	$.fn.disablePagingAction = function() {
+		$(this).addClass('disabled').find('.t3-icon').unwrap().wrap($('<span />'));
+	};
+
+	/**
+	 * return the main Recycler object
+	 * initialize once on document ready
+	 */
+	return function() {
+		$(document).ready(function() {
+			Recycler.initialize();
+		});
+
+		return Recycler;
+	}();
+});
diff --git a/typo3/sysext/recycler/ext_localconf.php b/typo3/sysext/recycler/ext_localconf.php
index 1e8bded9343d2646f0930c370cad82c5da30d7b6..7ec4801a7ee738836498143d3be53e22bcaefc1f 100644
--- a/typo3/sysext/recycler/ext_localconf.php
+++ b/typo3/sysext/recycler/ext_localconf.php
@@ -2,5 +2,5 @@
 defined('TYPO3_MODE') or die();
 
 if (TYPO3_MODE === 'BE') {
-	\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::registerAjaxHandler('RecyclerAjaxController::init', \TYPO3\CMS\Recycler\Controller\RecyclerAjaxController::class . '->init');
+	\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::registerAjaxHandler('RecyclerAjaxController::dispatch', \TYPO3\CMS\Recycler\Controller\RecyclerAjaxController::class . '->dispatch');
 }
diff --git a/typo3/sysext/recycler/ext_tables.php b/typo3/sysext/recycler/ext_tables.php
index caf5395ff3ac1ebd550215b2f42026d3e5b6103f..15ae46e0699015bdfd8a2b3dc6bfd153b26ff6c3 100644
--- a/typo3/sysext/recycler/ext_tables.php
+++ b/typo3/sysext/recycler/ext_tables.php
@@ -2,26 +2,18 @@
 defined('TYPO3_MODE') or die();
 
 if (TYPO3_MODE === 'BE') {
-	// Add module
-	\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModulePath(
-		'web_txrecyclerM1',
-		\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY) . 'mod1/'
-	);
-	\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule(
+	\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule(
+		'TYPO3.CMS.' . $_EXTKEY,
 		'web',
-		'txrecyclerM1',
+		'Recycler',
 		'',
-		\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY) . 'mod1/',
 		array(
-			'script' => '_DISPATCH',
+			'RecyclerModule' => 'index',
+		),
+		array(
 			'access' => 'user,group',
-			'name' => 'web_txrecyclerM1',
-			'labels' => array(
-				'tabs_images' => array(
-					'tab' => '../Resources/Public/Icons/module-recycler.png',
-				),
-				'll_ref' => 'LLL:EXT:recycler/mod1/locallang_mod.xlf',
-			),
+			'icon' => 'EXT:recycler/Resources/Public/Icons/module-recycler.png',
+			'labels' => 'LLL:EXT:' . $_EXTKEY . '/Resources/Private/Language/locallang_mod.xlf',
 		)
 	);
-}
+}
\ No newline at end of file
diff --git a/typo3/sysext/recycler/mod1/clear.gif b/typo3/sysext/recycler/mod1/clear.gif
deleted file mode 100644
index e1d2d83216e249399411d7e5caf10b5ece31db3d..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/mod1/clear.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/mod1/index.php b/typo3/sysext/recycler/mod1/index.php
deleted file mode 100644
index e21d0d20956e507247483ce0037e2ed947830371..0000000000000000000000000000000000000000
--- a/typo3/sysext/recycler/mod1/index.php
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-$SOBE = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Recycler\Controller\RecyclerModuleController::class);
-$SOBE->initialize();
-$SOBE->render();
-$SOBE->flush();
diff --git a/typo3/sysext/recycler/mod1/locallang.xlf b/typo3/sysext/recycler/mod1/locallang.xlf
deleted file mode 100644
index 6775f038546879022dc410f33995272369d93a5b..0000000000000000000000000000000000000000
--- a/typo3/sysext/recycler/mod1/locallang.xlf
+++ /dev/null
@@ -1,113 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
-	<file t3:id="1415814907" source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:35Z" product-name="recycler">
-		<header/>
-		<body>
-			<trans-unit id="title" xml:space="preserve">
-				<source>Recycler</source>
-			</trans-unit>
-			<trans-unit id="description" xml:space="preserve">
-				<source>The recycler allows you to select any deleted data and undelete it. You can undelete recursive if the parent of the element is deleted too.</source>
-			</trans-unit>
-			<trans-unit id="label_alltables" xml:space="preserve">
-				<source>All tables</source>
-			</trans-unit>
-			<trans-unit id="js.label_lostandfound" xml:space="preserve">
-				<source>Lost and found</source>
-			</trans-unit>
-			<trans-unit id="js.label_deletedTab" xml:space="preserve">
-				<source>Deleted data</source>
-			</trans-unit>
-			<trans-unit id="js.label_loadMessage" xml:space="preserve">
-				<source>Please wait...&lt;br/&gt;Records would be loaded!</source>
-			</trans-unit>
-			<trans-unit id="js.label_doDelete_confirmText" xml:space="preserve">
-				<source>Delete this record AND if page ALL subrecords ? &lt;br/&gt;ATTENTION: Data will be finally deleted from the database</source>
-			</trans-unit>
-			<trans-unit id="js.label_deleteButton_text" xml:space="preserve">
-				<source>Delete</source>
-			</trans-unit>
-			<trans-unit id="js.label_deleteButton_tooltip" xml:space="preserve">
-				<source>Delete selected items</source>
-			</trans-unit>
-			<trans-unit id="js.label_undeleteButton_text" xml:space="preserve">
-				<source>Undelete</source>
-			</trans-unit>
-			<trans-unit id="js.label_undeleteButton_tooltip" xml:space="preserve">
-				<source>Undelete selected items</source>
-			</trans-unit>
-			<trans-unit id="js.label_error_NoSelectedRows_title" xml:space="preserve">
-				<source>No row selected</source>
-			</trans-unit>
-			<trans-unit id="js.label_error_NoSelectedRows_msg" xml:space="preserve">
-				<source>You must select a row!</source>
-			</trans-unit>
-			<trans-unit id="js.label_yes" xml:space="preserve">
-				<source>Yes</source>
-			</trans-unit>
-			<trans-unit id="js.label_no" xml:space="preserve">
-				<source>No</source>
-			</trans-unit>
-			<trans-unit id="js.label_sure" xml:space="preserve">
-				<source>Are you Sure?</source>
-			</trans-unit>
-			<trans-unit id="js.label_crdate" xml:space="preserve">
-				<source>Created</source>
-			</trans-unit>
-			<trans-unit id="js.label_owner" xml:space="preserve">
-				<source>Owner</source>
-			</trans-unit>
-			<trans-unit id="js.label_tstamp" xml:space="preserve">
-				<source>Last edit</source>
-			</trans-unit>
-			<trans-unit id="js.label_clear" xml:space="preserve">
-				<source>Clear</source>
-			</trans-unit>
-			<trans-unit id="js.label_filter" xml:space="preserve">
-				<source>Filter</source>
-			</trans-unit>
-			<trans-unit id="js.label_title_undelete" xml:space="preserve">
-				<source>Undelete?</source>
-			</trans-unit>
-			<trans-unit id="js.label_text_undelete" xml:space="preserve">
-				<source>Undelete records from tables: </source>
-			</trans-unit>
-			<trans-unit id="js.label_title_delete" xml:space="preserve">
-				<source>Delete?</source>
-			</trans-unit>
-			<trans-unit id="js.label_text_delete" xml:space="preserve">
-				<source>Delete records from tables: </source>
-			</trans-unit>
-			<trans-unit id="js.label_boxLabel_undelete_recursive" xml:space="preserve">
-				<source>Undelete recursively</source>
-			</trans-unit>
-			<trans-unit id="js.label_tableMenu_label" xml:space="preserve">
-				<source>Table:</source>
-			</trans-unit>
-			<trans-unit id="js.label_tableMenu_emptyText" xml:space="preserve">
-				<source>Choose a table...</source>
-			</trans-unit>
-			<trans-unit id="js.label_filter_emptyText" xml:space="preserve">
-				<source>Keyword</source>
-			</trans-unit>
-			<trans-unit id="js.label_search" xml:space="preserve">
-				<source>Search:</source>
-			</trans-unit>
-			<trans-unit id="js.label_pagingMessage" xml:space="preserve">
-				<source>Displaying records {0} - {1} of {2}</source>
-			</trans-unit>
-			<trans-unit id="js.label_pagingEmpty" xml:space="preserve">
-				<source>No records to display</source>
-			</trans-unit>
-			<trans-unit id="js.label_noValueFound" xml:space="preserve">
-				<source>No records for {0}</source>
-			</trans-unit>
-			<trans-unit id="js.label_records" xml:space="preserve">
-				<source>Records</source>
-			</trans-unit>
-			<trans-unit id="js.label_table" xml:space="preserve">
-				<source>Table</source>
-			</trans-unit>
-		</body>
-	</file>
-</xliff>
diff --git a/typo3/sysext/recycler/mod1/mod_template.html b/typo3/sysext/recycler/mod1/mod_template.html
deleted file mode 100644
index f5f96bd32ebac23a8b92476cc98fb72daf5985b9..0000000000000000000000000000000000000000
--- a/typo3/sysext/recycler/mod1/mod_template.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<!-- ###FULLDOC### begin -->
-<div class="typo3-fullDoc">
-	<div id="typo3-docheader">
-		<div class="typo3-docheader-functions">
-			<div class="left">###FUNC_MENU###</div>
-			<div class="right">###PAGEPATH######PAGEINFO###</div>
-		</div>
-		<div class="typo3-docheader-buttons">
-			<div class="left">###BUTTONLIST_LEFT###</div>
-			<div class="right">###BUTTONLIST_RIGHT###</div>
-		</div>
-	</div>
-
-	<div id="typo3-docbody">
-		<div id="typo3-inner-docbody">
-			###CONTENT###
-		</div>
-	</div>
-</div>
-<!-- ###FULLDOC### end -->
-
-<!-- Grouping the icons on top -->
-
-<!-- ###BUTTON_GROUP_WRAP### -->
-<div class="buttongroup">###BUTTONS###</div>
-<!-- ###BUTTON_GROUP_WRAP### -->
-
-<!-- ###BUTTON_GROUPS_LEFT### -->
-<!-- ###BUTTON_GROUP1### -->###SAVE###<!-- ###BUTTON_GROUP1### -->
-<!-- ###BUTTON_GROUPS_LEFT### -->
-
-<!-- ###BUTTON_GROUPS_RIGHT### -->
-<!-- ###BUTTON_GROUP1### -->###SHORTCUT###<!-- ###BUTTON_GROUP1### -->
-<!-- ###BUTTON_GROUPS_RIGHT### -->
\ No newline at end of file
diff --git a/typo3/sysext/recycler/mod1/moduleicon.gif b/typo3/sysext/recycler/mod1/moduleicon.gif
deleted file mode 100644
index 3fc6c52ba0b8e0204a3c4bf24dd79bdd089510e4..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/mod1/moduleicon.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/css/customExtJs.css b/typo3/sysext/recycler/res/css/customExtJs.css
deleted file mode 100644
index dfad426261c0042d1f01f1c1047fdf80e7cd1ecd..0000000000000000000000000000000000000000
--- a/typo3/sysext/recycler/res/css/customExtJs.css
+++ /dev/null
@@ -1,47 +0,0 @@
-#recyclerContent {
-	margin-top: 10px;
-}
-
-.recycler-messagebox {
-	padding: 10px;
-}
-
-ul.recycler-table-list {
-	list-style: disc;
-	margin: 5px 0 5px 15px;
-}
-
-ul.recycler-table-list li {
-	margin-left: 10px;
-}
-
-dl.recycler-table-list-entry-details {
-	margin-left: 45px;
-}
-
-dl.recycler-table-list-entry-details dt {
-	font-weight: bold;
-	float: left;
-	clear: left;
-	margin-right: 3px;
-}
-
-button.delete {
-	background-image: url('../icons/delete.gif');
-}
-
-button.undelete {
-	background-image: url('../icons/arrow_rotate_anticlockwise.png');
-}
-
-button.backup {
-	background-image: url('../icons/database_save.png');
-}
-
-button.filter_refresh {
-	background-image: url('../icons/filter_refresh.png');
-}
-
-button.filter_clear {
-	background-image: url('../icons/filter_clear.png');
-}
\ No newline at end of file
diff --git a/typo3/sysext/recycler/res/icons/accept.png b/typo3/sysext/recycler/res/icons/accept.png
deleted file mode 100644
index b3141e1f138d9f77f0d064f69bf94cee0a2509a2..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/accept.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/arrow_redo.png b/typo3/sysext/recycler/res/icons/arrow_redo.png
deleted file mode 100644
index 7504a198237d04a37d8415d920ed783f96ab0a77..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/arrow_redo.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/arrow_rotate_anticlockwise.png b/typo3/sysext/recycler/res/icons/arrow_rotate_anticlockwise.png
deleted file mode 100644
index 7f8ffc16ba3f4e49eb05f32211dd9f79de6d4a15..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/arrow_rotate_anticlockwise.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/bin.png b/typo3/sysext/recycler/res/icons/bin.png
deleted file mode 100644
index 1168cf951bdbf620bdb267ff86043bcd424f6957..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/bin.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/bin_closed.png b/typo3/sysext/recycler/res/icons/bin_closed.png
deleted file mode 100644
index e5fae88a5ff9f289723d86f2591b439b648e94df..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/bin_closed.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/bin_empty.png b/typo3/sysext/recycler/res/icons/bin_empty.png
deleted file mode 100644
index 16d3b461789d2af0f6c98724057fcb3ef0cc0e89..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/bin_empty.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/database_save.png b/typo3/sysext/recycler/res/icons/database_save.png
deleted file mode 100644
index 9603e3cd06f7becb90d6bf2d6a1c5d836e5581c4..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/database_save.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/delete.gif b/typo3/sysext/recycler/res/icons/delete.gif
deleted file mode 100644
index 8b9cc3425a78d05ecd51c57104b643b044951be7..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/delete.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/filter_clear.png b/typo3/sysext/recycler/res/icons/filter_clear.png
deleted file mode 100644
index c7c6287e1f2822298d1a6db9f55b7368ff5731f0..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/filter_clear.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/filter_refresh.png b/typo3/sysext/recycler/res/icons/filter_refresh.png
deleted file mode 100644
index 2999ee3e8b1b443d3895c022dd2ac0479f62949f..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/filter_refresh.png and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/loading.gif b/typo3/sysext/recycler/res/icons/loading.gif
deleted file mode 100644
index a7f67befff39e97cd07d63c65ac0884534b2e1dd..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/loading.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/recycler.gif b/typo3/sysext/recycler/res/icons/recycler.gif
deleted file mode 100644
index ebad828a80b90e076fbad964e39b8d82ab17dcac..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/recycler.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/recycler2.gif b/typo3/sysext/recycler/res/icons/recycler2.gif
deleted file mode 100644
index 9f0bf9fd76e29be6ecc79840c983587767caaadf..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/recycler2.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/icons/x_toolbar_bg.gif b/typo3/sysext/recycler/res/icons/x_toolbar_bg.gif
deleted file mode 100644
index aebe6ef5a09b9e44a428d2105321423b25cc9151..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/recycler/res/icons/x_toolbar_bg.gif and /dev/null differ
diff --git a/typo3/sysext/recycler/res/js/t3_recycler.js b/typo3/sysext/recycler/res/js/t3_recycler.js
deleted file mode 100644
index 9025978a4f5965b2359dbbe672ca031399c5868d..0000000000000000000000000000000000000000
--- a/typo3/sysext/recycler/res/js/t3_recycler.js
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-/**
- * ExtJS for the 'recycler' extension.
- * Contains the Recycler functions
- *
- * @author	Julian Kleinhans <typo3@kj187.de>
- * @author  Erik Frister <erik_frister@otq-solutions.com>
- * @author  Steffen Kamper <steffen@typo3.org>
- */
-
-Ext.ns('Recycler');
-
-/****************************************************
- * row expander
- ****************************************************/
-Recycler.Expander = new Ext.grid.RowExpander({
-	tpl : new Ext.Template(
-		'<dl class="recycler-table-list-entry-details">' +
-			'<dt>' + TYPO3.l10n.localize('table') + ': </dt><dd>{table}</dd>' +
-			'<dt>' + TYPO3.l10n.localize('crdate') + ': </dt><dd>{crdate}</dd>' +
-			'<dt>' + TYPO3.l10n.localize('tstamp') + ': </dt><dd>{tstamp}</dd>' +
-			'<dt>' + TYPO3.l10n.localize('owner') + ': </dt><dd>{owner} (UID: {owner_uid})</dd>' +
-			'<dt>' + TYPO3.l10n.localize('path') + ': </dt><dd>{path}</dd>' +
-		'</dl>'
-	)
-});
-
-
-/****************************************************
- * Main store
- ****************************************************/
-Recycler.MainStore = new Ext.data.Store({
-	storeId: 'deletedRecordsStore',
-	reader: new Ext.data.JsonReader({
-		totalProperty: 'total',
-		root: 'rows'
-	}, [
-		{name: 'uid', type: 'int'},
-		{name: 'pid', type: 'int'},
-		{name: 'record', mapping: 'title'},
-		{name: 'crdate'},
-		{name: 'tstamp'},
-		{name: 'owner'},
-		{name: 'owner_uid'},
-		{name: 'tableTitle'},
-		{name: 'table'},
-		{name: 'path'}
-	]),
-	sortInfo: {
-		field: 'record',
-		direction: "ASC"
-	},
-	groupField: 'table',
-	url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::init'] + '&cmd=getDeletedRecords',
-	baseParams: {
-		depth: TYPO3.settings.Recycler.depthSelection,
-		startUid: TYPO3.settings.Recycler.startUid,
-		pagingSizeDefault: TYPO3.settings.Recycler.pagingSize,
-		table: TYPO3.settings.Recycler.tableSelection
-	}
-
-});
-
-/****************************************************
- * Simple table store
- ****************************************************/
-Recycler.TableStore = new Ext.data.Store({
-	url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::init'] + '&startUid=' + TYPO3.settings.Recycler.startUid + '&cmd=getTables' + '&depth=' + TYPO3.settings.Recycler.depthSelection,
-	reader: new Ext.data.ArrayReader({}, [
-		{name: 'table', type: 'string'},
-		{name: 'records', type: 'int'},
-		{name: 'valueField', type: 'string'},
-		{name: 'tableTitle', type: 'string'},
-		{name: 'tstamp', type: 'int'}
-	]),
-	listeners: {
-		'load': {
-			fn: function(store, records) {
-				Ext.getCmp('tableSelector').setValue(TYPO3.settings.Recycler.tableSelection);
-			},
-			single: true
-		}
-	}
-})
-
-/****************************************************
- * Confirmation Window
- ****************************************************/
-Recycler.ConfirmWindow = Ext.extend(Ext.Window, {
-
-	width: 300,
-	height: 200,
-
-	title: '',
-	confirmText: '',
-	confirmQuestion: '',
-	records: [],
-	hideRecursive: false,
-	showRecursiveCheckbox: false,
-	arePagesAffected: false,
-	command: '',
-	template: new Ext.XTemplate(
-			'<ul class="recycler-table-list">',
-			'<tpl for=".">',
-				'<li>{[values]}</li>',
-			'</tpl>',
-			'</ul>'
-	),
-	initComponent:function() {
-		Ext.apply(this, {
-			xtype: 'form',
-			bodyCssClass: 'recycler-messagebox',
-			modal: true,
-
-			items: [
-				{
-					xtype: 'label',
-					text: this.confirmText
-				}, {
-					xtype: 'displayfield',
-					tpl:  this.template,
-					data: this.tables
-				}, {
-					xtype: 'label',
-					text:  this.confirmQuestion
-				}, {
-					xtype: 'checkbox',
-					boxLabel: TYPO3.l10n.localize('boxLabel_undelete_recursive'),
-					name: 'recursiveCheckbox',
-					disabled: !this.showRecursiveCheckbox,
-					itemId: 'recursiveCheck',
-					hidden: this.hideRecursive // hide the checkbox when frm is used to permanently delete
-				}
-			],
-			buttons: [
-				{
-					text: TYPO3.l10n.localize('yes'),
-					scope: this,
-					handler: function(button, event) {
-						var tcemainData = [];
-
-						for (var i=0; i < this.records.length; i++) {
-							tcemainData[i] = [this.records[i].data.table, this.records[i].data.uid];
-						}
-						Ext.Ajax.request({
-							url: TYPO3.settings.ajaxUrls['RecyclerAjaxController::init'] + '&cmd=' + this.command,
-							params: {
-								'data': Ext.encode(tcemainData),
-								'recursive': this.getComponent('recursiveCheck').getValue()
-							},
-							callback: function(options, success, response) {
-								if (response.responseText === "1") {
-									// reload the records and the table selector
-									Recycler.MainStore.reload();
-									Recycler.TableStore.reload();
-									if (this.arePagesAffected) {
-										Recycler.Utility.updatePageTree();
-									}
-								} else {
-									Ext.MessageBox.show({
-										title: 'ERROR',
-										msg: response.responseText,
-										buttons: Ext.MessageBox.OK,
-										icon: Ext.MessageBox.ERROR
-									});
-								}
-							}
-						});
-
-						this.close();
-					}
-				},{
-					text: TYPO3.l10n.localize('no'),
-					scope: this,
-					handler: function(button, event) {
-						this.close();
-					}
-				}
-			]
-		});
-		Recycler.ConfirmWindow.superclass.initComponent.apply(this, arguments);
-	}
-});
-
-/****************************************************
- * Utility functions
- ****************************************************/
-Recycler.Utility = {
-	updatePageTree: function() {
-		if (top && top.content && top.content.nav_frame && top.content.nav_frame.Tree) {
-			top.content.nav_frame.Tree.refresh();
-		}
-	},
-
-	// not used?
-	filterGrid: function(grid, component) {
-		var filterText = component.getValue();
-
-		Recycler.MainStore.setBaseParam('filterTxt', filterText);
-		// load the datastore
-		Recycler.MainStore.load({
-			params: {
-				start: 0
-			}
-		});
-	},
-
-	/****************************************************
-	 * permanent deleting function
-	 ****************************************************/
-
-	function_delete: function(button, event) {
-		Recycler.Utility.rowAction(
-			'doDelete',
-			TYPO3.l10n.localize('cmd_doDelete_confirmText'),
-			TYPO3.l10n.localize('title_delete'),
-			TYPO3.l10n.localize('text_delete')
-		);
-	},
-
-	/****************************************************
-	 * Undeleting function
-	 ****************************************************/
-
-	function_undelete: function(button, event) {
-		Recycler.Utility.rowAction(
-			'doUndelete',
-			TYPO3.l10n.localize('sure'),
-			TYPO3.l10n.localize('title_undelete'),
-			TYPO3.l10n.localize('text_undelete')
-		);
-	},
-
-	/****************************************************
-	 * Row action function   ( deleted or undeleted )
-	 ****************************************************/
-
-	rowAction: function(command, confirmQuestion, confirmTitle, confirmText) {
-			// get the 'undeleted records' grid object
-		var records = Recycler.Grid.getSelectionModel().getSelections();
-
-		if (records.length > 0) {
-
-				// check if a page is checked
-			var recursiveCheckbox = false;
-			var arePagesAffected = false;
-			var tables = [];
-			var hideRecursive = ('doDelete' == command);
-
-			for (iterator=0; iterator < records.length; iterator++) {
-				if (tables.indexOf(records[iterator].data.table) < 0) {
-					tables.push(records[iterator].data.table);
-				}
-				if (command == 'doUndelete' && records[iterator].data.table == 'pages' ) {
-					recursiveCheckbox = true;
-					arePagesAffected = true;
-				}
-			}
-
-			var frmConfirm = new Recycler.ConfirmWindow({
-				title: confirmTitle,
-				records: records,
-				tables: tables,
-				confirmText: confirmText,
-				confirmQuestion: confirmQuestion,
-				hideRecursive: hideRecursive,
-				recursiveCheckbox: recursiveCheckbox,
-				arePagesAffected: arePagesAffected,
-				command: command
-			}).show();
-
-		} else {
-				// no row selected
-			Ext.MessageBox.show({
-				title: TYPO3.l10n.localize('error_NoSelectedRows_title'),
-				msg: TYPO3.l10n.localize('error_NoSelectedRows_msg'),
-				buttons: Ext.MessageBox.OK,
-				minWidth: 300,
-				minHeight: 200,
-				icon: Ext.MessageBox.ERROR
-			});
-		}
-	},
-
-	/****************************************************
-	 * pluggable renderer
-	 ****************************************************/
-
-	renderTopic: function (value, p, record) {
-		return String.format('{0}', value, record.data.table, record.data.uid, record.data.pid);
-	}
-};
-
-/****************************************************
- * Grid SelectionModel
- ****************************************************/
-Recycler.SelectionModel = new Ext.grid.CheckboxSelectionModel({
-	singleSelect: false
-});
-
-/****************************************************
- * Grid container
- ****************************************************/
-Recycler.GridContainer = Ext.extend(Ext.grid.GridPanel, {
-	layout: 'fit',
-	renderTo: TYPO3.settings.Recycler.renderTo,
-	width: '98%',
-	frame: true,
-	border: false,
-	defaults: {autoScroll: false},
-	plain: true,
-
-	initComponent : function() {
-		Ext.apply(this, {
-			id: 'delRecordId',
-			stateful: true,
-			stateId: 'recyclerGrid',
-			stateEvents: ['columnmove', 'columnresize', 'sortchange', 'expand', 'collapse'],
-			loadMask: true,
-			stripeRows: true,
-			collapsible: false,
-			animCollapse: false,
-			store: Recycler.MainStore,
-			cm: new Ext.grid.ColumnModel([
-				Recycler.SelectionModel,
-				Recycler.Expander,
-				{header: "UID", width: 10, sortable: true, dataIndex: 'uid'},
-				{header: "PID", width: 10, sortable: true, dataIndex: 'pid'},
-				{id: 'record', header: TYPO3.l10n.localize('records'), width: 50, sortable: true, dataIndex: 'record', renderer: Recycler.Utility.renderTopic},
-				{id: 'table', header: TYPO3.l10n.localize('table'), width: 15, sortable: true, dataIndex: 'tableTitle'},
-				{id: 'tstamp', header: TYPO3.l10n.localize('tstamp'), width: 15, sortable: true, dataIndex: 'tstamp'}
-			]),
-			viewConfig: {
-				forceFit: true
-			},
-			sm: Recycler.SelectionModel,
-			plugins: [Recycler.Expander, new Ext.ux.plugins.FitToParent()],
-			bbar: [
-				{
-
-					/****************************************************
-					 * Paging toolbar
-					 ****************************************************/
-					id: 'recordPaging',
-					xtype: 'paging',
-					store: Recycler.MainStore,
-					pageSize: TYPO3.settings.Recycler.pagingSize,
-					displayInfo: true,
-					displayMsg: TYPO3.l10n.localize('pagingMessage'),
-					emptyMsg: TYPO3.l10n.localize('pagingEmpty')
-				}, '-', {
-					/****************************************************
-					 * Delete button
-					 ****************************************************/
-					xtype: 'button',
-					width: 80,
-					id: 'deleteButton',
-					text: TYPO3.l10n.localize('deleteButton_text'),
-					tooltip: TYPO3.l10n.localize('deleteButton_tooltip'),
-					iconCls: 'delete',
-					disabled: TYPO3.settings.Recycler.deleteDisable,
-					handler: Recycler.Utility.function_delete
-				}, {
-					/****************************************************
-					 * Undelete button
-					 ****************************************************/
-					xtype: 'button',
-					width: 80,
-					id: 'undeleteButton',
-					text: TYPO3.l10n.localize('undeleteButton_text'),
-					tooltip: TYPO3.l10n.localize('undeleteButton_tooltip'),
-					iconCls: 'undelete',
-					handler: Recycler.Utility.function_undelete
-				}
-			],
-
-			tbar: [
-				TYPO3.l10n.localize('search'), ' ',
-					new Ext.app.SearchField({
-					store: Recycler.MainStore,
-					width: 200
-				}),
-				'-', {
-					xtype: 'tbtext',
-					text: TYPO3.l10n.localize('depth') + ':'
-				},{
-
-					/****************************************************
-					 * Depth menu
-					 ****************************************************/
-
-					xtype: 'combo',
-					stateful: true,
-					stateId: 'depthCombo',
-					stateEvents: ['select'],
-					width: 150,
-					lazyRender: true,
-					valueField: 'depth',
-					displayField: 'label',
-					id: 'depthSelector',
-					mode: 'local',
-					emptyText: TYPO3.l10n.localize('depth'),
-					selectOnFocus: true,
-					triggerAction: 'all',
-					editable: false,
-					forceSelection: true,
-					hidden: TYPO3.l10n.localize('showDepthMenu'),
-					store: new Ext.data.SimpleStore({
-						autoLoad: true,
-						fields: ['depth','label'],
-						data : [
-							['0', TYPO3.l10n.localize('depth_0')],
-							['1', TYPO3.l10n.localize('depth_1')],
-							['2', TYPO3.l10n.localize('depth_2')],
-							['3', TYPO3.l10n.localize('depth_3')],
-							['4', TYPO3.l10n.localize('depth_4')],
-							['999', TYPO3.l10n.localize('depth_infi')]
-						]
-					}),
-					value: TYPO3.settings.Recycler.depthSelection,
-					listeners: {
-						'select': {
-							fn: function(cmp, rec, index) {
-								var depth = rec.get('depth');
-								Recycler.MainStore.setBaseParam('depth', depth);
-								Recycler.MainStore.load({
-									params: {
-										start: 0
-									}
-								});
-
-								Ext.getCmp('tableSelector').store.load({
-									params: {
-										depth: depth
-									}
-								});
-							}
-						}
-					}
-				},'-',{
-					xtype: 'tbtext',
-					text: TYPO3.l10n.localize('tableMenu_label')
-				},{
-
-					/****************************************************
-					 * Table menu
-					 ****************************************************/
-
-					xtype: 'combo',
-					lazyRender: true,
-					stateful: true,
-					stateId: 'tableCombo',
-					stateEvents: ['select'],
-					valueField: 'valueField',
-					displayField: 'tableTitle',
-					id: 'tableSelector',
-					width: 220,
-					mode: 'local',
-					emptyText: TYPO3.l10n.localize('tableMenu_emptyText'),
-					selectOnFocus: true,
-					triggerAction: 'all',
-					editable: false,
-					forceSelection: true,
-
-					store: Recycler.TableStore,
-					valueNotFoundText: String.format(TYPO3.l10n.localize('noValueFound'), TYPO3.settings.Recycler.tableSelection),
-					tpl: '<tpl for="."><tpl if="records &gt; 0"><div ext:qtip="{table} ({records})" class="x-combo-list-item">{tableTitle} ({records}) </div></tpl><tpl if="records &lt; 1"><div ext:qtip="{table} ({records})" class="x-combo-list-item x-item-disabled">{tableTitle} ({records}) </div></tpl></tpl>',
-					listeners: {
-						'select': {
-							fn: function(component, record, index) {
-								var table = record.get('valueField');
-
-								// do not reload if the table selected has no deleted records - hide all records
-								if (record.get('records') <= 0) {
-									Recycler.MainStore.filter('uid', '-1'); // never true
-									return false;
-								}
-								Recycler.MainStore.setBaseParam('table', table);
-								Recycler.MainStore.load({
-									params: {
-										start: 0
-									}
-								});
-							}
-						}
-					}
-				}
-			]
-		});
-		Recycler.GridContainer.superclass.initComponent.apply(this, arguments);
-		Recycler.TableStore.load();
-	}
-});
-
-Recycler.App = {
-	/**
-	 * Initializes the recycler
-	 *
-	 * @return void
-	 **/
-	init: function() {
-		Recycler.Grid = new Recycler.GridContainer();
-		Recycler.MainStore.load();
-	}
-};
-
-Ext.onReady(function(){
-
-		//save states in BE_USER->uc
-	Ext.state.Manager.setProvider(new TYPO3.state.ExtDirectProvider({
-		key: 'moduleData.web_recycler.States'
-	}));
-
-	if (Ext.isObject(TYPO3.settings.Recycler.States)) {
-		Ext.state.Manager.getProvider().initState(TYPO3.settings.Recycler.States);
-	}
-
-	// disable loadindicator
-	Ext.UpdateManager.defaults.showLoadIndicator = false;
-	// fire recycler grid
-	Recycler.App.init();
-});
diff --git a/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less b/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less
index 86e7b5c4b00effd1af1bc26b7c2863faae329b6a..8ea76f655057bb42876e0f3fefdafb3d9eef3e1e 100644
--- a/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less
+++ b/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less
@@ -130,6 +130,10 @@ table {
 		position: relative;
 		display: inline-block;
 
+		&.btn-group {
+			float: none;
+		}
+
 		.btn-checkbox {
 			position: absolute;
 			top: 0;
diff --git a/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css b/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css
index 091e70293266157c5483ca03f1119e0722e4cecf..f83ac13c5029846ff623d70b0a79a198767491b5 100644
--- a/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css
+++ b/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css
@@ -8808,6 +8808,9 @@ fieldset[disabled] .table .btn.btn-primary.active {
   position: relative;
   display: inline-block;
 }
+.table .btn-checkbox-holder.btn-group {
+  float: none;
+}
 .table .btn-checkbox-holder .btn-checkbox {
   position: absolute;
   top: 0;