├── .gitignore ├── Classes ├── Cache │ ├── CacheFactory.php │ ├── CacheInterface.php │ ├── DatabaseCache.php │ ├── PathCacheEntry.php │ └── UrlCacheEntry.php ├── Configuration │ ├── AutomaticConfigurator.php │ └── ConfigurationReader.php ├── Controller │ ├── AliasesController.php │ ├── BackendModuleController.php │ ├── OverviewController.php │ ├── PathCacheController.php │ └── UrlCacheController.php ├── Decoder │ └── UrlDecoder.php ├── Domain │ ├── Model │ │ ├── Alias.php │ │ ├── PathCacheEntry.php │ │ └── UrlCacheEntry.php │ └── Repository │ │ ├── AbstractRepository.php │ │ ├── AliasRepository.php │ │ ├── PathCacheEntryRepository.php │ │ └── UrlCacheEntryRepository.php ├── EncodeDecoderBase.php ├── Encoder │ └── UrlEncoder.php ├── Evaluator │ └── SegmentFieldCleaner.php ├── Exceptions │ └── InvalidLanguageParameterException.php ├── Hooks │ └── DataHandler.php ├── Utility.php └── ViewHelpers │ ├── LanguageFromIdViewHelper.php │ ├── SetVariableViewHelper.php │ └── TranslateToJsonViewHelper.php ├── Configuration ├── TCA │ ├── Overrides │ │ ├── pages.php │ │ └── pages_language_overlay.php │ ├── tx_realurl_pathdata.php │ ├── tx_realurl_uniqalias.php │ └── tx_realurl_urldata.php └── TSConfig.txt ├── README.md ├── Resources ├── Private │ ├── Language │ │ ├── locallang.xlf │ │ └── locallang_db.xlf │ ├── Layouts │ │ └── Backend.html │ └── Templates │ │ ├── Aliases │ │ ├── Edit.html │ │ └── Index.html │ │ ├── Overview │ │ └── Index.html │ │ ├── PathCache │ │ └── Index.html │ │ └── UrlCache │ │ └── Index.html └── Public │ ├── Icons │ ├── Extension.svg │ ├── Module.svg │ ├── Module2.svg │ └── Module3.svg │ ├── realurl_be.css │ └── realurl_be.js ├── class.ext_update.php ├── composer.json ├── ext_conf_template.txt ├── ext_emconf.php ├── ext_icon.gif ├── ext_localconf.php ├── ext_tables.php ├── ext_tables.sql └── ext_typoscript_setup.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .Build/* 2 | vendor/* 3 | composer.lock -------------------------------------------------------------------------------- /Classes/Cache/CacheFactory.php: -------------------------------------------------------------------------------- 1 | cacheId = ''; 65 | } 66 | 67 | /** 68 | * @return string 69 | */ 70 | public function getCacheId() { 71 | return $this->cacheId; 72 | } 73 | 74 | /** 75 | * @return int 76 | */ 77 | public function getExpiration() { 78 | return $this->expiration; 79 | } 80 | 81 | /** 82 | * @return int 83 | */ 84 | public function getLanguageId() { 85 | return $this->languageId; 86 | } 87 | 88 | /** 89 | * @return string 90 | */ 91 | public function getMountPoint() { 92 | return $this->mountPoint; 93 | } 94 | 95 | /** 96 | * @return int 97 | */ 98 | public function getPageId() { 99 | return $this->pageId; 100 | } 101 | 102 | /** 103 | * @return string 104 | */ 105 | public function getPagePath() { 106 | return $this->pagePath; 107 | } 108 | 109 | /** 110 | * @return int 111 | */ 112 | public function getRootPageId() { 113 | return $this->rootPageId; 114 | } 115 | 116 | /** 117 | * @param string $cacheId 118 | */ 119 | public function setCacheId($cacheId) { 120 | $this->cacheId = $cacheId; 121 | } 122 | 123 | /** 124 | * @param int $expiration 125 | */ 126 | public function setExpiration($expiration) { 127 | $this->expiration = $expiration; 128 | } 129 | 130 | /** 131 | * @param int $languageId 132 | */ 133 | public function setLanguageId($languageId) { 134 | $this->languageId = $languageId; 135 | } 136 | 137 | /** 138 | * @param string $mountPoint 139 | */ 140 | public function setMountPoint($mountPoint) { 141 | $this->mountPoint = $mountPoint; 142 | } 143 | 144 | /** 145 | * @param int $pageId 146 | */ 147 | public function setPageId($pageId) { 148 | $this->pageId = $pageId; 149 | } 150 | 151 | /** 152 | * @param string $pagePath 153 | */ 154 | public function setPagePath($pagePath) { 155 | $this->pagePath = $pagePath; 156 | } 157 | 158 | /** 159 | * @param int $rootPageId 160 | */ 161 | public function setRootPageId($rootPageId) { 162 | $this->rootPageId = $rootPageId; 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /Classes/Cache/UrlCacheEntry.php: -------------------------------------------------------------------------------- 1 | cacheId; 66 | } 67 | 68 | /** 69 | * @return int 70 | */ 71 | public function getExpiration() { 72 | return (int)$this->expiration; 73 | } 74 | 75 | /** 76 | * @return string 77 | */ 78 | public function getOriginalUrl() { 79 | return $this->originalUrl; 80 | } 81 | 82 | /** 83 | * @return int 84 | */ 85 | public function getPageId() { 86 | return $this->pageId; 87 | } 88 | 89 | /** 90 | * @return array 91 | */ 92 | public function getRequestVariables() { 93 | return $this->requestVariables; 94 | } 95 | 96 | /** 97 | * @return int 98 | */ 99 | public function getRootPageId() { 100 | return $this->rootPageId; 101 | } 102 | 103 | /** 104 | * @return string 105 | */ 106 | public function getSpeakingUrl() { 107 | return $this->speakingUrl; 108 | } 109 | 110 | /** 111 | * @param string $cacheId 112 | */ 113 | public function setCacheId($cacheId) { 114 | $this->cacheId = $cacheId; 115 | } 116 | 117 | /** 118 | * @param int $expiration 119 | */ 120 | public function setExpiration($expiration) { 121 | $this->expiration = $expiration; 122 | } 123 | 124 | /** 125 | * @param string $originalUrl 126 | */ 127 | public function setOriginalUrl($originalUrl) { 128 | $this->originalUrl = $originalUrl; 129 | } 130 | 131 | /** 132 | * @param int $pageId 133 | */ 134 | public function setPageId($pageId) { 135 | $this->pageId = $pageId; 136 | } 137 | 138 | /** 139 | * @param array $requestVariables 140 | */ 141 | public function setRequestVariables(array $requestVariables) { 142 | $this->requestVariables = $requestVariables; 143 | } 144 | 145 | /** 146 | * @param int $rootPageId 147 | */ 148 | public function setRootPageId($rootPageId) { 149 | $this->rootPageId = $rootPageId; 150 | } 151 | 152 | /** 153 | * @param string $speakingUrl 154 | */ 155 | public function setSpeakingUrl($speakingUrl) { 156 | $this->speakingUrl = $speakingUrl; 157 | } 158 | } -------------------------------------------------------------------------------- /Classes/Configuration/AutomaticConfigurator.php: -------------------------------------------------------------------------------- 1 | databaseConnection = $GLOBALS['TYPO3_DB']; 48 | $this->hasStaticInfoTables = ExtensionManagementUtility::isLoaded('static_info_tables'); 49 | } 50 | 51 | /** 52 | * Configures RealURL. 53 | * 54 | * @return void 55 | */ 56 | public function configure() { 57 | $lockId = 'realurl_autoconfiguration'; 58 | if (version_compare(TYPO3_branch, '7.2', '<')) { 59 | $lock = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Locking\\Locker', $lockId); 60 | /** @var \TYPO3\CMS\Core\Locking\Locker $lock */ 61 | $lock->setEnableLogging(FALSE); 62 | $lock->acquireExclusiveLock(); 63 | } else { 64 | $lockFactory = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Locking\\LockFactory'); 65 | /** @var \TYPO3\CMS\Core\Locking\LockFactory $lockFactory */ 66 | $lock = $lockFactory->createLocker($lockId); 67 | $lock->acquire(); 68 | } 69 | 70 | if (!file_exists(PATH_site . TX_REALURL_AUTOCONF_FILE)) { 71 | $this->runConfigurator(); 72 | } 73 | $lock->release(); 74 | } 75 | 76 | /** 77 | * Adds languages to configuration 78 | * 79 | * @param array $configuration 80 | * @return void 81 | */ 82 | protected function addLanguages(array &$configuration) { 83 | if (version_compare(TYPO3_branch, '7.6', '>=')) { 84 | $languages = $this->databaseConnection->exec_SELECTgetRows('t1.uid AS uid,t1.language_isocode AS lg_iso_2', 'sys_language t1', 't1.hidden=0 AND t1.language_isocode<>\'\''); 85 | } 86 | elseif ($this->hasStaticInfoTables) { 87 | $languages = $this->databaseConnection->exec_SELECTgetRows('t1.uid AS uid,t2.lg_iso_2 AS lg_iso_2', 'sys_language t1, static_languages t2', 't2.uid=t1.static_lang_isocode AND t1.hidden=0 AND t2.lg_iso_2<>\'\''); 88 | } 89 | else { 90 | $languages = array(); 91 | } 92 | if (count($languages) > 0) { 93 | $configuration['preVars'] = array( 94 | 0 => array( 95 | 'GETvar' => 'L', 96 | 'valueMap' => array( 97 | ), 98 | 'noMatch' => 'bypass' 99 | ), 100 | ); 101 | foreach ($languages as $lang) { 102 | $configuration['preVars'][0]['valueMap'][strtolower($lang['lg_iso_2'])] = $lang['uid']; 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * Creates configuration for the installation without domain records. 109 | * 110 | * @param array $template 111 | * @return array 112 | */ 113 | protected function createConfigurationWithoutDomains(array $template) { 114 | $configuration = array( 115 | '_DEFAULT' => $template 116 | ); 117 | $row = $this->databaseConnection->exec_SELECTgetSingleRow('uid', 'pages', 118 | 'deleted=0 AND hidden=0 AND is_siteroot=1' 119 | ); 120 | if (is_array($row) > 0) { 121 | $configuration['_DEFAULT']['pagePath']['rootpage_id'] = $row['uid']; 122 | } 123 | 124 | return $configuration; 125 | } 126 | 127 | /** 128 | * Creates configuration for the given list of domains. 129 | * 130 | * @param array $domains 131 | * @param array $template 132 | * @return array 133 | */ 134 | protected function createConfigurationForDomains(array $domains, array $template) { 135 | $configuration = array(); 136 | foreach ($domains as $domain) { 137 | if ($domain['redirectTo'] != '') { 138 | // Redirects to another domain, see if we can make a shortcut 139 | $parts = parse_url($domain['redirectTo']); 140 | if (isset($domains[$parts['host']]) && ($domains['path'] == '/' || $domains['path'] == '')) { 141 | // Make a shortcut 142 | if ($configuration[$parts['host']] != $domain['domainName']) { 143 | // Here if there were no redirect from this domain to source domain 144 | $configuration[$domain['domainName']] = $parts['host']; 145 | } 146 | continue; 147 | } 148 | } 149 | // Make entry 150 | $configuration[$domain['domainName']] = $template; 151 | $configuration[$domain['domainName']]['pagePath']['rootpage_id'] = $domain['pid']; 152 | } 153 | 154 | return $configuration; 155 | } 156 | 157 | /** 158 | * Obtains a list of domains. 159 | * 160 | * @return array 161 | */ 162 | private function getDomains() { 163 | return $this->databaseConnection->exec_SELECTgetRows('pid,domainName,redirectTo', 'sys_domain', 'hidden=0', 164 | '', '', '', 'domainName' 165 | ); 166 | } 167 | 168 | /** 169 | * Creates common configuration template. 170 | * 171 | * @return array Template 172 | */ 173 | protected function getTemplate() { 174 | $confTemplate = array( 175 | 'init' => array( 176 | 'appendMissingSlash' => 'ifNotFile,redirect', 177 | 'emptyUrlReturnValue' => GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') 178 | ), 179 | 'pagePath' => array( 180 | ), 181 | 'fileName' => array( 182 | 'defaultToHTMLsuffixOnPrev' => 0, 183 | 'acceptHTMLsuffix' => 1, 184 | ) 185 | ); 186 | 187 | // Add print feature if TemplaVoila is not loaded 188 | $confTemplate['fileName']['index']['print'] = array( 189 | 'keyValues' => array( 190 | 'type' => 98, 191 | ) 192 | ); 193 | 194 | $this->addLanguages($confTemplate); 195 | 196 | // Add from extensions 197 | if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/realurl/class.tx_realurl_autoconfgen.php']['extensionConfiguration'])) { 198 | foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/realurl/class.tx_realurl_autoconfgen.php']['extensionConfiguration'] as $extKey => $userFunc) { 199 | $previousExceptionHandler = set_error_handler(function(/** @noinspection PhpUnusedParameterInspection */ $errno, $errstr) use ($userFunc) { 200 | $message = 'Error while calling "' . $userFunc . '" for realurl automatic configuration. Error message: ' . $errstr; 201 | error_log($message); 202 | }, E_RECOVERABLE_ERROR); 203 | 204 | $params = array( 205 | 'config' => $confTemplate, 206 | 'extKey' => $extKey 207 | ); 208 | $var = GeneralUtility::callUserFunction($userFunc, $params, $this); 209 | if ($var) { 210 | $confTemplate = $var; 211 | } 212 | 213 | set_error_handler($previousExceptionHandler, E_RECOVERABLE_ERROR); 214 | } 215 | } 216 | 217 | return $confTemplate; 218 | } 219 | 220 | /** 221 | * Runs a postprocessing hook for extensions. 222 | * 223 | * @param array $configuration 224 | * @return void 225 | */ 226 | protected function postProcessConfiguration(array &$configuration) { 227 | if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/realurl/class.tx_realurl_autoconfgen.php']['postProcessConfiguration'])) { 228 | foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/realurl/class.tx_realurl_autoconfgen.php']['postProcessConfiguration'] as $userFunc) { 229 | $previousExceptionHandler = set_error_handler(function(/** @noinspection PhpUnusedParameterInspection */ $errno, $errstr) use ($userFunc) { 230 | $message = 'Error while calling "' . $userFunc . '" for post processing realurl configuration. Error message: ' . $errstr; 231 | error_log($message); 232 | }, E_RECOVERABLE_ERROR); 233 | 234 | $parameters = array( 235 | 'config' => &$configuration, 236 | ); 237 | GeneralUtility::callUserFunction($userFunc, $parameters, $this); 238 | 239 | set_error_handler($previousExceptionHandler, E_RECOVERABLE_ERROR); 240 | } 241 | } 242 | } 243 | 244 | /** 245 | * Does the actual configuration. 246 | * 247 | * @return void 248 | */ 249 | protected function runConfigurator() { 250 | $template = $this->getTemplate(); 251 | 252 | $domains = $this->getDomains(); 253 | if (count($domains) == 0) { 254 | $configuration = $this->createConfigurationWithoutDomains($template); 255 | } else { 256 | $configuration = $this->createConfigurationForDomains($domains, $template); 257 | } 258 | 259 | $this->postProcessConfiguration($configuration); 260 | 261 | $this->saveConfiguration($configuration); 262 | } 263 | 264 | /** 265 | * Saves the configuration. 266 | * 267 | * @param array $configuration 268 | * @return void 269 | */ 270 | protected function saveConfiguration(array $configuration) { 271 | $extensionConfiguration = @unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['realurl']); 272 | 273 | $fileName = PATH_site . TX_REALURL_AUTOCONF_FILE; 274 | if ($extensionConfiguration['autoConfFormat'] == 0) { 275 | file_put_contents($fileName, '<' . '?php' . chr(10) . '$GLOBALS[\'TYPO3_CONF_VARS\'][\'EXTCONF\'][\'realurl\']=' . 276 | 'unserialize(\'' . str_replace('\'', '\\\'', serialize($configuration)) . '\');' . chr(10) 277 | ); 278 | } else { 279 | file_put_contents($fileName, '<' . '?php' . chr(10) . '$GLOBALS[\'TYPO3_CONF_VARS\'][\'EXTCONF\'][\'realurl\']=' . 280 | var_export($configuration, TRUE) . ';' . chr(10) 281 | ); 282 | } 283 | GeneralUtility::fixPermissions($fileName); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /Classes/Controller/AliasesController.php: -------------------------------------------------------------------------------- 1 | 39 | */ 40 | class AliasesController extends BackendModuleController { 41 | 42 | /** @var string[] */ 43 | protected $excludedArgments = array('uid', 'valueAlias', 'submit'); 44 | 45 | /** 46 | * @var \DmitryDulepov\Realurl\Domain\Repository\AliasRepository 47 | * @inject 48 | */ 49 | protected $repository; 50 | 51 | /** @var string */ 52 | static protected $validAliasCharacters = 'abcdefghijklmnopqrstuvwxyz0123456789-'; 53 | 54 | /** 55 | * Deletes the selected alias. 56 | * 57 | * @param int $uid 58 | * @param string $selectedAlias 59 | */ 60 | public function deleteAction($uid, $selectedAlias) { 61 | $this->databaseConnection->sql_query('START TRANSACTION'); 62 | $this->databaseConnection->exec_DELETEquery('tx_realurl_uniqalias', 63 | 'tablename=' . $this->databaseConnection->fullQuoteStr($selectedAlias, 'tx_realurl_uniqalias') . 64 | ' AND uid=' . (int)$uid 65 | ); 66 | $this->databaseConnection->exec_DELETEquery('tx_realurl_uniqalias_cache_map', 67 | 'alias_uid=' . (int)$uid 68 | ); 69 | $this->databaseConnection->sql_query('COMMIT'); 70 | $this->addFlashMessage(LocalizationUtility::translate('module.aliases.deleted', 'realurl')); 71 | $this->forward('index', null, null, $this->makeArgumentArray()); 72 | } 73 | 74 | /** 75 | * Deletes all aliases of the given kind. 76 | * 77 | * @param string $selectedAlias 78 | */ 79 | public function deleteAllAction($selectedAlias) { 80 | $this->databaseConnection->sql_query('START TRANSACTION'); 81 | $this->databaseConnection->exec_DELETEquery('tx_realurl_uniqalias_cache_map', 82 | 'alias_uid IN (SELECT uid FROM tx_realurl_uniqalias WHERE ' . 83 | 'tablename=' . $this->databaseConnection->fullQuoteStr($selectedAlias, 'tx_realurl_uniqalias_cache_map') . 84 | ')' 85 | ); 86 | $this->databaseConnection->exec_DELETEquery('tx_realurl_uniqalias', 87 | 'tablename=' . $this->databaseConnection->fullQuoteStr($selectedAlias, 'tx_realurl_uniqalias') 88 | ); 89 | $this->databaseConnection->sql_query('COMMIT'); 90 | $this->addFlashMessage(LocalizationUtility::translate('module.aliases.all_deleted', 'realurl')); 91 | $this->forward('index'); 92 | } 93 | 94 | /** 95 | * Shows the edit form for the selected alias. 96 | * 97 | * @param int $uid 98 | * @param string $selectedAlias 99 | */ 100 | public function editAction($uid, $selectedAlias) { 101 | if ($this->request->hasArgument('submit')) { 102 | if ($this->processEditSubmission()) { 103 | $_GET['tx_realurl_web_realurlrealurl'] = array( 104 | 'controller' => $_GET['tx_realurl_web_realurlrealurl']['controller'], 105 | ); 106 | $this->forward('index', null, null, $this->makeArgumentArray()); 107 | } 108 | } 109 | 110 | $alias = $this->repository->findByUid((int)$uid); 111 | /** @var \DmitryDulepov\Realurl\Domain\Model\Alias $alias */ 112 | if (!$alias) { 113 | $this->addFlashMessage(LocalizationUtility::translate('module.aliases.not_found', 'realurl')); 114 | $this->forward('index', null, null, $this->makeArgumentArray()); 115 | } 116 | 117 | if ($this->request->hasArgument('valueAlias')) { 118 | $alias->setValueAlias($this->request->getArgument('valueAlias')); 119 | } 120 | 121 | $this->view->assignMultiple(array( 122 | 'alias' => $alias, 123 | 'selectedAlias' => $selectedAlias, 124 | )); 125 | } 126 | 127 | /** 128 | * Performs alias management functions. 129 | * 130 | * @param string $selectedAlias 131 | */ 132 | public function indexAction($selectedAlias = '') { 133 | $availableAliasTypes = $this->getAvailableAliasTypes(); 134 | $this->view->assignMultiple(array( 135 | 'availableAliasTypes' => $availableAliasTypes, 136 | 'searchAlias' => $this->request->hasArgument('searchAlias') ? $this->request->getArgument('searchAlias') : '', 137 | 'selectedAlias' => $selectedAlias, 138 | )); 139 | 140 | if ($selectedAlias && isset($availableAliasTypes[$selectedAlias])) { 141 | $this->processSelectedAlias($selectedAlias); 142 | } 143 | } 144 | 145 | /** 146 | * Checks if this alias already exists for another record. 147 | * 148 | * @param string $aliasValue 149 | * @return bool 150 | * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException 151 | */ 152 | protected function doesAliasExistsForAnotherRecord($aliasValue) { 153 | $result = false; 154 | 155 | $alias = $this->repository->findByUid((int)$this->request->getArgument('uid')); 156 | /** @var \DmitryDulepov\Realurl\Domain\Model\Alias $alias */ 157 | if ($alias) { 158 | $query = $this->repository->createQuery(); 159 | $query->matching($query->logicalAnd(array( 160 | $query->equals('valueAlias', $aliasValue), 161 | $query->equals('tablename', $alias->getTablename()), 162 | $query->equals('lang', $alias->getLang()), 163 | $query->logicalNot($query->equals('uid', (int)$alias->getUid())) 164 | ))); 165 | 166 | $result = $query->count() > 0; 167 | } 168 | 169 | return $result; 170 | } 171 | 172 | /** 173 | * Obtains a list of aliases. 174 | * 175 | * @return array 176 | */ 177 | protected function getAvailableAliasTypes() { 178 | $result = array(); 179 | 180 | $rows = $this->databaseConnection->exec_SELECTgetRows('DISTINCT tablename AS tablename', 'tx_realurl_uniqalias', ''); 181 | array_walk($rows, function($row) use (&$result) { 182 | $tableNameKey = $row['tablename']; 183 | $tableName = '<' . $tableNameKey . '>'; 184 | if (isset($GLOBALS['TCA'][$tableNameKey]['ctrl']['title'])) { 185 | if (substr($GLOBALS['TCA'][$tableNameKey]['ctrl']['title'], 0, 4) === 'LLL:') { 186 | $tableName = LocalizationUtility::translate($GLOBALS['TCA'][$tableNameKey]['ctrl']['title'], ''); 187 | } 188 | else { 189 | $tableName = $GLOBALS['TCA'][$tableNameKey]['ctrl']['title']; 190 | } 191 | } 192 | $result[$tableNameKey] = $tableName; 193 | }); 194 | 195 | asort($result); 196 | 197 | return $result; 198 | } 199 | 200 | /** 201 | * Checks if value alias is valid. 202 | * 203 | * @param $aliasValue 204 | * @return int 205 | */ 206 | protected function isValidAliasValue($aliasValue) { 207 | return preg_match('/^[' . preg_quote(self::$validAliasCharacters, '/') . ']+$/', $aliasValue); 208 | } 209 | 210 | /** 211 | * Processes edit form submission. Also adds flash messages if something is not right. 212 | * 213 | * @return bool 214 | */ 215 | protected function processEditSubmission() { 216 | $result = false; 217 | 218 | $aliasValue = $this->request->getArgument('valueAlias'); 219 | 220 | if (!$this->isValidAliasValue($aliasValue)) { 221 | $this->addFlashMessage(LocalizationUtility::translate('module.aliases.edit.error.bad_alias_value', 'realurl', array(self::$validAliasCharacters)), '', AbstractMessage::ERROR); 222 | } 223 | elseif ($this->doesAliasExistsForAnotherRecord($aliasValue)) { 224 | $this->addFlashMessage(LocalizationUtility::translate('module.aliases.edit.error.already_exists', 'realurl'), '', AbstractMessage::ERROR); 225 | } 226 | else { 227 | $alias = $this->repository->findByUid((int)$this->request->getArgument('uid')); 228 | /** @var \DmitryDulepov\Realurl\Domain\Model\Alias $alias */ 229 | if (!$alias) { 230 | $this->addFlashMessage(LocalizationUtility::translate('module.aliases.not_found', 'realurl'), '', AbstractMessage::ERROR); 231 | } 232 | else { 233 | if ($alias->getValueAlias() != $aliasValue) { 234 | $alias->setValueAlias($aliasValue); 235 | $this->repository->update($alias); 236 | } 237 | $this->addFlashMessage(LocalizationUtility::translate('module.aliases.edit.saved', 'realurl'), '', AbstractMessage::OK); 238 | $result = true; 239 | } 240 | } 241 | 242 | return $result; 243 | } 244 | 245 | /** 246 | * Shows editing interface for the selected aliases. 247 | * 248 | * @param string $selectedAlias 249 | */ 250 | protected function processSelectedAlias($selectedAlias) { 251 | $query = $this->repository->createQuery(); 252 | $conditons[] = $query->equals('tablename', $selectedAlias); 253 | if ($this->request->hasArgument('searchAlias')) { 254 | $searchString = $this->request->getArgument('searchAlias'); 255 | $searchString = $GLOBALS['TYPO3_DB']->escapeStrForLike($searchString, $selectedAlias); 256 | $conditons[] = $query->like('valueAlias', '%' . $searchString . '%'); 257 | } 258 | $query->matching(count($conditons) > 1 ? $query->logicalAnd($conditons) : reset($conditons)); 259 | $query->setOrderings(array( 260 | 'valueId' => QueryInterface::ORDER_ASCENDING, 261 | 'lang' => QueryInterface::ORDER_ASCENDING, 262 | )); 263 | 264 | $this->view->assign('foundAliases', $query->execute()); 265 | } 266 | 267 | /** 268 | * Creates argument array for 'forward' method. 269 | * 270 | * @return array 271 | */ 272 | protected function makeArgumentArray() { 273 | $arguments = array(); 274 | if ($this->request->hasArgument('selectedAlias')) { 275 | $arguments['selectedAlias'] = $this->request->getArgument('selectedAlias'); 276 | } 277 | if ($this->request->hasArgument('@widget_0')) { 278 | $arguments['@widget_0'] = $this->request->getArgument('@widget_0'); 279 | } 280 | 281 | return $arguments; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /Classes/Controller/BackendModuleController.php: -------------------------------------------------------------------------------- 1 | 41 | */ 42 | abstract class BackendModuleController extends ActionController { 43 | 44 | /** @var int */ 45 | protected $currentPageId = 0; 46 | 47 | /** @var \TYPO3\CMS\Core\Database\DatabaseConnection */ 48 | protected $databaseConnection; 49 | 50 | /** @var string[] */ 51 | protected $excludedArgments = array(); 52 | 53 | /** 54 | * Forwards the request to the last active action. 55 | * 56 | * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException 57 | */ 58 | protected function forwardToLastModule() { 59 | $moduleData = BackendUtility::getModuleData( 60 | array('controller' => ''), 61 | array(), 62 | 'tx_realurl_web_realurlrealurl' 63 | ); 64 | //Don't need to check if it is an array because getModuleData always returns an array. Only have to check if it's empty. 65 | if (!empty($moduleData)) { 66 | $currentController = $this->getControllerName(); 67 | if ($moduleData['controller'] !== '' && $moduleData['controller'] !== $currentController) { 68 | $this->redirect(null, $moduleData['controller']); 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Makes action name from the current action method name. 75 | * 76 | * @return string 77 | */ 78 | protected function getActionName() { 79 | return substr($this->actionMethodName, 0, -6); 80 | } 81 | 82 | /** 83 | * Makes controller name from the controller class name. 84 | * 85 | * @return string 86 | */ 87 | protected function getControllerName() { 88 | return (string)preg_replace('/^.*\\\([^\\\]+)Controller$/', '\1', get_class($this)); 89 | } 90 | 91 | /** 92 | * Adds code to the standard request processor for saving the last action. 93 | * 94 | * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request 95 | * @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response 96 | * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException 97 | */ 98 | public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response) { 99 | parent::processRequest($request, $response); 100 | 101 | // We are here ony if the action did not throw exceptions (==successful and not forwarded). Save the action. 102 | $this->storeLastModuleInformation(); 103 | } 104 | 105 | /** 106 | * Checks if the BE user has access to the given page. 107 | * 108 | * @param int $pageId 109 | * @return bool 110 | */ 111 | protected function doesBackendUserHaveAccessToPage($pageId) { 112 | $record = BackendUtility::getRecord('pages', $pageId); 113 | return (0 !== ($GLOBALS['BE_USER']->calcPerms($record) & Permission::PAGE_SHOW)); 114 | } 115 | 116 | /** 117 | * Initializes all actions. 118 | * 119 | * @return void 120 | */ 121 | protected function initializeAction() { 122 | Utility::checkAndPerformRequiredUpdates(); 123 | 124 | $this->currentPageId = (int)\TYPO3\CMS\Core\Utility\GeneralUtility::_GET('id'); 125 | $this->databaseConnection = $GLOBALS['TYPO3_DB']; 126 | 127 | // Fix pagers 128 | $arguments = GeneralUtility::_GPmerged('tx_realurl_web_realurlrealurl'); 129 | if ($arguments && is_array($arguments)) { 130 | foreach ($arguments as $argumentKey => $argumentValue) { 131 | if ($argumentValue) { 132 | if (!in_array($argumentKey, $this->excludedArgments)) { 133 | GeneralUtility::_GETset($argumentValue, 'tx_realurl_web_realurlrealurl|' . $argumentKey); 134 | } 135 | else { 136 | GeneralUtility::_GETset('', 'tx_realurl_web_realurlrealurl|' . $argumentKey); 137 | } 138 | } 139 | } 140 | } 141 | else { 142 | $this->forwardToLastModule(); 143 | } 144 | 145 | parent::initializeAction(); 146 | } 147 | 148 | /** 149 | * Stores information about the last action of the module. 150 | */ 151 | protected function storeLastModuleInformation() { 152 | // Probably should store also arguments (except pager?) 153 | BackendUtility::getModuleData( 154 | array('controller' => ''), 155 | array('controller' => $this->getControllerName()), 156 | 'tx_realurl_web_realurlrealurl' 157 | ); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Classes/Controller/OverviewController.php: -------------------------------------------------------------------------------- 1 | 36 | */ 37 | class OverviewController extends BackendModuleController { 38 | 39 | /** 40 | * Shows the overview of functions. 41 | */ 42 | public function indexAction() { 43 | $this->view->assignMultiple(array( 44 | 'isCompatibleCacheImplementation' => $this->isCompatibleCacheImplementation() 45 | )); 46 | } 47 | 48 | /** 49 | * Checks if cache implementation is compatible with this module. 50 | * 51 | * @return bool 52 | */ 53 | protected function isCompatibleCacheImplementation() { 54 | return $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['realurl']['cacheImplementation'] === 'DmitryDulepov\\Realurl\\Cache\\DatabaseCache'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Classes/Controller/PathCacheController.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | use TYPO3\CMS\Backend\Utility\BackendUtility; 27 | use TYPO3\CMS\Core\Messaging\FlashMessage; 28 | use TYPO3\CMS\Core\Utility\GeneralUtility; 29 | use TYPO3\CMS\Extbase\Persistence\QueryInterface; 30 | use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; 31 | use TYPO3\CMS\Extbase\Utility\LocalizationUtility; 32 | 33 | /** 34 | * This class implements management of RealURL url cache. 35 | * 36 | * @author Dmitry Dulepov 37 | */ 38 | class PathCacheController extends BackendModuleController { 39 | 40 | /** 41 | * @var \DmitryDulepov\Realurl\Domain\Repository\PathCacheEntryRepository 42 | * @inject 43 | */ 44 | protected $repository; 45 | 46 | /** 47 | * Deletes a given entry for the given page. 48 | * 49 | * @param int $uid 50 | * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException 51 | */ 52 | public function deleteAction($uid) { 53 | $this->databaseConnection->exec_DELETEquery('tx_realurl_pathdata', 'uid=' . (int)$uid); 54 | 55 | $this->addFlashMessage(LocalizationUtility::translate('module.path_cache.entry_deleted', 'realurl')); 56 | 57 | $this->forward('index', 'PathCache'); 58 | } 59 | 60 | /** 61 | * Shows a list of page path entries. 62 | */ 63 | public function indexAction() { 64 | $entries = $this->getCacheEntries(); 65 | $this->makeMessagesForDuplicates($entries); 66 | $entries->rewind(); 67 | $this->view->assignMultiple(array( 68 | 'entries' => $entries 69 | )); 70 | } 71 | 72 | /** 73 | * Adds a message about duplicate URLs. 74 | * 75 | * @param string $pagePath 76 | * @param QueryResultInterface $entries 77 | */ 78 | protected function addDuplicateMessage($pagePath, QueryResultInterface $entries) { 79 | // The ugly variable below has to be used because Extbase misses DISTINCT for queries 80 | static $addedCombinations = array(); 81 | 82 | $pageIds = array(); 83 | foreach ($entries as $entry) { 84 | /** @var \DmitryDulepov\Realurl\Domain\Model\PathCacheEntry $entry */ 85 | $pageId = (int)$entry->getPageId(); 86 | if (!isset($pageIds[$pageId])) { 87 | if ($this->doesBackendUserHaveAccessToPage($pageId)) { 88 | $recordPath = rtrim(BackendUtility::getRecordPath($pageId, '', 300), '/'); 89 | $pageIds[$pageId] = sprintf('%d (%s)', $pageId, $recordPath); 90 | } 91 | } 92 | } 93 | ksort($pageIds); 94 | 95 | if (count($pageIds) > 0) { 96 | $combination = $pagePath . '_' . implode('_', $pageIds); 97 | if (!isset($addedCombinations[$combination])) { 98 | $addedCombinations[$combination] = true; 99 | 100 | $message = LocalizationUtility::translate('module.path_cache.duplicate_path', 'realurl', array($pagePath, implode(', ', $pageIds))); 101 | 102 | $flashMessage = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessage', $message, '', FlashMessage::ERROR); 103 | /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $flashMessage */ 104 | $this->controllerContext->getFlashMessageQueue()->enqueue($flashMessage); 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * Fetches cache entries. 111 | * 112 | * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface 113 | */ 114 | protected function getCacheEntries() { 115 | $pageId = (int)GeneralUtility::_GP('id'); 116 | $query = $this->repository->createQuery(); 117 | $query->setOrderings(array( 118 | 'languageId' => QueryInterface::ORDER_ASCENDING, 119 | 'expire' => QueryInterface::ORDER_ASCENDING, 120 | 'pagePath' => QueryInterface::ORDER_ASCENDING, 121 | )); 122 | $query->matching($query->equals('pageId', $pageId)); 123 | 124 | return $query->execute(); 125 | } 126 | 127 | /** 128 | * Checks if there are any duplicates for this url and adds warnings. 129 | * 130 | * @param QueryResultInterface $entries 131 | */ 132 | protected function makeMessagesForDuplicates(QueryResultInterface $entries) { 133 | foreach ($entries as $entry) { 134 | /** @var \DmitryDulepov\Realurl\Domain\Model\PathCacheEntry $entry */ 135 | $query = $this->repository->createQuery(); 136 | // Conditions (logical and): 137 | // 1. Different page id 138 | // 2. Same path 139 | // 3. Same root page id 140 | // 4. Same language id 141 | /** @noinspection PhpMethodParametersCountMismatchInspection */ 142 | $query->matching($query->logicalAnd( 143 | $query->logicalNot($query->equals('pageId', $entry->getPageId())), 144 | $query->equals('rootPageId', $entry->getRootPageId()), 145 | $query->equals('pagePath', $entry->getPagePath()), 146 | $query->equals('languageId', $entry->getLanguageId()) 147 | )); 148 | $query->setOrderings(array( 149 | 'pagePath' => QueryInterface::ORDER_ASCENDING, 150 | 'languageId' => QueryInterface::ORDER_ASCENDING, 151 | )); 152 | 153 | $result = $query->execute(); 154 | if ($result->count() > 0) { 155 | $this->addDuplicateMessage($entry->getPagePath(), $result); 156 | } 157 | 158 | unset($result); 159 | unset($query); 160 | } 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /Classes/Controller/UrlCacheController.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | use TYPO3\CMS\Backend\Utility\BackendUtility; 27 | use TYPO3\CMS\Core\Messaging\FlashMessage; 28 | use TYPO3\CMS\Core\Utility\GeneralUtility; 29 | use TYPO3\CMS\Extbase\Persistence\QueryInterface; 30 | use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; 31 | use TYPO3\CMS\Extbase\Utility\LocalizationUtility; 32 | 33 | /** 34 | * This class implements management of RealURL url cache. 35 | * 36 | * @author Dmitry Dulepov 37 | */ 38 | class UrlCacheController extends BackendModuleController { 39 | 40 | /** 41 | * @var \DmitryDulepov\Realurl\Domain\Repository\UrlCacheEntryRepository 42 | * @inject 43 | */ 44 | protected $repository; 45 | 46 | /** 47 | * Deletes a given entry for the given page. 48 | * 49 | * @param int $uid 50 | * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException 51 | */ 52 | public function deleteAction($uid) { 53 | $this->databaseConnection->sql_query('START TRANSACTION'); 54 | $this->databaseConnection->exec_DELETEquery('tx_realurl_urldata', 'uid=' . (int)$uid); 55 | $this->databaseConnection->exec_DELETEquery('tx_realurl_uniqalias_cache_map', 'url_cache_id=' . (int)$uid); 56 | $this->databaseConnection->sql_query('COMMIT'); 57 | 58 | $this->addFlashMessage(LocalizationUtility::translate('module.url_cache.entry_deleted', 'realurl')); 59 | 60 | $this->forward('index', 'UrlCache'); 61 | } 62 | 63 | /** 64 | * Deletes all entries for the given page. 65 | * 66 | * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException 67 | */ 68 | public function deleteAllAction() { 69 | $this->databaseConnection->sql_query('START TRANSACTION'); 70 | $this->databaseConnection->sql_query('DELETE FROM tx_realurl_uniqalias_cache_map WHERE ' . 71 | 'url_cache_id IN (SELECT uid FROM tx_realurl_urldata WHERE page_id=' . (int)GeneralUtility::_GP('id') . ')' 72 | ); 73 | $this->databaseConnection->sql_query('DELETE FROM tx_realurl_urldata WHERE page_id=' . (int)GeneralUtility::_GP('id')); 74 | $this->databaseConnection->sql_query('COMMIT'); 75 | 76 | $this->addFlashMessage(LocalizationUtility::translate('module.url_cache.all_entries_deleted', 'realurl')); 77 | 78 | $this->forward('index', 'UrlCache'); 79 | } 80 | 81 | /** 82 | * Deletes all entries from the cache. 83 | * 84 | * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException 85 | */ 86 | public function flushAction() { 87 | $this->databaseConnection->sql_query('START TRANSACTION'); 88 | // Not using TRUNCATE because of DBAL 89 | $this->databaseConnection->sql_query('DELETE FROM tx_realurl_uniqalias_cache_map'); 90 | $this->databaseConnection->sql_query('DELETE FROM tx_realurl_urldata'); 91 | $this->databaseConnection->sql_query('COMMIT'); 92 | 93 | $this->addFlashMessage(LocalizationUtility::translate('module.url_cache.flushed', 'realurl')); 94 | 95 | $this->forward('index', 'UrlCache'); 96 | } 97 | 98 | /** 99 | * Shows a list of URL cache entries. 100 | */ 101 | public function indexAction() { 102 | $entries = $this->getCacheEntries(); 103 | $this->makeMessagesForDuplicates($entries); 104 | $entries->rewind(); 105 | $this->view->assignMultiple(array( 106 | 'entries' => $entries, 107 | 'showFlushAllButton' => $this->shouldShowFlushAllButton(), 108 | 'showFlushAllPageUrlsButton' => $this->shouldShowFlushAllPageUrlsButton(), 109 | )); 110 | } 111 | 112 | /** 113 | * Adds a message about duplicate URLs. 114 | * 115 | * @param string $speakingUrl 116 | * @param QueryResultInterface $entries 117 | */ 118 | protected function addDuplicateMessage($speakingUrl, QueryResultInterface $entries) { 119 | // The ugly variable below has to be used because Extbase misses DISTINCT for queries 120 | static $addedCombinations = array(); 121 | 122 | $pageIds = array(); 123 | foreach ($entries as $entry) { 124 | /** @var \DmitryDulepov\Realurl\Domain\Model\UrlCacheEntry $entry */ 125 | $pageId = (int)$entry->getPageId(); 126 | if (!isset($pageIds[$pageId])) { 127 | if ($this->doesBackendUserHaveAccessToPage($pageId)) { 128 | $recordPath = rtrim(BackendUtility::getRecordPath($pageId, '', 300), '/'); 129 | $pageIds[$pageId] = sprintf('%d (%s)', $pageId, $recordPath); 130 | } 131 | } 132 | } 133 | ksort($pageIds); 134 | 135 | if (count($pageIds) > 0) { 136 | $combination = $speakingUrl . implode(' ', $pageIds); 137 | if (!isset($addedCombinations[$combination])) { 138 | $message = LocalizationUtility::translate('module.url_cache.duplicate_url', 'realurl', array($speakingUrl, implode(', ', $pageIds))); 139 | 140 | $flashMessage = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessage', $message, '', FlashMessage::ERROR); 141 | /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $flashMessage */ 142 | $this->controllerContext->getFlashMessageQueue()->enqueue($flashMessage); 143 | } 144 | } 145 | } 146 | 147 | /** 148 | * Loads URL cache entries. 149 | * 150 | * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface 151 | */ 152 | protected function getCacheEntries() { 153 | $pageId = (int)GeneralUtility::_GP('id'); 154 | $query = $this->repository->createQuery(); 155 | $query->setOrderings(array( 156 | 'speakingUrl' => QueryInterface::ORDER_ASCENDING, 157 | 'originalUrl' => QueryInterface::ORDER_ASCENDING, 158 | 'expire' => QueryInterface::ORDER_ASCENDING, 159 | )); 160 | $query->matching($query->equals('pageId', $pageId)); 161 | 162 | return $query->execute(); 163 | } 164 | 165 | /** 166 | * Gets restrictions TSConfig part. 167 | * 168 | * @param string $restrictionPropertyName 169 | * @param bool $defaultValue 170 | * @return bool 171 | */ 172 | protected function getTsConfigRestriction($restrictionPropertyName, $defaultValue = false) { 173 | $result = $defaultValue; 174 | 175 | $tsConfig = BackendUtility::getModTSconfig((int)GeneralUtility::_GP('id'), 'mod.tx_realurl'); 176 | if (is_array($tsConfig['properties']) && is_array($tsConfig['properties']['restrictions.']) && isset($tsConfig['properties']['restrictions.'][$restrictionPropertyName])) { 177 | $result = (bool)$tsConfig['properties']['restrictions.'][$restrictionPropertyName]; 178 | } 179 | 180 | return $result; 181 | } 182 | 183 | /** 184 | * Checks if there are any duplicates for this url and adds warnings. 185 | * 186 | * @param QueryResultInterface $entries 187 | */ 188 | protected function makeMessagesForDuplicates(QueryResultInterface $entries) { 189 | foreach ($entries as $entry) { 190 | /** @var \DmitryDulepov\Realurl\Domain\Model\UrlCacheEntry $entry */ 191 | $query = $this->repository->createQuery(); 192 | // Conditions (logical and): 193 | // 1. Different page id 194 | // 2. Same url 195 | // 3. Same root page id 196 | /** @noinspection PhpMethodParametersCountMismatchInspection */ 197 | $query->matching($query->logicalAnd( 198 | $query->logicalNot($query->equals('pageId', $entry->getPageId())), 199 | $query->equals('rootPageId', $entry->getRootPageId()), 200 | $query->equals('speakingUrl', $entry->getSpeakingUrl()) 201 | )); 202 | $query->setOrderings(array( 203 | 'speakingUrl' => QueryInterface::ORDER_ASCENDING, 204 | )); 205 | 206 | $result = $query->execute(); 207 | if ($result->count() > 0) { 208 | $this->addDuplicateMessage($entry->getSpeakingUrl(), $result); 209 | } 210 | 211 | unset($result); 212 | unset($query); 213 | } 214 | } 215 | 216 | /** 217 | * Checks if flush all entries should be visible. 218 | * 219 | * @return bool 220 | */ 221 | protected function shouldShowFlushAllButton() { 222 | return $GLOBALS['BE_USER']->isAdmin() || !$this->getTsConfigRestriction('disableFlushAllUrls'); 223 | } 224 | 225 | protected function shouldShowFlushAllPageUrlsButton() { 226 | return $GLOBALS['BE_USER']->isAdmin() || !$this->getTsConfigRestriction('disableFlushAllPageUrls'); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /Classes/Domain/Model/Alias.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; 27 | 28 | class Alias extends AbstractEntity { 29 | 30 | /** @var int */ 31 | protected $expire; 32 | 33 | /** @var string */ 34 | protected $fieldAlias; 35 | 36 | /** @var string */ 37 | protected $fieldId; 38 | 39 | /** @var int */ 40 | protected $lang; 41 | 42 | /** @var string */ 43 | protected $tablename; 44 | 45 | /** @var string */ 46 | protected $valueAlias; 47 | 48 | /** @var int */ 49 | protected $valueId; 50 | 51 | /** 52 | * @return int 53 | */ 54 | public function getExpire() { 55 | return $this->expire; 56 | } 57 | 58 | /** 59 | * @param int $expire 60 | */ 61 | public function setExpire($expire) { 62 | $this->expire = $expire; 63 | } 64 | 65 | /** 66 | * @return string 67 | */ 68 | public function getFieldAlias() { 69 | return $this->fieldAlias; 70 | } 71 | 72 | /** 73 | * @param string $fieldAlias 74 | */ 75 | public function setFieldAlias($fieldAlias) { 76 | $this->fieldAlias = $fieldAlias; 77 | } 78 | 79 | /** 80 | * @return string 81 | */ 82 | public function getFieldId() { 83 | return $this->fieldId; 84 | } 85 | 86 | /** 87 | * @param string $fieldId 88 | */ 89 | public function setFieldId($fieldId) { 90 | $this->fieldId = $fieldId; 91 | } 92 | 93 | /** 94 | * @return int 95 | */ 96 | public function getLang() { 97 | return $this->lang; 98 | } 99 | 100 | /** 101 | * @param int $lang 102 | */ 103 | public function setLang($lang) { 104 | $this->lang = $lang; 105 | } 106 | 107 | /** 108 | * @return string 109 | */ 110 | public function getTablename() { 111 | return $this->tablename; 112 | } 113 | 114 | /** 115 | * @param string $tablename 116 | */ 117 | public function setTablename($tablename) { 118 | $this->tablename = $tablename; 119 | } 120 | 121 | /** 122 | * @return string 123 | */ 124 | public function getValueAlias() { 125 | return $this->valueAlias; 126 | } 127 | 128 | /** 129 | * @param string $valueAlias 130 | */ 131 | public function setValueAlias($valueAlias) { 132 | $this->valueAlias = $valueAlias; 133 | } 134 | 135 | /** 136 | * @return int 137 | */ 138 | public function getValueId() { 139 | return $this->valueId; 140 | } 141 | 142 | /** 143 | * @param int $valueId 144 | */ 145 | public function setValueId($valueId) { 146 | $this->valueId = $valueId; 147 | } 148 | 149 | /** 150 | * @return string 151 | */ 152 | public function getRecordFieldValue() { 153 | /** @noinspection PhpUndefinedMethodInspection */ 154 | $row = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow( 155 | $this->fieldAlias, $this->tablename, 156 | $this->fieldId . '=' . (int)$this->valueId 157 | ); 158 | 159 | return is_array($row) && isset($row[$this->fieldAlias]) ? $row[$this->fieldAlias] : ''; 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /Classes/Domain/Model/PathCacheEntry.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; 27 | 28 | /** 29 | * This class represents a path cache entry, It is used in the Backend 30 | * administration module. 31 | * 32 | * @author Dmitry Dulepov 33 | */ 34 | class PathCacheEntry extends AbstractEntity { 35 | 36 | /** @var int */ 37 | protected $expire; 38 | 39 | /** @var int */ 40 | protected $languageId; 41 | 42 | /** @var string */ 43 | protected $mpVar; 44 | 45 | /** @var int */ 46 | protected $pageId; 47 | 48 | /** @var string */ 49 | protected $pagePath; 50 | 51 | /** @var int */ 52 | protected $rootPageId; 53 | 54 | /** 55 | * @return int 56 | */ 57 | public function getExpire() { 58 | return $this->expire; 59 | } 60 | 61 | /** 62 | * @param int $expire 63 | */ 64 | public function setExpire($expire) { 65 | $this->expire = $expire; 66 | } 67 | 68 | /** 69 | * @return int 70 | */ 71 | public function getLanguageId() { 72 | return $this->languageId; 73 | } 74 | 75 | /** 76 | * @param int $languageId 77 | */ 78 | public function setLanguageId($languageId) { 79 | $this->languageId = $languageId; 80 | } 81 | 82 | /** 83 | * @return string 84 | */ 85 | public function getMpVar() { 86 | return $this->mpVar; 87 | } 88 | 89 | /** 90 | * @param string $mpVar 91 | */ 92 | public function setMpVar($mpVar) { 93 | $this->mpVar = $mpVar; 94 | } 95 | 96 | /** 97 | * @return int 98 | */ 99 | public function getPageId() { 100 | return $this->pageId; 101 | } 102 | 103 | /** 104 | * @param int $pageId 105 | */ 106 | public function setPageId($pageId) { 107 | $this->pageId = $pageId; 108 | } 109 | 110 | /** 111 | * @return string 112 | */ 113 | public function getPagePath() { 114 | return $this->pagePath; 115 | } 116 | 117 | /** 118 | * @param string $pagePath 119 | */ 120 | public function setPagePath($pagePath) { 121 | $this->pagePath = $pagePath; 122 | } 123 | 124 | /** 125 | * @return int 126 | */ 127 | public function getRootPageId() { 128 | return $this->rootPageId; 129 | } 130 | 131 | /** 132 | * @param int $rootPageId 133 | */ 134 | public function setRootPageId($rootPageId) { 135 | $this->rootPageId = $rootPageId; 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /Classes/Domain/Model/UrlCacheEntry.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; 27 | 28 | /** 29 | * This class represents a url cache entry, It is used in the Backend 30 | * administration module. 31 | * 32 | * @author Dmitry Dulepov 33 | */ 34 | class UrlCacheEntry extends AbstractEntity { 35 | 36 | /** @var int */ 37 | protected $expire; 38 | 39 | /** @var string */ 40 | protected $originalUrl; 41 | 42 | /** @var int */ 43 | protected $pageId; 44 | 45 | /** @var string */ 46 | protected $requestVariables; 47 | 48 | /** @var int */ 49 | protected $rootPageId; 50 | 51 | /** @var string */ 52 | protected $speakingUrl; 53 | 54 | /** 55 | * @return int 56 | */ 57 | public function getExpire() { 58 | return $this->expire; 59 | } 60 | 61 | /** 62 | * @return string 63 | */ 64 | public function getOriginalUrl() { 65 | return $this->originalUrl; 66 | } 67 | 68 | /** 69 | * @param string $originalUrl 70 | */ 71 | public function setOriginalUrl($originalUrl) { 72 | $this->originalUrl = $originalUrl; 73 | } 74 | 75 | /** 76 | * @return int 77 | */ 78 | public function getPageId() { 79 | return $this->pageId; 80 | } 81 | 82 | /** 83 | * @param int $pageId 84 | */ 85 | public function setPageId($pageId) { 86 | $this->pageId = $pageId; 87 | } 88 | 89 | /** 90 | * @return string 91 | */ 92 | public function getRequestVariables() { 93 | return $this->requestVariables; 94 | } 95 | 96 | /** 97 | * @param string $requestVariables 98 | */ 99 | public function setRequestVariables($requestVariables) { 100 | $this->requestVariables = $requestVariables; 101 | } 102 | 103 | /** 104 | * @return int 105 | */ 106 | public function getRootPageId() { 107 | return $this->rootPageId; 108 | } 109 | 110 | /** 111 | * @param int $rootPageId 112 | */ 113 | public function setRootPageId($rootPageId) { 114 | $this->rootPageId = $rootPageId; 115 | } 116 | 117 | /** 118 | * @return string 119 | */ 120 | public function getSpeakingUrl() { 121 | return $this->speakingUrl; 122 | } 123 | 124 | /** 125 | * @param string $speakingUrl 126 | */ 127 | public function setSpeakingUrl($speakingUrl) { 128 | $this->speakingUrl = $speakingUrl; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Classes/Domain/Repository/AbstractRepository.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | use TYPO3\CMS\Extbase\Persistence\Repository; 27 | 28 | /** 29 | * This class implements a base repository for all RealURl repositories. 30 | * 31 | * @author Dmitry Dulepov 32 | */ 33 | abstract class AbstractRepository extends Repository { 34 | 35 | /** 36 | * Creates query and makes sure that no Extbase magic is performed with 37 | * pid, languages, etc. 38 | * 39 | * Magic is cool but not on this case. 40 | * 41 | * @return \TYPO3\CMS\Extbase\Persistence\QueryInterface 42 | */ 43 | public function createQuery() { 44 | $query = parent::createQuery(); 45 | $query->getQuerySettings()->setRespectStoragePage(false)->setIgnoreEnableFields(true)->setRespectSysLanguage(false); 46 | 47 | return $query; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /Classes/Domain/Repository/AliasRepository.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | class AliasRepository extends AbstractRepository { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Classes/Domain/Repository/PathCacheEntryRepository.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | class PathCacheEntryRepository extends AbstractRepository { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Classes/Domain/Repository/UrlCacheEntryRepository.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | class UrlCacheEntryRepository extends AbstractRepository { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Classes/EncodeDecoderBase.php: -------------------------------------------------------------------------------- 1 | 43 | */ 44 | abstract class EncodeDecoderBase { 45 | 46 | /** @var CacheInterface */ 47 | protected $cache; 48 | 49 | /** @var \DmitryDulepov\Realurl\Configuration\ConfigurationReader */ 50 | protected $configuration; 51 | 52 | /** @var \TYPO3\CMS\Core\Database\DatabaseConnection */ 53 | protected $databaseConnection; 54 | 55 | /** @var string */ 56 | protected $emptySegmentValue; 57 | 58 | /** @var array */ 59 | protected $ignoredUrlParameters = array(); 60 | 61 | /** @var \TYPO3\CMS\Core\Log\Logger */ 62 | protected $logger; 63 | 64 | /** @var PageRepository */ 65 | protected $pageRepository = NULL; 66 | 67 | /** @var array */ 68 | static public $pageTitleFields = array('tx_realurl_pathsegment', 'alias', 'nav_title', 'title', 'uid'); 69 | 70 | /** @var int */ 71 | protected $rootPageId; 72 | 73 | /** 74 | * Undocumented, unsupported & deprecated. 75 | * 76 | * @var string 77 | */ 78 | protected $separatorCharacter; 79 | 80 | /** @var TypoScriptFrontendController */ 81 | protected $tsfe; 82 | 83 | /** @var \DmitryDulepov\Realurl\Utility */ 84 | protected $utility; 85 | 86 | /** 87 | * Initializes the class. 88 | */ 89 | public function __construct() { 90 | Utility::checkAndPerformRequiredUpdates(); 91 | $this->databaseConnection = $GLOBALS['TYPO3_DB']; 92 | $this->logger = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Log\\LogManager')->getLogger(get_class($this)); 93 | $this->tsfe = $GLOBALS['TSFE']; 94 | // Warning! It is important to init the new object and not reuse any existing object 95 | // $this->pageRepository->sys_language_uid must stay 0 because we will do overlays manually 96 | $this->pageRepository = GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Page\\PageRepository'); 97 | $this->pageRepository->init(FALSE); 98 | self::overwritePageTitleFieldsFromConfiguration(); 99 | } 100 | 101 | /** 102 | * Returns the configuration reader. This can be used in hooks. 103 | * 104 | * @return \DmitryDulepov\Realurl\Configuration\ConfigurationReader 105 | */ 106 | public function getConfiguration() { 107 | return $this->configuration; 108 | } 109 | 110 | /** 111 | * Returns $this->rootPageId. This can be used in hooks. 112 | * 113 | * @return int 114 | */ 115 | public function getRootPageId() { 116 | return $this->rootPageId; 117 | } 118 | 119 | /** 120 | * Creates a query string (without preceding question mark) from 121 | * parameters. 122 | * 123 | * @param array $parameters 124 | * @return mixed 125 | */ 126 | protected function createQueryStringFromParameters(array $parameters) { 127 | return substr(GeneralUtility::implodeArrayForUrl('', $parameters, '', false, true), 1); 128 | } 129 | 130 | /** 131 | * Checks conditions for xxxVar. 132 | * 133 | * @param array $conditionConfiguration 134 | * @param string $previousValue 135 | * @return bool 136 | */ 137 | protected function checkLegacyCondition(array $conditionConfiguration, $previousValue) { 138 | $result = true; 139 | 140 | // Check previous value 141 | if (isset($conditionConfiguration['prevValueInList'])) { 142 | if (!GeneralUtility::inList($conditionConfiguration['prevValueInList'], $previousValue)) 143 | $result = false; 144 | } 145 | 146 | return $result; 147 | } 148 | 149 | /** 150 | * Sets configuration blocks for fixedPostVars and postVarSets according 151 | * to priority: current page id first, _DEFAULT last. Also resolves aliases 152 | * for configuration. 153 | * 154 | * @param array $configuration 155 | * @param int $pageId 156 | * @return array 157 | */ 158 | protected function getConfigurationForPostVars(array $configuration, $pageId) { 159 | $configurationBlock = NULL; 160 | if (isset($configuration[$pageId])) { 161 | $maxTries = 10; 162 | while ($maxTries-- && isset($configuration[$pageId]) && !is_array($configuration[$pageId])) { 163 | $pageId = $configuration[$pageId]; 164 | } 165 | if (is_array($configuration[$pageId])) { 166 | $configurationBlock = $configuration[$pageId]; 167 | } 168 | } 169 | if (is_null($configurationBlock) && isset($configuration['_DEFAULT'])) { 170 | $configurationBlock = $configuration['_DEFAULT']; 171 | } 172 | if (!is_array($configurationBlock)) { 173 | $configurationBlock = array(); 174 | } 175 | 176 | return $configurationBlock; 177 | } 178 | 179 | /** 180 | * Initializes confguration reader. 181 | * 182 | * @return void 183 | */ 184 | abstract protected function initializeConfiguration(); 185 | 186 | /** 187 | * Looks up an ID value (integer) in lookup-table based on input alias value. 188 | * (The lookup table for id<->alias is meant to contain UNIQUE alias strings for id integers) 189 | * In the lookup table 'tx_realurl_uniqalias' the field "value_alias" should be unique (per combination of field_alias+field_id+tablename)! However the "value_id" field doesn't have to; that is a feature which allows more aliases to point to the same id. The alias selected for converting id to alias will be the first inserted at the moment. This might be more intelligent in the future, having an order column which can be controlled from the backend for instance! 190 | * 191 | * @param array $configuration 192 | * @param string $aliasValue 193 | * @return int ID integer. If none is found: false 194 | */ 195 | protected function getFromAliasCacheByAliasValue(array $configuration, $aliasValue) { 196 | $row = $this->databaseConnection->exec_SELECTgetSingleRow('value_id', 'tx_realurl_uniqalias', 197 | 'value_alias=' . $this->databaseConnection->fullQuoteStr($aliasValue, 'tx_realurl_uniqalias') . 198 | ' AND field_alias=' . $this->databaseConnection->fullQuoteStr($configuration['alias_field'], 'tx_realurl_uniqalias') . 199 | ' AND field_id=' . $this->databaseConnection->fullQuoteStr($configuration['id_field'], 'tx_realurl_uniqalias') . 200 | ' AND tablename=' . $this->databaseConnection->fullQuoteStr($configuration['table'], 'tx_realurl_uniqalias') . 201 | ' AND (expire=0 OR expire>' . time() . ')'); 202 | 203 | return (is_array($row) ? (int)$row['value_id'] : false); 204 | } 205 | 206 | /** 207 | * Initializes the instance. 208 | * 209 | * @throws \Exception 210 | */ 211 | protected function initialize() { 212 | $this->initializeConfiguration(); 213 | $this->emptySegmentValue = $this->configuration->get('init/emptySegmentValue'); 214 | $this->rootPageId = (int)$this->configuration->get('pagePath/rootpage_id'); 215 | $this->utility = GeneralUtility::makeInstance('DmitryDulepov\\Realurl\\Utility', $this->configuration); 216 | $this->cache = $this->utility->getCache(); 217 | $this->separatorCharacter = $this->configuration->get('pagePath/spaceCharacter'); 218 | } 219 | 220 | /** 221 | * Checks if system runs in non-live workspace 222 | * 223 | * @return boolean 224 | */ 225 | protected function isInWorkspace() { 226 | $result = false; 227 | if ($this->tsfe->beUserLogin) { 228 | $result = ($GLOBALS['BE_USER']->workspace !== 0); 229 | } 230 | return $result; 231 | } 232 | 233 | /** 234 | * Overwrites page title fields from extension configuration. This function 235 | * is used from the constructor and also from DataHandler hook, thus made 236 | * public. 237 | * 238 | * @return void 239 | */ 240 | public static function overwritePageTitleFieldsFromConfiguration() { 241 | $configuration = @unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['realurl']); 242 | if (!empty($configuration['segTitleFieldList'])) { 243 | $segTitleFieldList = GeneralUtility::trimExplode(',', $configuration['segTitleFieldList']); 244 | if (count($segTitleFieldList) > 0) { 245 | self::$pageTitleFields = $segTitleFieldList; 246 | } 247 | } 248 | } 249 | 250 | /** 251 | * Removes ignored parameters from the query string. 252 | * 253 | * @param string $queryString 254 | * @return string 255 | */ 256 | protected function removeIgnoredParametersFromQueryString($queryString) { 257 | if ($queryString) { 258 | $ignoredParametersRegExp = $this->configuration->get('cache/ignoredGetParametersRegExp'); 259 | if ($ignoredParametersRegExp) { 260 | $collectedParameters = array(); 261 | foreach (explode('&', trim($queryString, '&')) as $parameterPair) { 262 | list($parameterName, $parameterValue) = explode('=', $parameterPair, 2); 263 | if ($parameterName !== '') { 264 | $parameterName = urldecode($parameterName); 265 | if (preg_match($ignoredParametersRegExp, $parameterName)) { 266 | $this->ignoredUrlParameters[$parameterName] = urldecode($parameterValue); 267 | } 268 | else { 269 | $collectedParameters[$parameterName] = urldecode($parameterValue); 270 | } 271 | } 272 | } 273 | $queryString = $this->createQueryStringFromParameters($collectedParameters); 274 | } 275 | } else { 276 | $queryString = ''; 277 | } 278 | 279 | return $queryString; 280 | } 281 | 282 | /** 283 | * Removes ignored parameters from the URL. Removed parameters are stored in 284 | * $this->ignoredUrlParameters and can be restored using restoreIgnoredUrlParameters. 285 | * 286 | * @param string $url 287 | * @return string 288 | */ 289 | protected function removeIgnoredParametersFromURL($url) { 290 | list($path, $queryString) = explode('?', $url, 2); 291 | $queryString = $this->removeIgnoredParametersFromQueryString((string)$queryString); 292 | 293 | $url = $path; 294 | if (!empty($queryString)) { 295 | $url .= '?'; 296 | } 297 | $url .= $queryString; 298 | 299 | return $url; 300 | } 301 | 302 | /** 303 | * Restores ignored URL parameters. 304 | * 305 | * @param array $urlParameters 306 | */ 307 | protected function restoreIgnoredUrlParameters(array &$urlParameters) { 308 | if (count($this->ignoredUrlParameters) > 0) { 309 | ArrayUtility::mergeRecursiveWithOverrule($urlParameters, $this->ignoredUrlParameters); 310 | $this->sortArrayDeep($urlParameters); 311 | } 312 | } 313 | 314 | /** 315 | * Restores ignored URL parameters. 316 | * 317 | * @param string $url 318 | * @return string 319 | */ 320 | protected function restoreIgnoredUrlParametersInURL($url) { 321 | if (count($this->ignoredUrlParameters) > 0) { 322 | list($path, $queryString) = explode('?', $url); 323 | $appendedPart = $this->createQueryStringFromParameters($this->ignoredUrlParameters); 324 | if (!empty($queryString)) { 325 | $queryString .= '&'; 326 | } 327 | $queryString .= $appendedPart; 328 | 329 | $url = $path . '?' . $queryString; 330 | } 331 | 332 | return $url; 333 | } 334 | 335 | /** 336 | * Sorts the array deeply. 337 | * 338 | * @param array $pathParts 339 | * @return void 340 | */ 341 | protected function sortArrayDeep(array &$pathParts) { 342 | if (count($pathParts) > 1) { 343 | ksort($pathParts); 344 | } 345 | foreach ($pathParts as $key => $part) { 346 | if (is_array($part)) { 347 | $this->sortArrayDeep($pathParts[$key]); 348 | } 349 | } 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /Classes/Evaluator/SegmentFieldCleaner.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | /** 27 | * This class contains form field evaluator that will remove leading and 28 | * trailing slashes from the tx_realurl_pathsegment field on save. 29 | * 30 | * @author Dmitry Dulepov 31 | */ 32 | class SegmentFieldCleaner { 33 | 34 | /** 35 | * Evaluates field value. 36 | * 37 | * @param string $value 38 | * @return string 39 | */ 40 | public function evaluateFieldValue($value) { 41 | $value = str_replace('_', '-', $value); 42 | return trim($value, '/'); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Classes/Exceptions/InvalidLanguageParameterException.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | class InvalidLanguageParameterException extends \Exception { 27 | 28 | public function __construct($language) { 29 | parent::__construct(sprintf('Bad "L" parameter ("%s") was detected by realurl', $language), 1489744197); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Classes/Hooks/DataHandler.php: -------------------------------------------------------------------------------- 1 | cache = CacheFactory::getCache(); 52 | $this->databaseConnection = $GLOBALS['TYPO3_DB']; 53 | EncodeDecoderBase::overwritePageTitleFieldsFromConfiguration(); 54 | } 55 | 56 | /** 57 | * Clears path and URL caches if the page was deleted. 58 | * 59 | * @param string $table 60 | * @param string|int $id 61 | */ 62 | public function processCmdmap_deleteAction($table, $id) { 63 | if (($table === 'pages' || $table === 'pages_language_overlay') && MathUtility::canBeInterpretedAsInteger($id)) { 64 | if ($table === 'pages_language_overlay') { 65 | $record = BackendUtility::getRecord($table, $id); 66 | $id = $record['pid']; 67 | } 68 | $this->deleteCachesForPageAndSubpages($id); 69 | } 70 | } 71 | 72 | /** 73 | * Expires caches if the page was moved. 74 | * 75 | * @param string $command 76 | * @param string $table 77 | * @param int $id 78 | */ 79 | public function processCmdmap_postProcess($command, $table, $id) { 80 | if ($table === 'pages') { 81 | if ($command === 'move') { 82 | $this->expireCachesForPageAndSubpages((int)$id, 0); 83 | 84 | $languageOverlays = $this->getRecordsByField('pages_language_overlay', 'pid', $id); 85 | if (is_array($languageOverlays)) { 86 | foreach ($languageOverlays as $languageOverlay) { 87 | $this->expireCachesForPageAndSubpages($languageOverlay['pid'], $languageOverlay['sys_language_uid']); 88 | } 89 | } 90 | } elseif ($command === 'delete') { 91 | $this->cache->clearPathCacheForPage($id); 92 | $this->cache->clearUrlCacheForPage($id); 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * A DataHandler hook to expire old records. 99 | * 100 | * @param string $status 'new' (ignoring) or 'update' 101 | * @param string $tableName 102 | * @param int $recordId 103 | * @param array $databaseData 104 | * @param \TYPO3\CMS\Core\DataHandling\DataHandler $dataHandler 105 | * @return void 106 | */ 107 | public function processDatamap_afterDatabaseOperations($status, $tableName, $recordId, array $databaseData, /** @noinspection PhpUnusedParameterInspection */ \TYPO3\CMS\Core\DataHandling\DataHandler $dataHandler) { 108 | if (!MathUtility::canBeInterpretedAsInteger($recordId)) { 109 | $recordId = (int)$dataHandler->substNEWwithIDs[$recordId]; 110 | } 111 | $this->expireCache($status, $tableName, $recordId, $databaseData); 112 | $this->clearAutoConfiguration($tableName, $databaseData); 113 | if ($status !== 'new') { 114 | $this->clearUrlCacheForAliasChanges($tableName, (int)$recordId); 115 | } 116 | } 117 | 118 | /** 119 | * Clears automatic configuration when necessary. Note: we do not check if 120 | * it is enabled. Even if now it is disabled, later it can be re-enabled 121 | * and suddenly obsolete config will be used. So we clear always. 122 | * 123 | * @param string $tableName 124 | * @param array $databaseData 125 | */ 126 | protected function clearAutoConfiguration($tableName, array $databaseData) { 127 | if ($tableName === 'sys_domain' || $tableName === 'pages' && isset($databaseData['is_siteroot'])) { 128 | if (file_exists(PATH_site . TX_REALURL_AUTOCONF_FILE)) { 129 | @unlink(PATH_site . TX_REALURL_AUTOCONF_FILE); 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * Clears URL cache if it is found in the alias table. 136 | * 137 | * @param string $tableName 138 | * @param int $recordId 139 | * @return void 140 | */ 141 | protected function clearUrlCacheForAliasChanges($tableName, $recordId) { 142 | if (!preg_match('/^(?:sys_|cf_)/', $tableName)) { 143 | if ($tableName == 'pages_language_overlay') { 144 | $tableName = 'pages'; 145 | } 146 | $languageId = 0; 147 | if (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField']) && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])) { 148 | // tx_realurl_uniqalias* tables store uid of records in the 149 | // main language. We have to find the uid of the current 150 | // record in the main language to proceed. 151 | // 152 | // We disable deleteClause here because this function is also 153 | // called when the aliases record is deleted. 154 | $record = BackendUtility::getRecord($tableName, $recordId, '*', '', false); 155 | if ($record) { 156 | $languageField = $GLOBALS['TCA'][$tableName]['ctrl']['languageField']; 157 | $languageId = (int)$record[$languageField]; 158 | if ($languageId > 0) { 159 | // Can't use !== 0 here because we need to ignore "$languageId === -1" 160 | $transOrigPointerField = $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']; 161 | $recordId = (int)$record[$transOrigPointerField]; 162 | unset($transOrigPointerField); 163 | } 164 | } 165 | unset($record); 166 | } 167 | $expirationTime = time() + 30*24*60*60; 168 | // This check would be sufficient for most cases but only when id_field is 'uid' in the configuration 169 | $result = $this->databaseConnection->sql_query( 170 | 'SELECT tx_realurl_uniqalias.uid,expire,url_cache_id FROM ' . 171 | 'tx_realurl_uniqalias LEFT JOIN tx_realurl_uniqalias_cache_map ON tx_realurl_uniqalias.uid=alias_uid ' . 172 | 'WHERE tablename=' . $this->databaseConnection->fullQuoteStr($tableName, 'tx_realurl_uniqalias') . ' ' . 173 | 'AND value_id=' . $recordId . ' AND lang IN (-1,' . $languageId . ')' 174 | ); 175 | while (FALSE !== ($data = $this->databaseConnection->sql_fetch_assoc($result))) { 176 | if ($data['url_cache_id']) { 177 | $this->cache->expireUrlCacheById($data['url_cache_id'], $expirationTime); 178 | } 179 | if ((int)$data['expire'] === 0) { 180 | $this->databaseConnection->exec_UPDATEquery('tx_realurl_uniqalias', 'uid=' . (int)$data['uid'], array( 181 | 'expire' => $expirationTime 182 | )); 183 | } 184 | } 185 | $this->databaseConnection->sql_free_result($result); 186 | } 187 | } 188 | 189 | /** 190 | * Expires cache for the page and subpages. 191 | * 192 | * @param int $pageId 193 | * @param int $level 194 | * @return void 195 | */ 196 | protected function deleteCachesForPageAndSubpages($pageId, $level = 0) { 197 | if ($pageId) { 198 | $this->cache->clearPathCacheForPage((int)$pageId); 199 | $this->cache->clearUrlCacheForPage((int)$pageId); 200 | $this->clearUrlCacheForAliasChanges('pages', (int)$pageId); 201 | if ($level++ < 20) { 202 | $subpages = $this->getRecordsByField('pages', 'pid', $pageId); 203 | if (is_array($subpages)) { 204 | $uidList = array(); 205 | foreach ($subpages as $subpage) { 206 | $uidList[] = (int)$subpage['uid']; 207 | } 208 | unset($subpages); 209 | foreach ($uidList as $uid) { 210 | $this->deleteCachesForPageAndSubpages($uid, $level); 211 | } 212 | } 213 | } 214 | } 215 | } 216 | 217 | /** 218 | * Expires cache if necessary when the record changes. For 'pages' we expire 219 | * cache only if the page was modified. For 'pages_language_overlay' we 220 | * do it on creation of a new translation too. For reasons see 221 | * https://github.com/dmitryd/typo3-realurl/issues/313#issuecomment-257268851 222 | * 223 | * @param string $status 224 | * @param string $tableName 225 | * @param string|int $recordId 226 | * @param array $databaseData 227 | * @return void 228 | */ 229 | protected function expireCache($status, $tableName, $recordId, array $databaseData) { 230 | if ($status === 'update' && $tableName === 'pages' || $tableName === 'pages_language_overlay') { 231 | if ($tableName == 'pages') { 232 | $languageId = 0; 233 | $pageId = $recordId; 234 | } else { 235 | $fullRecord = BackendUtility::getRecord($tableName, $recordId); 236 | $pageId = $fullRecord['pid']; 237 | $languageId = $fullRecord['sys_language_uid']; 238 | unset($fullRecord); 239 | } 240 | $expireCache = FALSE; 241 | if (isset($databaseData['hidden'])) { 242 | $expireCache = TRUE; 243 | } else if (isset($databaseData['tx_realurl_pathoverride']) || isset($databaseData['tx_realurl_exclude'])) { 244 | $expireCache = TRUE; 245 | 246 | // Updating alternative language pages as well 247 | $languageOverlays = $this->getRecordsByField('pages_language_overlay', 'pid', $pageId); 248 | if (is_array($languageOverlays)) { 249 | foreach ($languageOverlays as $languageOverlay) { 250 | $this->expireCachesForPageAndSubpages($languageOverlay['pid'], $languageOverlay['sys_language_uid']); 251 | } 252 | } 253 | } else { 254 | foreach (EncodeDecoderBase::$pageTitleFields as $fieldName) { 255 | if (isset($databaseData[$fieldName])) { 256 | $expireCache = TRUE; 257 | break; 258 | } 259 | } 260 | } 261 | if ($expireCache) { 262 | $this->expireCachesForPageAndSubpages($pageId, $languageId); 263 | } 264 | } 265 | } 266 | 267 | /** 268 | * Expires cache for the page and subpages. 269 | * 270 | * @param int $pageId 271 | * @param int $languageId 272 | * @param int $level 273 | * @return void 274 | */ 275 | protected function expireCachesForPageAndSubpages($pageId, $languageId, $level = 0) { 276 | if ($pageId) { 277 | $this->cache->expireCache($pageId, $languageId); 278 | if ($level++ < 20) { 279 | $subpages = $this->getRecordsByField('pages', 'pid', $pageId); 280 | if (is_array($subpages)) { 281 | $uidList = array(); 282 | foreach ($subpages as $subpage) { 283 | $uidList[] = (int)$subpage['uid']; 284 | } 285 | unset($subpages); 286 | foreach ($uidList as $uid) { 287 | $this->expireCachesForPageAndSubpages($uid, $languageId, $level); 288 | } 289 | } 290 | } 291 | } 292 | } 293 | 294 | /** 295 | * Fetches records from the database by the field name. This is a replacement for the 296 | * BackendUtility::getRecordsByField() method, which is deprecated since TYPO3 8.7. 297 | * 298 | * @param string $tableName 299 | * @param string $fieldName 300 | * @param mixed $fieldValue 301 | * @return array 302 | */ 303 | protected function getRecordsByField($tableName, $fieldName, $fieldValue) 304 | { 305 | $rows = $this->databaseConnection->exec_SELECTgetRows( 306 | '*', 307 | $tableName, 308 | $fieldName . '=' . $this->databaseConnection->fullQuoteStr($fieldValue, $tableName) 309 | ); 310 | 311 | return (array)$rows; 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /Classes/Utility.php: -------------------------------------------------------------------------------- 1 | 44 | */ 45 | class Utility { 46 | 47 | /** @var \TYPO3\CMS\Core\Charset\CharsetConverter */ 48 | protected $csConvertor; 49 | 50 | /** @var ConfigurationReader */ 51 | protected $configuration; 52 | 53 | /** @var \TYPO3\CMS\Core\Database\DatabaseConnection */ 54 | protected $databaseConnection; 55 | 56 | /** 57 | * Initializes the class. 58 | * 59 | * @param ConfigurationReader $configuration 60 | */ 61 | public function __construct(ConfigurationReader $configuration) { 62 | $this->csConvertor = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Charset\\CharsetConverter'); 63 | $this->configuration = $configuration; 64 | $this->databaseConnection = $GLOBALS['TYPO3_DB']; 65 | } 66 | 67 | /** 68 | * Checks if required update should run and runs it if necessary. 69 | * 70 | * @return void 71 | */ 72 | static public function checkAndPerformRequiredUpdates() { 73 | $currentUpdateLevel = 6; 74 | 75 | $registry = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Registry'); 76 | /** @var \TYPO3\CMS\Core\Registry $registry */ 77 | $updateLevel = (int)$registry->get('tx_realurl', 'updateLevel', 0); 78 | if ($updateLevel < $currentUpdateLevel) { 79 | // Change it at once in order to prevent parallel updates 80 | $registry->set('tx_realurl', 'updateLevel', $currentUpdateLevel); 81 | 82 | /** @noinspection PhpIncludeInspection */ 83 | require_once(ExtensionManagementUtility::extPath('realurl', 'class.ext_update.php')); 84 | $updater = GeneralUtility::makeInstance('DmitryDulepov\\Realurl\\ext_update'); 85 | /** @var \DmitryDulepov\Realurl\ext_update $updater */ 86 | if ($updater->access()) { 87 | $updater->main(); 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * Converts a given string to a string that can be used as a URL segment. 94 | * The result is not url-encoded. 95 | * 96 | * @param string $processedTitle 97 | * @param string $spaceCharacter 98 | * @param bool $strToLower 99 | * @return string 100 | */ 101 | public function convertToSafeString($processedTitle, $spaceCharacter = '-', $strToLower = true) { 102 | $encodingConfiguration = array('strtolower' => $strToLower, 'spaceCharacter' => $spaceCharacter); 103 | 104 | // Pre-processing hook 105 | if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['realurl']['convertToSafeString_preProc'])) { 106 | foreach($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['realurl']['convertToSafeString_preProc'] as $userFunc) { 107 | if (is_string($userFunc)) { 108 | $hookParams = array('pObj' => &$this, 'processedTitle' => $processedTitle, 'encodingConfiguration' => $encodingConfiguration); 109 | $processedTitle = GeneralUtility::callUserFunction($userFunc, $hookParams, $this); 110 | } 111 | } 112 | } 113 | 114 | if ($strToLower) { 115 | $processedTitle = mb_strtolower($processedTitle, 'UTF-8'); 116 | } 117 | $processedTitle = strip_tags($processedTitle); 118 | $processedTitle = preg_replace('/[ \t\x{00A0}\-+_]+/u', $spaceCharacter, $processedTitle); 119 | $processedTitle = $this->csConvertor->specCharsToASCII('utf-8', $processedTitle); 120 | $processedTitle = preg_replace('/[^\p{L}0-9' . preg_quote($spaceCharacter) . ']/u', '', $processedTitle); 121 | $processedTitle = preg_replace('/' . preg_quote($spaceCharacter) . '{2,}/', $spaceCharacter, $processedTitle); 122 | $processedTitle = trim($processedTitle, $spaceCharacter); 123 | 124 | // Post-processing hook 125 | if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['realurl']['convertToSafeString_postProc'])) { 126 | foreach($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['realurl']['convertToSafeString_postProc'] as $userFunc) { 127 | if (is_string($userFunc)) { 128 | $hookParams = array('pObj' => &$this, 'processedTitle' => $processedTitle, 'encodingConfiguration' => $encodingConfiguration); 129 | $processedTitle = GeneralUtility::callUserFunction($userFunc, $hookParams, $this); 130 | } 131 | } 132 | } 133 | 134 | if ($strToLower) { 135 | $processedTitle = strtolower($processedTitle); 136 | } 137 | 138 | return $processedTitle; 139 | } 140 | 141 | /** 142 | * Returns the cache to use. 143 | * 144 | * @return CacheInterface 145 | */ 146 | public function getCache() { 147 | $cache = CacheFactory::getCache(); 148 | 149 | return $cache; 150 | } 151 | 152 | /** 153 | * Obtains the current host. 154 | * 155 | * @return string 156 | */ 157 | public function getCurrentHost() { 158 | static $cachedHost = null; 159 | 160 | if ($cachedHost === null) { 161 | $currentHost = (string)GeneralUtility::getIndpEnv('HTTP_HOST'); 162 | 163 | // Call user hooks 164 | if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['realurl']['getHost'])) { 165 | foreach($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['realurl']['getHost'] as $userFunc) { 166 | $hookParams = array( 167 | 'host' => $currentHost, 168 | ); 169 | $newHost = GeneralUtility::callUserFunction($userFunc, $hookParams, $this); 170 | if (!empty($newHost) && is_string($newHost)) { 171 | $currentHost = $newHost; 172 | } 173 | } 174 | } 175 | $cachedHost = $currentHost; 176 | } 177 | 178 | return $cachedHost; 179 | } 180 | 181 | /** 182 | * Dumps function arguments in a log-friendly way. 183 | * 184 | * @param array $arguments 185 | * @return string 186 | */ 187 | protected function dumpFunctionArguments(array $arguments) { 188 | $dumpedArguments = array(); 189 | foreach ($arguments as $argument) { 190 | if (is_numeric($argument)) { 191 | $dumpedArguments[] = $argument; 192 | } elseif (is_string($argument)) { 193 | if (strlen($argument) > 80) { 194 | $argument = substr($argument, 0, 30) . '...'; 195 | } 196 | $argument = addslashes($argument); 197 | $argument = preg_replace('/\r/', '\r', $argument); 198 | $argument = preg_replace('/\n/', '\n', $argument); 199 | $dumpedArguments[] = '\'' . $argument . '\''; 200 | } 201 | elseif (is_null($argument)) { 202 | $dumpedArguments[] = 'null'; 203 | } 204 | elseif (is_object($argument)) { 205 | $dumpedArguments[] = get_class($argument); 206 | } 207 | elseif (is_array($argument)) { 208 | $dumpedArguments[] = 'array(' . (count($arguments) ? '...' : '') . ')'; 209 | } 210 | else { 211 | $dumpedArguments[] = gettype($argument); 212 | } 213 | } 214 | 215 | return '(' . implode(', ', $dumpedArguments) . ')'; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /Classes/ViewHelpers/LanguageFromIdViewHelper.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper; 27 | 28 | class LanguageFromIdViewHelper extends AbstractViewHelper { 29 | 30 | static protected $languageCache = array(); 31 | 32 | /** 33 | * Obtains language title from its id. 34 | * 35 | * @param string $language 36 | * @return string 37 | */ 38 | public function render($language) { 39 | if ($language == 0) { 40 | $result = $GLOBALS['LANG']->sL('LLL:EXT:realurl/Resources/Private/Language/locallang.xlf:viewhelper.languageFromId.defaultLanguage'); 41 | } 42 | else { 43 | if (!isset(self::$languageCache[$language])) { 44 | /** @noinspection PhpUndefinedMethodInspection */ 45 | $record = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', 'sys_language', 'uid=' . (int)$language); 46 | self::$languageCache[$language] = is_array($record) ? $record['title'] : '(' . $language . ')'; 47 | } 48 | $result = self::$languageCache[$language]; 49 | } 50 | 51 | return $result; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Classes/ViewHelpers/SetVariableViewHelper.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | 26 | use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper; 27 | 28 | /** 29 | * This class provides a viewhelper to set the variable from the fluid template. 30 | * It is an exact copy of EXT:vhs'es . Thanks to Claus Due! 31 | * 32 | * @author Claus Due 33 | * @author Dmitry Dulepov 34 | */ 35 | class SetVariableViewHelper extends AbstractViewHelper { 36 | 37 | /** 38 | * Set (override) the variable in $name. 39 | * 40 | * @param string $name 41 | * @param mixed $value 42 | * @return void 43 | */ 44 | public function render($name, $value = NULL) { 45 | if ($value === NULL) { 46 | $value = $this->renderChildren(); 47 | } 48 | if (FALSE === strpos($name, '.')) { 49 | if ($this->templateVariableContainer->exists($name) === TRUE) { 50 | $this->templateVariableContainer->remove($name); 51 | } 52 | $this->templateVariableContainer->add($name, $value); 53 | } elseif (1 == substr_count($name, '.')) { 54 | $parts = explode('.', $name); 55 | $objectName = array_shift($parts); 56 | $path = implode('.', $parts); 57 | if (FALSE === $this->templateVariableContainer->exists($objectName)) { 58 | return NULL; 59 | } 60 | $object = $this->templateVariableContainer->get($objectName); 61 | try { 62 | \TYPO3\CMS\Extbase\Reflection\ObjectAccess::setProperty($object, $path, $value); 63 | // Note: re-insert the variable to ensure unreferenced values like arrays also get updated 64 | $this->templateVariableContainer->remove($objectName); 65 | $this->templateVariableContainer->add($objectName, $object); 66 | } catch (\Exception $error) { 67 | return NULL; 68 | } 69 | } 70 | return NULL; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Classes/ViewHelpers/TranslateToJsonViewHelper.php: -------------------------------------------------------------------------------- 1 | 8 | * All rights reserved 9 | * 10 | * This script is part of the TYPO3 project. The TYPO3 project is 11 | * free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * The GNU General Public License can be found at 17 | * http://www.gnu.org/copyleft/gpl.html. 18 | * 19 | * This script is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * This copyright notice MUST APPEAR in all copies of the script! 25 | ***************************************************************/ 26 | 27 | use TYPO3\CMS\Extbase\Utility\LocalizationUtility; 28 | use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper; 29 | 30 | class TranslateToJsonViewHelper extends AbstractViewHelper { 31 | 32 | /** 33 | * @var boolean 34 | */ 35 | protected $escapeOutput = FALSE; 36 | 37 | /** 38 | * Renders the translation and encodes to json string. 39 | * 40 | * @param string $key Translation Key 41 | * @return string The translated key or tag body if key doesn't exist 42 | */ 43 | public function render($key) { 44 | $result = LocalizationUtility::translate($key, 'realurl'); 45 | 46 | return json_encode($result); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Configuration/TCA/Overrides/pages.php: -------------------------------------------------------------------------------- 1 | array( 7 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:pages.tx_realurl_pathsegment', 8 | 'exclude' => 1, 9 | 'config' => array ( 10 | 'type' => 'input', 11 | 'max' => 255, 12 | 'default' => '', 13 | 'eval' => 'trim,nospace,lower,DmitryDulepov\\Realurl\\Evaluator\\SegmentFieldCleaner' 14 | ), 15 | ), 16 | 'tx_realurl_pathoverride' => array( 17 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:pages.tx_realurl_path_override', 18 | 'exclude' => 1, 19 | 'config' => array ( 20 | 'type' => 'check', 21 | 'default' => 0, 22 | 'items' => array( 23 | array('LLL:EXT:lang/locallang_core.xlf:labels.enabled', '') 24 | ) 25 | ) 26 | ), 27 | 'tx_realurl_exclude' => array( 28 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:pages.tx_realurl_exclude', 29 | 'exclude' => 1, 30 | 'config' => array ( 31 | 'type' => 'check', 32 | 'default' => 0, 33 | 'items' => array( 34 | array('LLL:EXT:lang/locallang_core.xlf:labels.enabled', '') 35 | ) 36 | ) 37 | ), 38 | )); 39 | 40 | \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes('pages', '--palette--;LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:pages.palette_title;tx_realurl', '1,3', 'after:nav_title'); 41 | \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes('pages', '--palette--;LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:pages.palette_title;tx_realurl', '4,7,199,254', 'after:title'); 42 | 43 | $GLOBALS['TCA']['pages']['palettes']['tx_realurl'] = array( 44 | 'showitem' => 'tx_realurl_pathsegment,--linebreak--,tx_realurl_exclude,tx_realurl_pathoverride' 45 | ); 46 | 47 | // Make sure that speaking path and related options are not set when copying pages -- thanks to University of Basel EasyWeb team for the bug report! 48 | if (!empty($GLOBALS['TCA']['pages']['ctrl']['setToDefaultOnCopy'])) { 49 | $GLOBALS['TCA']['pages']['ctrl']['setToDefaultOnCopy'] .= ','; 50 | } 51 | $GLOBALS['TCA']['pages']['ctrl']['setToDefaultOnCopy'] .= 'tx_realurl_pathsegment,tx_realurl_pathoverride,tx_realurl_exclude'; 52 | } 53 | -------------------------------------------------------------------------------- /Configuration/TCA/Overrides/pages_language_overlay.php: -------------------------------------------------------------------------------- 1 | array( 6 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:pages.tx_realurl_pathsegment', 7 | 'exclude' => 1, 8 | 'config' => array( 9 | 'type' => 'input', 10 | 'max' => 255, 11 | 'default' => '', 12 | 'eval' => 'trim,nospace,lower,DmitryDulepov\\Realurl\\Evaluator\\SegmentFieldCleaner' 13 | ), 14 | ), 15 | )); 16 | 17 | \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes('pages_language_overlay', 'tx_realurl_pathsegment', '1,3,4,6,7', 'after:nav_title'); 18 | \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes('pages_language_overlay', 'tx_realurl_pathsegment', '254', 'after:title'); 19 | 20 | // Make sure that speaking path and related options are not set when copying pages -- thanks to University of Basel EasyWeb team for the bug report! 21 | if (!empty($GLOBALS['TCA']['pages_language_overlay']['ctrl']['setToDefaultOnCopy'])) { 22 | $GLOBALS['TCA']['pages_language_overlay']['ctrl']['setToDefaultOnCopy'] .= ','; 23 | } 24 | $GLOBALS['TCA']['pages_language_overlay']['ctrl']['setToDefaultOnCopy'] .= 'tx_realurl_pathsegment'; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Configuration/TCA/tx_realurl_pathdata.php: -------------------------------------------------------------------------------- 1 | 6 | * All rights reserved 7 | * 8 | * This script is part of the TYPO3 project. The TYPO3 project is 9 | * free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * The GNU General Public License can be found at 15 | * http://www.gnu.org/copyleft/gpl.html. 16 | * 17 | * This script is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * 22 | * This copyright notice MUST APPEAR in all copies of the script! 23 | ***************************************************************/ 24 | 25 | $GLOBALS['TCA']['tx_realurl_pathdata'] = array( 26 | 'ctrl' => array( 27 | 'title' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_pathdata', 28 | 'label' => 'pagepath', 29 | 'iconfile' => 'EXT:realurl/Resources/Public/Icons/Extension.svg', 30 | 'hideTable' => 1, 31 | 'rootLevel' => 1, 32 | ), 33 | 'columns' => array( 34 | 'page_id' => array( 35 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_pathdata.page_id', 36 | 'config' => array( 37 | 'type' => 'input', 38 | 'eval' => 'int,required', 39 | ) 40 | ), 41 | 'rootpage_id' => array( 42 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_pathdata.rootpage_id', 43 | 'config' => array( 44 | 'type' => 'input', 45 | 'eval' => 'int,required', 46 | ) 47 | ), 48 | 'language_id' => array( 49 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_pathdata.language_id', 50 | 'config' => array( 51 | 'type' => 'input', 52 | 'eval' => 'int,required', 53 | ) 54 | ), 55 | 'mpvar' => array( 56 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_pathdata.mpvar', 57 | 'config' => array( 58 | 'type' => 'input', 59 | 'eval' => 'trim', 60 | ) 61 | ), 62 | 'pagepath' => array( 63 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_pathdata.pagepath', 64 | 'config' => array( 65 | 'type' => 'input', 66 | 'eval' => 'trim,required', 67 | ) 68 | ), 69 | 'expire' => array( 70 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_pathdata.expire', 71 | 'config' => array( 72 | 'type' => 'input', 73 | 'renderType' => 'inputDateTime', 74 | 'eval' => 'datetime', 75 | 'default' => 0, 76 | ) 77 | ), 78 | ), 79 | 'types' => array( 80 | 0 => array( 81 | 'showitem' => '', 82 | ), 83 | ), 84 | ); 85 | -------------------------------------------------------------------------------- /Configuration/TCA/tx_realurl_uniqalias.php: -------------------------------------------------------------------------------- 1 | 6 | * All rights reserved 7 | * 8 | * This script is part of the TYPO3 project. The TYPO3 project is 9 | * free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * The GNU General Public License can be found at 15 | * http://www.gnu.org/copyleft/gpl.html. 16 | * 17 | * This script is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * 22 | * This copyright notice MUST APPEAR in all copies of the script! 23 | ***************************************************************/ 24 | 25 | $GLOBALS['TCA']['tx_realurl_uniqalias'] = array( 26 | 'ctrl' => array( 27 | 'title' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_uniqalias', 28 | 'label' => 'value_alias', 29 | 'iconfile' => 'EXT:realurl/Resources/Public/Icons/Extension.svg', 30 | 'hideTable' => 1, 31 | 'rootLevel' => 1, 32 | ), 33 | 'columns' => array( 34 | 'expire' => array( 35 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_uniqalias.expire', 36 | 'config' => array( 37 | 'type' => 'input', 38 | 'renderType' => 'inputDateTime', 39 | 'eval' => 'datetime', 40 | 'default' => 0, 41 | ) 42 | ), 43 | 'lang' => array( 44 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_uniqalias.lang', 45 | 'config' => array( 46 | 'type' => 'input', 47 | 'eval' => 'int,required', 48 | 'default' => 0, 49 | ) 50 | ), 51 | 'tablename' => array( 52 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_uniqalias.tablename', 53 | 'config' => array( 54 | 'type' => 'input', 55 | 'eval' => 'required', 56 | ) 57 | ), 58 | 'value_alias' => array( 59 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_uniqalias.value_alias', 60 | 'config' => array( 61 | 'type' => 'input', 62 | 'eval' => 'required', 63 | ) 64 | ), 65 | 'value_id' => array( 66 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_uniqalias.value_id', 67 | 'config' => array( 68 | 'type' => 'input', 69 | 'eval' => 'int,required', 70 | ) 71 | ), 72 | 'field_alias' => array( 73 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_uniqalias.field_alias', 74 | 'config' => array( 75 | 'type' => 'input', 76 | 'eval' => 'required', 77 | ) 78 | ), 79 | 'field_id' => array( 80 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_uniqalias.field_id', 81 | 'config' => array( 82 | 'type' => 'input', 83 | 'eval' => 'required', 84 | ) 85 | ), 86 | ), 87 | 'types' => array( 88 | 'types' => array( 89 | 0 => array( 90 | 'showitem' => '', 91 | ), 92 | ), 93 | ), 94 | ); -------------------------------------------------------------------------------- /Configuration/TCA/tx_realurl_urldata.php: -------------------------------------------------------------------------------- 1 | 6 | * All rights reserved 7 | * 8 | * This script is part of the TYPO3 project. The TYPO3 project is 9 | * free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * The GNU General Public License can be found at 15 | * http://www.gnu.org/copyleft/gpl.html. 16 | * 17 | * This script is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * 22 | * This copyright notice MUST APPEAR in all copies of the script! 23 | ***************************************************************/ 24 | 25 | $GLOBALS['TCA']['tx_realurl_urldata'] = array( 26 | 'ctrl' => array( 27 | 'title' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_urldata', 28 | 'label' => 'speaking_url', 29 | 'iconfile' => 'EXT:realurl/Resources/Public/Icons/Extension.svg', 30 | 'hideTable' => 1, 31 | 'rootLevel' => 1, 32 | ), 33 | 'columns' => array( 34 | 'page_id' => array( 35 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_urldata.page_id', 36 | 'config' => array( 37 | 'type' => 'input', 38 | 'eval' => 'int,required', 39 | ) 40 | ), 41 | 'rootpage_id' => array( 42 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_urldata.rootpage_id', 43 | 'config' => array( 44 | 'type' => 'input', 45 | 'eval' => 'int,required', 46 | ) 47 | ), 48 | 'original_url' => array( 49 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_urldata.original_url', 50 | 'config' => array( 51 | 'type' => 'input', 52 | 'eval' => 'trim,required', 53 | ) 54 | ), 55 | 'speaking_url' => array( 56 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_urldata.speaking_url', 57 | 'config' => array( 58 | 'type' => 'input', 59 | 'eval' => 'trim,required', 60 | ) 61 | ), 62 | 'request_variables' => array( 63 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_urldata.request_variables', 64 | 'config' => array( 65 | 'type' => 'input', 66 | ) 67 | ), 68 | 'expire' => array( 69 | 'label' => 'LLL:EXT:realurl/Resources/Private/Language/locallang_db.xlf:tx_realurl_urldata.expire', 70 | 'config' => array( 71 | 'type' => 'input', 72 | 'renderType' => 'inputDateTime', 73 | 'eval' => 'datetime', 74 | 'default' => 0, 75 | ) 76 | ), 77 | ), 78 | 'types' => array( 79 | 0 => array( 80 | 'showitem' => '', 81 | ), 82 | ), 83 | ); 84 | -------------------------------------------------------------------------------- /Configuration/TSConfig.txt: -------------------------------------------------------------------------------- 1 | TCEFORM { 2 | be_groups { 3 | tables_select { 4 | removeItems := addToList(tx_realurl_uniqalias,tx_realurl_urldata,tx_realurl_pathdata) 5 | } 6 | tables_modify { 7 | removeItems := addToList(tx_realurl_uniqalias,tx_realurl_urldata,tx_realurl_pathdata) 8 | } 9 | } 10 | sys_collection { 11 | table_name { 12 | removeItems := addToList(tx_realurl_uniqalias,tx_realurl_urldata,tx_realurl_pathdata) 13 | } 14 | } 15 | index_config { 16 | table2index { 17 | removeItems := addToList(tx_realurl_uniqalias,tx_realurl_urldata,tx_realurl_pathdata) 18 | } 19 | } 20 | sys_action { 21 | t3_tables { 22 | removeItems := addToList(tx_realurl_uniqalias,tx_realurl_urldata,tx_realurl_pathdata) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RealURL extension for TYPO3 CMS 2 | 3 | This is version 2.x of the famous RealURL extension for TYPO3 CMS. This version is rewritten from scratch and will work only with TYPO3 6.2 or newer. 4 | 5 | Repository, bug reporting, pull requests, etc is handled via [github project](https://github.com/dmitryd/typo3-realurl/) 6 | 7 | Notes: 8 | * TYPO3 6.2 is supported partially: Backend modules work only with TYPO3 ≥ 7.6 9 | * TYPO3 7.6 and 8.7 are supported fully 10 | * Anything newer than TYPO3 8.7.9999 is not supported and will never be supported. Don't even try. 11 | * If you find errors, please, report them to the [issue tracker](https://github.com/dmitryd/typo3-realurl/issues). This place is **only** for bugs. Use TYPO3 Slack (#realurl channel) for questions! 12 | * You are strongly advised to have "Is root page?" set in page properties on all your root pages. Otherwise it is your own fault :) 13 | * If you have questions like "Why did you...", please, read the Developer's page on the Wiki first. 14 | * If you want to contribute, you are welcome! Please, read "Contribution" page in the documentation. 15 | * If you think you found a bug, in 99% of cases it means you did not read docs. Wiki pages are available. RTFM first! :) 16 | 17 | *** 18 | 19 | Since TYPO3 6.2, 7.6 and 8.7 are EOL now, this extension is going to retire. What does it mean? 20 | 21 | 1. There will be no new features anymore unless they are sponsored. 22 | 1. There will be bug fixes from time to time. Also you can [sponsor](mailto:dmitry.dulepov@gmail.com) a bug fix if you really need it. Otherwise you can 23 | add it to the issue tracker and hope to be fixed :) 24 | 25 | I am sorry but after developing and supporting this most popular TYPO3 extension mostly for free since 2006, 26 | I have to stop at some point. 27 | 28 | *** 29 | 30 | [Documentation](https://github.com/dmitryd/typo3-realurl/wiki) 31 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Speaking URLs 8 | 9 | 10 | Speaking URL management 11 | 12 | 13 | Management for speaking URLs in TYPO3 CMS 14 | 15 | 16 | Overview 17 | 18 | 19 | This is a RealURL administration module. It provides functions that you can select from the selector at the top. Here is the overview of the provided functions. 20 | 21 | 22 | Aliases 23 | 24 | 25 | Aliases 26 | 27 | 28 | This module manages aliases that are created for items like news or addresses or any other custom types of data. 29 | 30 | 31 | Currently there are no aliases available. 32 | 33 | 34 | The sellected alias is no longer available. 35 | 36 | 37 | Available aliases 38 | 39 | 40 | Select alias type: 41 | 42 | 43 | The alias was deleted. 44 | 45 | 46 | Aliases were deleted. 47 | 48 | 49 | Save 50 | 51 | 52 | Cancel 53 | 54 | 55 | Alias value: 56 | 57 | 58 | Table name: 59 | 60 | 61 | Record uid: 62 | 63 | 64 | Field value: 65 | 66 | 67 | Alias successfully updated. 68 | 69 | 70 | Such alias already exists for another record in this language. 71 | 72 | 73 | Alias value may only contain the following characters: %s 74 | 75 | 76 | Search alias value for: 77 | 78 | 79 | Submit 80 | 81 | 82 | Reset 83 | 84 | 85 | URL Data 86 | 87 | 88 | extremely dangerous to manipulate these entries manually! Your installation can be broken if you do so.]]> 89 | 90 | 91 | Path Data 92 | 93 | 94 | extremely dangerous to manipulate these entries manually! Your installation can be broken if you do so.]]> 95 | 96 | 97 | Configuration 98 | 99 | 100 |

Note: your TYPO3 solution provider may use its own ways to configure RealURL at runtime. If that's the case, this module may not show confguration accurately. If you suspect that this is the case, contact your TYPO3 solution provider.]]> 101 | 102 | 103 | d.m.Y H:i 104 | 105 | 106 | Alias 107 | 108 | 109 | Id 110 | 111 | 112 | Language 113 | 114 | 115 | Expires 116 | 117 | 118 | Default 119 | 120 | 121 | Delete all aliases of this type 122 | 123 | 124 | Do you want to delete this alias? 125 | 126 | 127 | Yes 128 | 129 | 130 | No 131 | 132 | 133 | Confirm 134 | 135 | 136 | Do you want to delete all aliases of this type? 137 | 138 | 139 | Speaking URL 140 | 141 | 142 | Original URL 143 | 144 | 145 | Root page id 146 | 147 | 148 | Page path 149 | 150 | 151 | Mount point 152 | 153 | 154 | Do you want to delete this entry? This can be harmful for your installation! 155 | 156 | 157 | Do you want to delete all entries for this page? This can be harmful for your installation! 158 | 159 | 160 | Delete all entries for this page (harmful!) 161 | 162 | 163 | Flush all entries (harmful!) 164 | 165 | 166 | Do you want to remove all entries? This WILL cause performance issues with the web site and can make some pages inaccessible! 167 | 168 | 169 | Entry was removed. 170 | 171 | 172 | All entries for this page were removed. 173 | 174 | 175 | All cache entries were removed. 176 | 177 | 178 | Entry was removed. 179 | 180 | 181 | URL conflict detected: '%s' is also used on page(s): %s 182 | 183 | 184 | Path conflict detected: '%s' is also used on page(s): %s 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang_db.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | 6 | 7 | Speaking URL path segment 8 | 9 | 10 | Exclude from speaking URL 11 | 12 | 13 | Override the whole page path 14 | 15 | 16 | Speaking URL 17 | 18 | 19 | RealURL Path Data 20 | 21 | 22 | Page id: 23 | 24 | 25 | Root page id: 26 | 27 | 28 | Language id: 29 | 30 | 31 | MP parameter: 32 | 33 | 34 | Page path: 35 | 36 | 37 | Expires: 38 | 39 | 40 | RealURL Unique Alias 41 | 42 | 43 | Expires: 44 | 45 | 46 | Language id: 47 | 48 | 49 | Table name: 50 | 51 | 52 | Value alias: 53 | 54 | 55 | Value id: 56 | 57 | 58 | Field alias: 59 | 60 | 61 | Field id: 62 | 63 | 64 | RealURL URL Data 65 | 66 | 67 | Page id: 68 | 69 | 70 | Root page id: 71 | 72 | 73 | Original URL: 74 | 75 | 76 | Speaking URL: 77 | 78 | 79 | Request variables: 80 | 81 | 82 | Expires: 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /Resources/Private/Layouts/Backend.html: -------------------------------------------------------------------------------- 1 | {namespace d=DmitryDulepov\Realurl\ViewHelpers} 2 | 3 | 4 | 9 |
10 |
11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 | 59 |
60 |
61 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Aliases/Edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 |

6 | 7 | 8 | 9 |
10 | 11 | 12 |

13 |
14 | 15 |
16 |

{alias.tablename}

17 |
18 |
19 |
20 | 21 |
22 |

{alias.valueId}

23 |
24 |
25 |
26 | 27 |
28 |

{alias.recordFieldValue}

29 |
30 |
31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 |
43 |
44 |
45 | 46 |
-------------------------------------------------------------------------------- /Resources/Private/Templates/Aliases/Index.html: -------------------------------------------------------------------------------- 1 | {namespace d=DmitryDulepov\Realurl\ViewHelpers} 2 | {namespace c=TYPO3\CMS\Core\ViewHelpers} 3 | 4 | 5 | 6 | 7 |

8 |

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 |
28 |

29 |
30 | 31 |
32 | 38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 |
46 | 47 |
48 | 49 |
50 | 51 |
52 |
53 |
54 |
55 |
56 | 57 | 58 |
59 |
60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
86 |
87 | 89 | 90 | 91 | 92 |
93 |
94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 104 | 105 | 106 | 107 | {alias.valueAlias} 108 | {alias.valueId} 109 | {d:languageFromId(language: alias.lang)} 110 | 111 | 112 | 113 | {alias.expire} 114 | 115 | 116 | – 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /Resources/Private/Templates/Overview/Index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 |

6 |

7 |

8 | 9 |

10 |

11 |

12 |

13 |
14 | 15 |

16 |

17 |
18 |
-------------------------------------------------------------------------------- /Resources/Private/Templates/PathCache/Index.html: -------------------------------------------------------------------------------- 1 | {namespace d=DmitryDulepov\Realurl\ViewHelpers} 2 | {namespace c=TYPO3\CMS\Core\ViewHelpers} 3 | 4 | 5 | 6 | 7 |

8 |

9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | 49 | 50 | 51 | 52 | {cacheEntry.pagePath} 53 | 54 | 55 | 56 | {cacheEntry.mpVar} 57 | 58 | 59 | – 60 | 61 | 62 | 63 | 64 | 65 | 66 | {cacheEntry.expire} 67 | 68 | 69 | – 70 | 71 | 72 | 73 | {d:languageFromId(language: cacheEntry.languageId)} 74 | {cacheEntry.rootPageId} 75 | 76 | 77 | -------------------------------------------------------------------------------- /Resources/Private/Templates/UrlCache/Index.html: -------------------------------------------------------------------------------- 1 | {namespace d=DmitryDulepov\Realurl\ViewHelpers} 2 | {namespace c=TYPO3\CMS\Core\ViewHelpers} 3 | 4 | 5 | 6 | 7 |

8 |

9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 |   50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 |
59 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | 69 | {cacheEntry.speakingUrl} 70 | {cacheEntry.originalUrl} 71 | 72 | 73 | 74 | {cacheEntry.expire} 75 | 76 | 77 | – 78 | 79 | 80 | 81 | {cacheEntry.rootPageId} 82 | 83 | 84 | -------------------------------------------------------------------------------- /Resources/Public/Icons/Extension.svg: -------------------------------------------------------------------------------- 1 | Extension -------------------------------------------------------------------------------- /Resources/Public/Icons/Module.svg: -------------------------------------------------------------------------------- 1 | Module -------------------------------------------------------------------------------- /Resources/Public/Icons/Module2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | ]> 6 | 10 | 11 | 12 | 13 | 14 | 28 | 29 | -------------------------------------------------------------------------------- /Resources/Public/Icons/Module3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | ]> 6 | 10 | 11 | 12 | 13 | 14 | 31 | 32 | -------------------------------------------------------------------------------- /Resources/Public/realurl_be.css: -------------------------------------------------------------------------------- 1 | .realurl-backend .icon-column { 2 | white-space: nowrap; 3 | } -------------------------------------------------------------------------------- /Resources/Public/realurl_be.js: -------------------------------------------------------------------------------- 1 | ;(function($) { 2 | // Alias selector 3 | $(document).ready(function() { 4 | $('#realurlSelectedAlias').on('change', function() { 5 | $('#realurlAliasSelectionForm').submit(); 6 | }); 7 | }); 8 | 9 | // Confirmation box 10 | window.tx_realurl_confirm = function(title, message, url) { 11 | top.TYPO3.Modal.confirm(title, message).on('button.clicked', function(e) { 12 | if (e.target.name == 'ok') { 13 | document.location.href = url; 14 | } 15 | top.TYPO3.Modal.dismiss(); 16 | }); 17 | return false; 18 | } 19 | })(TYPO3.jQuery || jQuery); 20 | -------------------------------------------------------------------------------- /class.ext_update.php: -------------------------------------------------------------------------------- 1 | 7 | * All rights reserved 8 | * 9 | * This script is part of the TYPO3 project. The TYPO3 project is 10 | * free software; you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation; either version 2 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * The GNU General Public License can be found at 16 | * http://www.gnu.org/copyleft/gpl.html. 17 | * 18 | * This script is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * This copyright notice MUST APPEAR in all copies of the script! 24 | ***************************************************************/ 25 | use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; 26 | use TYPO3\CMS\Core\Utility\GeneralUtility; 27 | 28 | /** 29 | * This class updates realurl from version 1.x to 2.x. 30 | * 31 | * @author Dmitry Dulepov 32 | */ 33 | class ext_update { 34 | 35 | /** Defines characters that cannot appear in the tx_realurl_data.spealing_url field */ 36 | const MYSQL_REGEXP_FOR_NON_URL_CHARACTERS = '[^a-zA-Z0-9\._%\-/\?=]'; 37 | 38 | /** @var \TYPO3\CMS\Core\Database\DatabaseConnection */ 39 | protected $databaseConnection; 40 | 41 | /** 42 | * Creates the instance of the class. 43 | */ 44 | public function __construct() { 45 | $this->databaseConnection = $GLOBALS['TYPO3_DB']; 46 | } 47 | 48 | /** 49 | * Runs the update. 50 | */ 51 | public function main() { 52 | $locker = $this->getLocker(); 53 | try { 54 | if ($locker) { 55 | $locker->acquire(); 56 | } 57 | } 58 | catch (\Exception $e) { 59 | // Nothing 60 | } 61 | 62 | $this->checkAndRenameTables(); 63 | $this->checkAndUpdatePathCachePrimaryKey(); 64 | $this->updateRealurlTableStructure(); 65 | $this->removeUrlDataEntriesWithIgnoredParameters(); 66 | $this->updateCJKSpeakingUrls(); 67 | $this->updateUrlHashes(); 68 | $this->updateZeroPids(); 69 | 70 | if ($locker && (method_exists($locker, 'isAcquired') && $locker->isAcquired() || method_exists($locker, 'getLockStatus') && $locker->getLockStatus())) { 71 | $locker->release(); 72 | } 73 | } 74 | 75 | /** 76 | * Checks if the script should execute. We check for everything except table 77 | * structure. 78 | * 79 | * @return bool 80 | */ 81 | public function access() { 82 | return $this->hasOldCacheTables() || $this->pathCacheNeedsUpdates() || $this->hasUnencodedCJKCharacters() || 83 | $this->hasEmptyUrlHashes() || $this->hasNonZeroPids() 84 | ; 85 | } 86 | 87 | /** 88 | * Checks and renames *cache tables to *data tables. 89 | */ 90 | protected function checkAndRenameTables() { 91 | $tableMap = array( 92 | 'tx_realurl_pathcache' => 'tx_realurl_pathdata', 93 | 'tx_realurl_urlcache' => 'tx_realurl_urldata', 94 | ); 95 | 96 | $tables = $this->databaseConnection->admin_get_tables(); 97 | foreach ($tableMap as $oldTableName => $newTableName) { 98 | if (isset($tables[$oldTableName])) { 99 | if (!isset($tables[$newTableName])) { 100 | $this->databaseConnection->sql_query('ALTER TABLE ' . $oldTableName . ' RENAME TO ' . $newTableName); 101 | } 102 | else { 103 | if ((int)$tables[$newTableName]['Rows'] === 0) { 104 | $this->databaseConnection->sql_query('DROP TABLE ' . $newTableName); 105 | $this->databaseConnection->sql_query('CREATE TABLE ' . $newTableName . ' LIKE ' . $oldTableName); 106 | $this->databaseConnection->sql_query('INSERT INTO ' . $newTableName . ' SELECT * FROM ' . $oldTableName); 107 | } 108 | $this->databaseConnection->sql_query('DROP TABLE ' . $oldTableName); 109 | } 110 | } 111 | } 112 | } 113 | 114 | /** 115 | * Checks if the primary key needs updates (this is something that TYPO3 116 | * sql parser fails to do for years) and does necessary changes. 117 | * 118 | * @return void 119 | */ 120 | protected function checkAndUpdatePathCachePrimaryKey() { 121 | if ($this->pathCacheNeedsUpdates()) { 122 | $this->databaseConnection->sql_query('ALTER TABLE tx_realurl_pathdata CHANGE cache_id uid int(11) NOT NULL'); 123 | $this->databaseConnection->sql_query('ALTER TABLE tx_realurl_pathdata DROP PRIMARY KEY'); 124 | $this->databaseConnection->sql_query('ALTER TABLE tx_realurl_pathdata MODIFY uid int(11) NOT NULL auto_increment PRIMARY KEY'); 125 | } 126 | } 127 | 128 | /** 129 | * Obtains the locker depending on the TYPO3 version. 130 | * 131 | * @return \TYPO3\CMS\Core\Locking\Locker|\TYPO3\CMS\Core\Locking\LockingStrategyInterface 132 | */ 133 | protected function getLocker() { 134 | if (class_exists('\\TYPO3\\CMS\\Core\\Locking\\LockFactory')) { 135 | $locker = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Locking\\LockFactory')->createLocker('tx_realurl_update'); 136 | } 137 | elseif (class_exists('\\TYPO3\\CMS\\Core\\Locking\\Locker')) { 138 | $locker = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Locking\\Locker', 'tx_realurl_update'); 139 | } 140 | else { 141 | $locker = null; 142 | } 143 | 144 | return $locker; 145 | } 146 | 147 | /** 148 | * Checks if the database has empty url hashes. 149 | * 150 | * @return bool 151 | */ 152 | protected function hasEmptyUrlHashes() { 153 | $count = $this->databaseConnection->exec_SELECTcountRows( 154 | '*', 155 | 'tx_realurl_urldata', 156 | 'original_url_hash=0 OR speaking_url_hash=0' 157 | ); 158 | 159 | // If $count is false, it means that fields are missing. So we force 160 | // the update because table structure will be also fixed by the updater. 161 | 162 | return $count === false || $count > 0; 163 | } 164 | 165 | /** 166 | * Checks if the system has old cache tables. 167 | * 168 | * @return bool 169 | */ 170 | protected function hasOldCacheTables() { 171 | $tables = $this->databaseConnection->admin_get_tables(); 172 | return isset($tables['tx_realurl_pathcache']) || isset($tables['tx_realurl_urlcache']); 173 | } 174 | 175 | /** 176 | * Checks if tx_realurl_urldata has unencoded CJK characters. 177 | * 178 | * @return bool 179 | */ 180 | protected function hasUnencodedCJKCharacters() { 181 | if (!ExtensionManagementUtility::isLoaded('dbal')) { 182 | $count = $this->databaseConnection->exec_SELECTcountRows('*', 'tx_realurl_urldata', 'speaking_url RLIKE \'' . self::MYSQL_REGEXP_FOR_NON_URL_CHARACTERS . '\''); 183 | } 184 | else { 185 | $count = 0; 186 | } 187 | 188 | return ($count > 0); 189 | } 190 | 191 | /** 192 | * Checks if the system has zer pid values in realurl tables. 193 | * 194 | * @return bool 195 | */ 196 | protected function hasNonZeroPids() 197 | { 198 | $count = $this->databaseConnection->exec_SELECTcountRows('*', 'tx_realurl_urldata', 'pid<>0'); 199 | $count += $this->databaseConnection->exec_SELECTcountRows('*', 'tx_realurl_pathdata', 'pid<>0'); 200 | 201 | return $count > 0; 202 | } 203 | 204 | /** 205 | * Checks if path cache table is ok. 206 | * 207 | * @return bool 208 | */ 209 | protected function pathCacheNeedsUpdates() { 210 | $fields = $this->databaseConnection->admin_get_fields('tx_realurl_pathdata'); 211 | 212 | return isset($fields['cache_id']) || !isset($fields['uid']) || stripos($fields['uid']['Extra'], 'auto_increment') === false; 213 | } 214 | 215 | /** 216 | * Removes entries with parameters that should be ignored. 217 | */ 218 | protected function removeUrlDataEntriesWithIgnoredParameters() { 219 | $this->databaseConnection->exec_DELETEquery('tx_realurl_urldata', 'original_url RLIKE \'(^|&)(utm_[a-z]+|pk_campaign|pk_kwd)=\''); 220 | } 221 | 222 | /** 223 | * Converts CJK characters to url-encoded form. 224 | * 225 | * @see https://github.com/dmitryd/typo3-realurl/issue/378 226 | * @see http://www.regular-expressions.info/unicode.html#script 227 | * @see http://php.net/manual/en/regexp.reference.unicode.php 228 | */ 229 | protected function updateCJKSpeakingUrls() { 230 | if (!ExtensionManagementUtility::isLoaded('dbal')) { 231 | $resource = $this->databaseConnection->exec_SELECTquery('uid, speaking_url', 'tx_realurl_urldata', 'speaking_url RLIKE \'' . self::MYSQL_REGEXP_FOR_NON_URL_CHARACTERS . '\''); 232 | while (false !== ($data = $this->databaseConnection->sql_fetch_assoc($resource))) { 233 | if (preg_match('/[\p{Han}\p{Hangul}\p{Kannada}\p{Katakana}\p{Hiragana}\p{Tai_Tham}\p{Thai}]+/u', $data['speaking_url'])) { 234 | // Note: cannot use parse_url() here because it corrupts CJK characters! 235 | list($path, $query) = explode('?', $data['speaking_url']); 236 | $segments = explode('/', $path); 237 | array_walk($segments, function(&$segment) { 238 | $segment = rawurlencode($segment); 239 | }); 240 | $url = implode('/', $segments); 241 | if (!empty($query)) { 242 | $url .= '?' . $query; 243 | } 244 | 245 | $this->databaseConnection->exec_UPDATEquery('tx_realurl_urldata', 'uid=' . (int)$data['uid'], array( 246 | 'speaking_url' => $url 247 | )); 248 | } 249 | } 250 | $this->databaseConnection->sql_free_result($resource); 251 | } 252 | } 253 | 254 | /** 255 | * Updates realurl table structure. The code is copied almost 1:1 from 256 | * ExtensionManagerTables class. 257 | * 258 | * We ignore any errors because nothing can be done about those really. The 259 | * client will have to do database update anyway, so he will see all failed 260 | * queries. 261 | * 262 | * @return void 263 | */ 264 | protected function updateRealurlTableStructure() { 265 | $updateStatements = array(); 266 | 267 | // Get all necessary statements for ext_tables.sql file 268 | $rawDefinitions = file_get_contents(ExtensionManagementUtility::extPath('realurl', 'ext_tables.sql')); 269 | $sqlParser = GeneralUtility::makeInstance('TYPO3\\CMS\\Install\\Service\\SqlSchemaMigrationService'); 270 | $fieldDefinitionsFromFile = $sqlParser->getFieldDefinitions_fileContent($rawDefinitions); 271 | if (count($fieldDefinitionsFromFile)) { 272 | $fieldDefinitionsFromCurrentDatabase = $sqlParser->getFieldDefinitions_database(); 273 | $diff = $sqlParser->getDatabaseExtra($fieldDefinitionsFromFile, $fieldDefinitionsFromCurrentDatabase); 274 | $updateStatements = $sqlParser->getUpdateSuggestions($diff); 275 | } 276 | 277 | foreach ((array)$updateStatements['add'] as $string) { 278 | $this->databaseConnection->admin_query($string); 279 | } 280 | foreach ((array)$updateStatements['change'] as $string) { 281 | $this->databaseConnection->admin_query($string); 282 | } 283 | foreach ((array)$updateStatements['create_table'] as $string) { 284 | $this->databaseConnection->admin_query($string); 285 | } 286 | } 287 | 288 | /** 289 | * Updates URL hashes. 290 | */ 291 | protected function updateUrlHashes() { 292 | $this->databaseConnection->sql_query('UPDATE tx_realurl_urldata SET 293 | original_url_hash=CRC32(original_url), 294 | speaking_url_hash=CRC32(speaking_url) 295 | WHERE original_url_hash=0 OR speaking_url_hash=0 296 | '); 297 | } 298 | 299 | /** 300 | * Updates zero pid values. 301 | */ 302 | protected function updateZeroPids() 303 | { 304 | $this->databaseConnection->sql_query('UPDATE tx_realurl_urldata SET pid=0'); 305 | $this->databaseConnection->sql_query('UPDATE tx_realurl_pathdata SET pid=0'); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dmitryd/typo3-realurl", 3 | "type": "typo3-cms-extension", 4 | "description": "Speaking URLs for TYPO3", 5 | "homepage": "https://github.com/dmitryd/typo3-realurl", 6 | "license": "GPL-3.0+", 7 | "conflict": { 8 | "bednee/cooluri": ">0.0.1", 9 | "helhum/realurl": ">0.0.1", 10 | "typo3-ter/cooluri": ">=0.0.1", 11 | "typo3-ter/simulatestatic": ">0.0.1" 12 | }, 13 | "keywords": [ 14 | "TYPO3", 15 | "CMS", 16 | "RealURL", 17 | "typo3", 18 | "realurl" 19 | ], 20 | "replace": { 21 | "dmitryd/typo3-realurl": "self.version", 22 | "typo3-ter/realurl": "self.version" 23 | }, 24 | "require": { 25 | "typo3/cms-core": ">=6.2.0,<9.0.0", 26 | "php": ">=5.4.0,<=7.4.99", 27 | "ext-mbstring": "*" 28 | }, 29 | "require-dev": { 30 | "phpunit/phpunit": "~4.8.0" 31 | }, 32 | "suggest": { 33 | "sjbr/static-info-tables": ">=6.2.0" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "DmitryDulepov\\Realurl\\": "Classes" 38 | } 39 | }, 40 | "autoload-dev": { 41 | "psr-4": { 42 | "DmitryDulepov\\Realurl\\Tests\\": "Tests/", 43 | "TYPO3\\CMS\\Core\\Tests\\": ".Build/vendor/typo3/cms/typo3/sysext/core/Tests/" 44 | } 45 | }, 46 | "authors": [ 47 | { 48 | "name": "Dmitry Dulepov", 49 | "email": "dmitry.dulepov@gmail.com", 50 | "homepage": "http://www.dmitry-dulepov.com/" 51 | } 52 | ], 53 | "config": { 54 | "vendor-dir": ".Build/vendor", 55 | "bin-dir": ".Build/bin" 56 | }, 57 | "scripts": { 58 | "post-autoload-dump": [ 59 | "mkdir -p .Build/Web/typo3conf/ext/", 60 | "[ -L .Build/Web/typo3conf/ext/realurl ] || ln -snvf ../../../../. .Build/Web/typo3conf/ext/realurl" 61 | ], 62 | "test": "export TYPO3_PATH_WEB=`pwd`/.Build/Web; export typo3DatabaseName='realurl'; export typo3DatabaseHost='127.0.0.1'; export typo3DatabaseUsername='realurl'; export typo3DatabasePassword=''; .Build/bin/phpunit --colors -c .Build/vendor/typo3/cms/typo3/sysext/core/Build/UnitTests.xml Tests/Unit/; .Build/bin/phpunit --colors -c .Build/vendor/typo3/cms/typo3/sysext/core/Build/FunctionalTests.xml Tests/Functional/" 63 | }, 64 | "extra": { 65 | "typo3/cms": { 66 | "cms-package-dir": "{$vendor-dir}/typo3/cms", 67 | "extension-key": "realurl", 68 | "web-dir": ".Build/Web" 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ext_conf_template.txt: -------------------------------------------------------------------------------- 1 | # cat=basic/enable; type=string; label=Path to configuration file:Optional. If you placed RealURL configuration in a separate file, RealURL can include it for you. Specify a file name related to web site root directory. 2 | configFile = typo3conf/realurl_conf.php 3 | 4 | # cat=basic/enable; type=boolean; label=Enable automatic configuration:Enable this if you do not want to write configuration manually. It will generate configuration automatically and store it in typo3conf/. Automatically ignored if you wrote configuration yourself. See manual for more information. 5 | enableAutoConf = 1 6 | 7 | # cat=basic/enable; type=options[Serialized=0,PHP source (slow!)=1]; label=Automatic configuration file format:Defines how autoconfiguration file is stored. Use "Serialized" because it is 10 times or more faster! Use "PHP source" if you are curious what is generated or want to copy and modify generated configuration. 8 | autoConfFormat = 0 9 | 10 | # cat=basic/enable; type=string; label=Order of page fields lookup for segment construction (old segTitleFieldList): Defaults to "tx_realurl_pathsegment, alias, nav_title, title, uid" if not set. 11 | segTitleFieldList = 12 | 13 | # cat=basic/enable; type=boolean; label=Enable devLog:Debugging-only! Required any 3rd party devLog extension 14 | enableDevLog = 0 15 | 16 | # cat=basic/enable; type=options[Icon 1=0,Icon 2=1,Icon 3=2]; label=Select Backend module icon:Choose your favorite! 17 | moduleIcon = 0 18 | -------------------------------------------------------------------------------- /ext_emconf.php: -------------------------------------------------------------------------------- 1 | 'Speaking URLs for TYPO3', 13 | 'description' => 'Makes TYPO3 URLs search engine friendly. Donations are welcome to dmitry.dulepov@gmail.com. They help to support the extension!', 14 | 'category' => 'services', 15 | 'version' => '2.6.3', 16 | 'state' => 'stable', 17 | 'uploadfolder' => 0, 18 | 'createDirs' => '', 19 | 'modify_tables' => 'pages,pages_language_overlay', 20 | 'clearcacheonload' => 1, 21 | 'author' => 'Dmitry Dulepov', 22 | 'author_email' => 'dmitry.dulepov@gmail.com', 23 | 'author_company' => '', 24 | 'constraints' => 25 | array ( 26 | 'depends' => 27 | array ( 28 | 'typo3' => '6.2.0-8.9.999', 29 | 'php' => '5.4.0-7.3.999', 30 | 'scheduler' => '6.2.0-8.9.999', 31 | ), 32 | 'conflicts' => 33 | array ( 34 | 'cooluri' => '', 35 | 'simulatestatic' => '', 36 | ), 37 | 'suggests' => 38 | array ( 39 | 'static_info_tables' => '6.2.0-', 40 | ), 41 | ), 42 | 'comment' => 'Maintenance release', 43 | 'user' => 'dmitry', 44 | ); 45 | 46 | ?> -------------------------------------------------------------------------------- /ext_icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitryd/typo3-realurl/168046c842a54e475eb07ed55ef373bb28ccaf6f/ext_icon.gif -------------------------------------------------------------------------------- /ext_localconf.php: -------------------------------------------------------------------------------- 1 | 'typo3conf/realurl_conf.php', 23 | 'enableAutoConf' => true, 24 | ); 25 | } 26 | 27 | $existingConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['realurl']; 28 | 29 | $realurlConfigurationFile = trim($configuration['configFile']); 30 | if ($realurlConfigurationFile && @file_exists(PATH_site . $realurlConfigurationFile)) { 31 | /** @noinspection PhpIncludeInspection */ 32 | \TYPO3\CMS\Core\Utility\GeneralUtility::requireOnce(PATH_site . $realurlConfigurationFile); 33 | } 34 | elseif ($configuration['enableAutoConf'] && file_exists(PATH_site . TX_REALURL_AUTOCONF_FILE)) { 35 | /** @noinspection PhpIncludeInspection */ 36 | \TYPO3\CMS\Core\Utility\GeneralUtility::requireOnce(PATH_site . TX_REALURL_AUTOCONF_FILE); 37 | } 38 | 39 | if (is_array($existingConfiguration)) { 40 | \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule( 41 | $existingConfiguration, 42 | $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['realurl'] 43 | ); 44 | $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['realurl'] = $existingConfiguration; 45 | } 46 | } 47 | } 48 | 49 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tstemplate.php']['linkData-PostProc']['realurl'] = 'DmitryDulepov\\Realurl\\Encoder\\UrlEncoder->encodeUrl'; 50 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc']['realurl'] = 'DmitryDulepov\\Realurl\\Encoder\\UrlEncoder->postProcessEncodedUrl'; 51 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc']['realurl'] = 'DmitryDulepov\\Realurl\\Decoder\\UrlDecoder->decodeUrl'; 52 | 53 | includeRealurlConfiguration(); 54 | 55 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['realurl'] = 'DmitryDulepov\\Realurl\\Hooks\\DataHandler'; 56 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['realurl'] = 'DmitryDulepov\\Realurl\\Hooks\\DataHandler'; 57 | 58 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['realurl']['cacheImplementation'] = 'DmitryDulepov\\Realurl\\Cache\\DatabaseCache'; 59 | 60 | if (!empty($GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'])) { 61 | $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'] .= ','; 62 | } 63 | $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'] .= 'tx_realurl_pathsegment,tx_realurl_exclude,tx_realurl_pathoverride'; 64 | 65 | if (isset($GLOBALS['TYPO3_CONF_VARS']['FE']['pageOverlayFields'])) { 66 | if (!empty($GLOBALS['TYPO3_CONF_VARS']['FE']['pageOverlayFields'])) { 67 | $GLOBALS['TYPO3_CONF_VARS']['FE']['pageOverlayFields'] .= ','; 68 | } 69 | $GLOBALS['TYPO3_CONF_VARS']['FE']['pageOverlayFields'] .= 'tx_realurl_pathsegment'; 70 | } 71 | 72 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals']['DmitryDulepov\\Realurl\\Evaluator\\SegmentFieldCleaner'] = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('realurl', 'Classes/Evaluator/SegmentFieldCleaner.php'); 73 | 74 | \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig(''); 75 | 76 | // Scheduler clean up of expired tables 77 | if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['TYPO3\\CMS\\Scheduler\\Task\\TableGarbageCollectionTask']['options']['tables']['tx_realurl_urldata'])) { 78 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['TYPO3\\CMS\\Scheduler\\Task\\TableGarbageCollectionTask']['options']['tables']['tx_realurl_urldata'] = array( 79 | 'expireField' => 'expire', 80 | ); 81 | } 82 | if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['TYPO3\\CMS\\Scheduler\\Task\\TableGarbageCollectionTask']['options']['tables']['tx_realurl_pathdata'])) { 83 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['TYPO3\\CMS\\Scheduler\\Task\\TableGarbageCollectionTask']['options']['tables']['tx_realurl_pathdata'] = array( 84 | 'expireField' => 'expire', 85 | ); 86 | } 87 | if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['TYPO3\\CMS\\Scheduler\\Task\\TableGarbageCollectionTask']['options']['tables']['tx_realurl_uniqalias'])) { 88 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['TYPO3\\CMS\\Scheduler\\Task\\TableGarbageCollectionTask']['options']['tables']['tx_realurl_uniqalias'] = array( 89 | 'expireField' => 'expire', 90 | ); 91 | } 92 | 93 | // Exclude gclid from cHash because TYPO3 does not do that 94 | if (!is_array($GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'])) { 95 | $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'] = array(); 96 | } 97 | if (!in_array('gclid', $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'])) { 98 | $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = 'gclid'; 99 | if (!isset($GLOBALS['TYPO3_CONF_VARS']['FE']['cHashExcludedParameters'])) { 100 | $GLOBALS['TYPO3_CONF_VARS']['FE']['cHashExcludedParameters'] = ''; 101 | } 102 | $GLOBALS['TYPO3_CONF_VARS']['FE']['cHashExcludedParameters'] .= ', gclid'; 103 | } 104 | 105 | // Exclude fbclid from cHash 106 | if (!in_array('fbclid', $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'])) { 107 | $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = 'fbclid'; 108 | if (!isset($GLOBALS['TYPO3_CONF_VARS']['FE']['cHashExcludedParameters'])) { 109 | $GLOBALS['TYPO3_CONF_VARS']['FE']['cHashExcludedParameters'] = ''; 110 | } 111 | $GLOBALS['TYPO3_CONF_VARS']['FE']['cHashExcludedParameters'] .= ', fbclid'; 112 | } 113 | 114 | 115 | // Turn logging off by default 116 | $logConfiguration = array( 117 | 'writerConfiguration' => array( 118 | \TYPO3\CMS\Core\Log\LogLevel::DEBUG => array( 119 | 'TYPO3\\CMS\\Core\\Log\Writer\\NullWriter' => array( 120 | ) 121 | ) 122 | ) 123 | ); 124 | 125 | if (isset($GLOBALS['TYPO3_CONF_VARS']['LOG']['DmitryDulepov']['Realurl'])) { 126 | \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule( 127 | $logConfiguration, 128 | $GLOBALS['TYPO3_CONF_VARS']['LOG']['DmitryDulepov']['Realurl'] 129 | ); 130 | } 131 | 132 | $GLOBALS['TYPO3_CONF_VARS']['LOG']['DmitryDulepov']['Realurl'] = $logConfiguration; 133 | -------------------------------------------------------------------------------- /ext_tables.php: -------------------------------------------------------------------------------- 1 | =')) { 4 | $realurlConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['realurl']; 5 | if (is_string($realurlConfiguration)) { 6 | $realurlConfiguration = (array)@unserialize($realurlConfiguration); 7 | } 8 | else { 9 | $realurlConfiguration = array(); 10 | } 11 | 12 | $realurlModuleIcon = ((!isset($realurlConfiguration['moduleIcon']) || $realurlConfiguration['moduleIcon'] == 0) ? 'Module.svg' : 13 | ($realurlConfiguration['moduleIcon'] == 1 ? 'Module2.svg' : 'Module3.svg') 14 | ); 15 | 16 | \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule( 17 | 'DmitryDulepov.Realurl', 18 | 'web', 19 | 'realurl', 20 | '', 21 | array( 22 | 'Overview' => 'index', 23 | 'Aliases' => 'index,edit,delete,deleteAll', 24 | 'UrlCache' => 'index,delete,deleteAll,flush', 25 | 'PathCache' => 'index,delete', 26 | ), 27 | array( 28 | 'access' => 'user,group', 29 | 'icon' => 'EXT:realurl/Resources/Public/Icons/' . $realurlModuleIcon, 30 | 'labels' => 'LLL:EXT:realurl/Resources/Private/Language/locallang.xlf', 31 | ) 32 | ); 33 | 34 | unset($realurlConfiguration, $realurlModuleIcon); 35 | } 36 | -------------------------------------------------------------------------------- /ext_tables.sql: -------------------------------------------------------------------------------- 1 | # 2 | # Table structure for table 'tx_realurl_uniqalias' 3 | # 4 | CREATE TABLE tx_realurl_uniqalias ( 5 | uid int(11) NOT NULL auto_increment, 6 | pid int(11) DEFAULT '0' NOT NULL, 7 | tablename varchar(255) DEFAULT '' NOT NULL, 8 | field_alias varchar(255) DEFAULT '' NOT NULL, 9 | field_id varchar(60) DEFAULT '' NOT NULL, 10 | value_alias varchar(255) DEFAULT '' NOT NULL, 11 | value_id int(11) DEFAULT '0' NOT NULL, 12 | lang int(11) DEFAULT '0' NOT NULL, 13 | expire int(11) DEFAULT '0' NOT NULL, 14 | 15 | PRIMARY KEY (uid), 16 | KEY parent (pid), 17 | KEY tablename (tablename), 18 | KEY bk_realurl01 (field_alias(20),field_id,value_id,lang,expire), 19 | KEY bk_realurl02 (tablename(32),field_alias(20),field_id,value_alias(20),expire) 20 | ) ENGINE=InnoDB; 21 | 22 | # 23 | # Table structure for table 'tx_realurl_uniqalias_cache_map' 24 | # 25 | CREATE TABLE tx_realurl_uniqalias_cache_map ( 26 | uid int(11) NOT NULL auto_increment, 27 | alias_uid int(11) DEFAULT '0' NOT NULL, 28 | url_cache_id int(11) DEFAULT '0' NOT NULL, 29 | 30 | PRIMARY KEY (uid), 31 | KEY check_existence (alias_uid,url_cache_id) 32 | ) ENGINE=InnoDB; 33 | 34 | # 35 | # Table structure for table 'tx_realurl_urldata' 36 | # 37 | CREATE TABLE tx_realurl_urldata ( 38 | uid int(11) NOT NULL auto_increment, 39 | pid int(11) DEFAULT '0' NOT NULL, 40 | crdate int(11) DEFAULT '0' NOT NULL, 41 | # Field page_id is deprecated! Use pid field instead! 42 | page_id int(11) DEFAULT '0' NOT NULL, 43 | rootpage_id int(11) DEFAULT '0' NOT NULL, 44 | original_url text, 45 | original_url_hash int(11) unsigned DEFAULT '0' NOT NULL, 46 | speaking_url text, 47 | speaking_url_hash int(11) unsigned DEFAULT '0' NOT NULL, 48 | request_variables text, 49 | expire int(11) DEFAULT '0' NOT NULL, 50 | 51 | PRIMARY KEY (uid), 52 | KEY parent (pid), 53 | KEY pathq1 (rootpage_id,original_url_hash,expire), 54 | KEY pathq2 (rootpage_id,speaking_url_hash,expire), 55 | KEY page_id (page_id) 56 | ) ENGINE=InnoDB; 57 | 58 | # 59 | # Table structure for table 'tx_realurl_pathdata' 60 | # 61 | CREATE TABLE tx_realurl_pathdata ( 62 | uid int(11) NOT NULL auto_increment, 63 | pid int(11) DEFAULT '0' NOT NULL, 64 | # Field page_id is deprecated! Use pid field instead! 65 | page_id int(11) DEFAULT '0' NOT NULL, 66 | language_id int(11) DEFAULT '0' NOT NULL, 67 | rootpage_id int(11) DEFAULT '0' NOT NULL, 68 | mpvar tinytext, 69 | pagepath text, 70 | expire int(11) DEFAULT '0' NOT NULL, 71 | 72 | PRIMARY KEY (uid), 73 | KEY parent (pid), 74 | KEY pathq1 (rootpage_id,pagepath(32),expire), 75 | KEY pathq2 (page_id,language_id,rootpage_id,expire), 76 | KEY expire (expire) 77 | ) ENGINE=InnoDB; 78 | 79 | # 80 | # Modifying pages table 81 | # 82 | CREATE TABLE pages ( 83 | tx_realurl_pathsegment varchar(255) DEFAULT '' NOT NULL, 84 | tx_realurl_pathoverride int(1) DEFAULT '0' NOT NULL, 85 | tx_realurl_exclude int(1) DEFAULT '0' NOT NULL 86 | ); 87 | 88 | # 89 | # Modifying pages_language_overlay table 90 | # 91 | CREATE TABLE pages_language_overlay ( 92 | tx_realurl_pathsegment varchar(255) DEFAULT '' NOT NULL 93 | ); 94 | -------------------------------------------------------------------------------- /ext_typoscript_setup.txt: -------------------------------------------------------------------------------- 1 | config.tx_extbase.persistence.classes { 2 | DmitryDulepov\Realurl\Domain\Model\Alias { 3 | mapping { 4 | tableName = tx_realurl_uniqalias 5 | } 6 | } 7 | DmitryDulepov\Realurl\Domain\Model\UrlCacheEntry { 8 | mapping { 9 | tableName = tx_realurl_urldata 10 | columns { 11 | rootpage_id.mapOnProperty = rootPageId 12 | } 13 | } 14 | } 15 | DmitryDulepov\Realurl\Domain\Model\PathCacheEntry { 16 | mapping { 17 | tableName = tx_realurl_pathdata 18 | columns { 19 | mpvar.mapOnProperty = mpVar 20 | pagepath.mapOnProperty = pagePath 21 | rootpage_id.mapOnProperty = rootPageId 22 | } 23 | } 24 | } 25 | } 26 | --------------------------------------------------------------------------------