[TASK] Decouple usage of cObject->editPanel and ->editIcons 30/58030/2
authorBenni Mack <benni@typo3.org>
Fri, 24 Aug 2018 16:22:09 +0000 (18:22 +0200)
committerSusanne Moog <susanne.moog@typo3.org>
Tue, 28 Aug 2018 12:04:49 +0000 (14:04 +0200)
Both methods within "ContentObjectRenderer->editPanel()" and
"ContentObjectRenderer->editIcons()" were built to have "FrontendEditingController"
resolve the editing panel (EXT:feedit) which is not needed, so ContentObjectRenderer
is calling the extensions itself.

For that use, the FrontendBackendUserAuthentication gains functionality
for checking if a record can be edited.

Resolves: #85972
Releases: master
Change-Id: Ic8405fde4cdf5b6d1336fd0925cd0553bae6cf5f
Reviewed-on: https://review.typo3.org/58030
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Daniel Gorges <daniel.gorges@b13.de>
Tested-by: Daniel Gorges <daniel.gorges@b13.de>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
typo3/sysext/backend/Classes/FrontendBackendUserAuthentication.php
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php

index 407c50d..9e47fbb 100644 (file)
@@ -14,8 +14,11 @@ namespace TYPO3\CMS\Backend;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
@@ -266,6 +269,122 @@ class FrontendBackendUserAuthentication extends BackendUserAuthentication
         return $theList;
     }
 
+    /**
+     * Edit Access
+     */
+
+    /**
+     * Checks whether the user has access to edit the language for the
+     * requested record.
+     *
+     * @param string $table The name of the table.
+     * @param array $currentRecord The record.
+     * @return bool
+     */
+    public function allowedToEditLanguage($table, array $currentRecord): bool
+    {
+        // If no access right to record languages, return immediately
+        /** @var LanguageAspect $languageAspect */
+        $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
+        if ($table === 'pages') {
+            $languageId = $languageAspect->getId();
+        } elseif ($table === 'tt_content') {
+            $languageId = $languageAspect->getContentId();
+        } elseif ($GLOBALS['TCA'][$table]['ctrl']['languageField']) {
+            $languageId = $currentRecord[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
+        } else {
+            $languageId = -1;
+        }
+        return $this->checkLanguageAccess($languageId);
+    }
+
+    /**
+     * Checks whether the user is allowed to edit the requested table.
+     *
+     * @param string $table The name of the table.
+     * @param array $dataArray The data array.
+     * @param array $conf The configuration array for the edit panel.
+     * @param bool $checkEditAccessInternals Boolean indicating whether recordEditAccessInternals should not be checked. Defaults
+     * @return bool
+     */
+    public function allowedToEdit(string $table, array $dataArray, array $conf, bool $checkEditAccessInternals): bool
+    {
+        // Unless permissions specifically allow it, editing is not allowed.
+        $mayEdit = false;
+        if ($checkEditAccessInternals) {
+            $editAccessInternals = $this->recordEditAccessInternals($table, $dataArray, false, false);
+        } else {
+            $editAccessInternals = true;
+        }
+        if ($editAccessInternals) {
+            if ($table === 'pages') {
+                if ($this->isAdmin() || $this->doesUserHaveAccess($dataArray, Permission::PAGE_EDIT)) {
+                    $mayEdit = true;
+                }
+            } else {
+                if ($this->isAdmin() || $this->doesUserHaveAccess(BackendUtility::getRecord('pages', $dataArray['pid']), Permission::CONTENT_EDIT)) {
+                    $mayEdit = true;
+                }
+            }
+            if (!$conf['onlyCurrentPid'] || $dataArray['pid'] == $GLOBALS['TSFE']->id) {
+                // Permissions:
+                $perms = $this->calcPerms($GLOBALS['TSFE']->page);
+                if ($table === 'pages') {
+                    $allow = $this->getAllowedEditActions($table, $conf, $dataArray['pid']);
+                    // Can only display editbox if there are options in the menu
+                    if (!empty($allow)) {
+                        $mayEdit = true;
+                    }
+                } else {
+                    $types = GeneralUtility::trimExplode(',', strtolower($conf['allow']), true);
+                    $allow = array_flip($types);
+                    $mayEdit = !empty($allow) && $perms & Permission::CONTENT_EDIT;
+                }
+            }
+        }
+        return $mayEdit;
+    }
+
+    /**
+     * Takes an array of generally allowed actions and filters that list based on page and content permissions.
+     *
+     * @param string $table The name of the table.
+     * @param array $conf The configuration array.
+     * @param int $pid The PID where editing will occur.
+     * @return array
+     */
+    public function getAllowedEditActions($table, array $conf, $pid): array
+    {
+        $types = GeneralUtility::trimExplode(',', strtolower($conf['allow']), true);
+        $allow = array_flip($types);
+        if (!$conf['onlyCurrentPid'] || $pid == $GLOBALS['TSFE']->id) {
+            // Permissions
+            $types = GeneralUtility::trimExplode(',', strtolower($conf['allow']), true);
+            $allow = array_flip($types);
+            $perms = $this->calcPerms($GLOBALS['TSFE']->page);
+            if ($table === 'pages') {
+                // Rootpage
+                if (count($GLOBALS['TSFE']->config['rootLine']) === 1) {
+                    unset($allow['move']);
+                    unset($allow['hide']);
+                    unset($allow['delete']);
+                }
+                if (!($perms & Permission::PAGE_EDIT) || !$this->checkLanguageAccess(0)) {
+                    unset($allow['edit']);
+                    unset($allow['move']);
+                    unset($allow['hide']);
+                }
+                if (!($perms & Permission::PAGE_DELETE)) {
+                    unset($allow['delete']);
+                }
+                if (!($perms & Permission::PAGE_NEW)) {
+                    unset($allow['new']);
+                }
+            }
+        }
+        return $allow;
+    }
+
     /*****************************************************
      *
      * Localization handling
index be6346c..eb4f562 100644 (file)
@@ -28,7 +28,6 @@ use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
-use TYPO3\CMS\Core\FrontendEditing\FrontendEditingController;
 use TYPO3\CMS\Core\Html\HtmlParser;
 use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
 use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
@@ -7146,20 +7145,57 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $content A content string containing the content related to the edit panel. For cObject "EDITPANEL" this is empty but not so for the stdWrap property. The edit panel is appended to this string and returned.
      * @param array $conf TypoScript configuration properties for the editPanel
      * @param string $currentRecord The "table:uid" of the record being shown. If empty string then $this->currentRecord is used. For new records (set by $conf['newRecordFromTable']) it's auto-generated to "[tablename]:NEW
-     * @param array $dataArr Alternative data array to use. Default is $this->data
+     * @param array $dataArray Alternative data array to use. Default is $this->data
      * @return string The input content string with the editPanel appended. This function returns only an edit panel appended to the content string if a backend user is logged in (and has the correct permissions). Otherwise the content string is directly returned.
      */
-    public function editPanel($content, $conf, $currentRecord = '', $dataArr = [])
+    public function editPanel($content, $conf, $currentRecord = '', $dataArray = [])
     {
-        if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
-            if (!$currentRecord) {
-                $currentRecord = $this->currentRecord;
+        if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
+            return $content;
+        }
+        if (!$this->getTypoScriptFrontendController()->displayEditIcons) {
+            return $content;
+        }
+
+        if (!$currentRecord) {
+            $currentRecord = $this->currentRecord;
+        }
+        if (empty($dataArray)) {
+            $dataArray = $this->data;
+        }
+
+        if ($conf['newRecordFromTable']) {
+            $currentRecord = $conf['newRecordFromTable'] . ':NEW';
+            $conf['allow'] = 'new';
+            $checkEditAccessInternals = false;
+        } else {
+            $checkEditAccessInternals = true;
+        }
+        list($table, $uid) = explode(':', $currentRecord);
+        // Page ID for new records, 0 if not specified
+        $newRecordPid = (int)$conf['newRecordInPid'];
+        $newUid = null;
+        if (!$conf['onlyCurrentPid'] || $dataArray['pid'] == $this->getTypoScriptFrontendController()->id) {
+            if ($table === 'pages') {
+                $newUid = $uid;
+            } else {
+                if ($conf['newRecordFromTable']) {
+                    $newUid = $this->getTypoScriptFrontendController()->id;
+                    if ($newRecordPid) {
+                        $newUid = $newRecordPid;
+                    }
+                } else {
+                    $newUid = -1 * $uid;
+                }
             }
-            if (empty($dataArr)) {
-                $dataArr = $this->data;
+        }
+        if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, $checkEditAccessInternals) && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) {
+            $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
+            if ($editClass) {
+                $edit = GeneralUtility::makeInstance($editClass);
+                $allowedActions = $this->getFrontendBackendUser()->getAllowedEditActions($table, $conf, $dataArray['pid']);
+                $content = $edit->editPanel($content, $conf, $currentRecord, $dataArray, $table, $allowedActions, $newUid, []);
             }
-            // Delegate rendering of the edit panel to the frontend edit
-            $content = $this->getFrontendBackendUser()->frontendEdit->displayEditPanel($content, $conf, $currentRecord, $dataArr);
         }
         return $content;
     }
@@ -7172,21 +7208,46 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $params The parameters defining which table and fields to edit. Syntax is [tablename]:[fieldname],[fieldname],[fieldname],... OR [fieldname],[fieldname],[fieldname],... (basically "[tablename]:" is optional, default table is the one of the "current record" used in the function). The fieldlist is sent as "&columnsOnly=" parameter to FormEngine
      * @param array $conf TypoScript properties for configuring the edit icons.
      * @param string $currentRecord The "table:uid" of the record being shown. If empty string then $this->currentRecord is used. For new records (set by $conf['newRecordFromTable']) it's auto-generated to "[tablename]:NEW
-     * @param array $dataArr Alternative data array to use. Default is $this->data
+     * @param array $dataArray Alternative data array to use. Default is $this->data
      * @param string $addUrlParamStr Additional URL parameters for the link pointing to FormEngine
      * @return string The input content string, possibly with edit icons added (not necessarily in the end but just after the last string of normal content.
      */
-    public function editIcons($content, $params, array $conf = [], $currentRecord = '', $dataArr = [], $addUrlParamStr = '')
+    public function editIcons($content, $params, array $conf = [], $currentRecord = '', $dataArray = [], $addUrlParamStr = '')
     {
-        if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
-            if (!$currentRecord) {
-                $currentRecord = $this->currentRecord;
-            }
-            if (empty($dataArr)) {
-                $dataArr = $this->data;
+        if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
+            return $content;
+        }
+        if (!$this->getTypoScriptFrontendController()->displayFieldEditIcons) {
+            return $content;
+        }
+        if (!$currentRecord) {
+            $currentRecord = $this->currentRecord;
+        }
+        if (empty($dataArray)) {
+            $dataArray = $this->data;
+        }
+        // Check incoming params:
+        list($currentRecordTable, $currentRecordUID) = explode(':', $currentRecord);
+        list($fieldList, $table) = array_reverse(GeneralUtility::trimExplode(':', $params, true));
+        // Reverse the array because table is optional
+        if (!$table) {
+            $table = $currentRecordTable;
+        } elseif ($table != $currentRecordTable) {
+            // If the table is set as the first parameter, and does not match the table of the current record, then just return.
+            return $content;
+        }
+
+        $editUid = $dataArray['_LOCALIZED_UID'] ?: $currentRecordUID;
+        // Edit icons imply that the editing action is generally allowed, assuming page and content element permissions permit it.
+        if (!array_key_exists('allow', $conf)) {
+            $conf['allow'] = 'edit';
+        }
+        if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, true) && $fieldList && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) {
+            $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
+            if ($editClass) {
+                $edit = GeneralUtility::makeInstance($editClass);
+                $content = $edit->editIcons($content, $params, $conf, $currentRecord, $dataArray, $addUrlParamStr, $table, $editUid, $fieldList);
             }
-            // Delegate rendering of the edit panel to frontend edit class.
-            $content = $this->getFrontendBackendUser()->frontendEdit->displayEditIcons($content, $params, $conf, $currentRecord, $dataArr, $addUrlParamStr);
         }
         return $content;
     }