1<?php 2 3/* 4 * This file is part of the TYPO3 CMS project. 5 * 6 * It is free software; you can redistribute it and/or modify it under 7 * the terms of the GNU General Public License, either version 2 8 * of the License, or any later version. 9 * 10 * For the full copyright and license information, please read the 11 * LICENSE.txt file that was distributed with this source code. 12 * 13 * The TYPO3 project - inspiring people to share! 14 */ 15 16namespace TYPO3\CMS\Core\Localization\Parser; 17 18use TYPO3\CMS\Core\Core\Environment; 19use TYPO3\CMS\Core\Localization\Exception\FileNotFoundException; 20use TYPO3\CMS\Core\Localization\Exception\InvalidXmlFileException; 21use TYPO3\CMS\Core\Utility\GeneralUtility; 22use TYPO3\CMS\Core\Utility\PathUtility; 23 24/** 25 * Abstract class for XML based parser. 26 * @internal This class is a concrete implementation and is not part of the TYPO3 Core API. 27 */ 28abstract class AbstractXmlParser implements LocalizationParserInterface 29{ 30 /** 31 * @var string 32 */ 33 protected $sourcePath; 34 35 /** 36 * @var string 37 */ 38 protected $languageKey; 39 40 /** 41 * New method for parsing xml files, not part of an interface as the plan is to replace 42 * the entire API for labels with something different in TYPO3 12 43 * 44 * @param string $sourcePath 45 * @param string $languageKey 46 * @param string $fileNamePattern 47 * @return array 48 */ 49 public function parseExtensionResource(string $sourcePath, string $languageKey, string $fileNamePattern): array 50 { 51 $fileName = Environment::getLabelsPath() . sprintf($fileNamePattern, $languageKey); 52 53 return $this->_getParsedData($sourcePath, $languageKey, $fileName); 54 } 55 56 /** 57 * Returns parsed representation of XML file. 58 * 59 * @param string $sourcePath Source file path 60 * @param string $languageKey Language key 61 * @return array 62 * @throws \TYPO3\CMS\Core\Localization\Exception\FileNotFoundException 63 */ 64 public function getParsedData($sourcePath, $languageKey) 65 { 66 return $this->_getParsedData($sourcePath, $languageKey, null); 67 } 68 69 /** 70 * Actually doing all the work of parsing an XML file 71 * 72 * @param string $sourcePath Source file path 73 * @param string $languageKey Language key 74 * @return array 75 * @throws \TYPO3\CMS\Core\Localization\Exception\FileNotFoundException 76 */ 77 protected function _getParsedData($sourcePath, $languageKey, ?string $labelsPath) 78 { 79 $this->sourcePath = $sourcePath; 80 $this->languageKey = $languageKey; 81 if ($this->languageKey !== 'default') { 82 $this->sourcePath = $labelsPath ?? $this->getLocalizedFileName($this->sourcePath, $this->languageKey); 83 if (!@is_file($this->sourcePath)) { 84 // Global localization is not available, try split localization file 85 $this->sourcePath = $this->getLocalizedFileName($sourcePath, $languageKey, true); 86 } 87 if (!@is_file($this->sourcePath)) { 88 throw new FileNotFoundException('Localization file does not exist', 1306332397); 89 } 90 } 91 $LOCAL_LANG = []; 92 $LOCAL_LANG[$languageKey] = $this->parseXmlFile(); 93 return $LOCAL_LANG; 94 } 95 96 /** 97 * Loads the current XML file before processing. 98 * 99 * @return array An array representing parsed XML file (structure depends on concrete parser) 100 * @throws \TYPO3\CMS\Core\Localization\Exception\InvalidXmlFileException 101 */ 102 protected function parseXmlFile() 103 { 104 $xmlContent = file_get_contents($this->sourcePath); 105 if ($xmlContent === false) { 106 throw new InvalidXmlFileException( 107 'The path provided does not point to an existing and accessible file.', 108 1278155987 109 ); 110 } 111 // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept 112 $previousValueOfEntityLoader = null; 113 if (PHP_MAJOR_VERSION < 8) { 114 $previousValueOfEntityLoader = libxml_disable_entity_loader(true); 115 } 116 $rootXmlNode = simplexml_load_string($xmlContent, \SimpleXMLElement::class, LIBXML_NOWARNING); 117 if (PHP_MAJOR_VERSION < 8) { 118 libxml_disable_entity_loader($previousValueOfEntityLoader); 119 } 120 if ($rootXmlNode === false) { 121 $xmlError = libxml_get_last_error(); 122 throw new InvalidXmlFileException( 123 'The path provided does not point to an existing and accessible well-formed XML file. Reason: ' . $xmlError->message . ' in ' . $this->sourcePath . ', line ' . $xmlError->line, 124 1278155988 125 ); 126 } 127 return $this->doParsingFromRoot($rootXmlNode); 128 } 129 130 /** 131 * Checks if a localized file is found in labels pack (e.g. a language pack was downloaded in the backend) 132 * or if $sameLocation is set, then checks for a file located in "{language}.locallang.xlf" at the same directory 133 * 134 * @param string $fileRef Absolute file reference to locallang file 135 * @param string $language Language key 136 * @param bool $sameLocation If TRUE, then locallang localization file name will be returned with same directory as $fileRef 137 * @return string Absolute path to the language file 138 */ 139 protected function getLocalizedFileName(string $fileRef, string $language, bool $sameLocation = false) 140 { 141 // If $fileRef is already prefixed with "[language key]" then we should return it as is 142 $fileName = PathUtility::basename($fileRef); 143 if (str_starts_with($fileName, $language . '.')) { 144 return GeneralUtility::getFileAbsFileName($fileRef); 145 } 146 147 if ($sameLocation) { 148 return GeneralUtility::getFileAbsFileName(str_replace($fileName, $language . '.' . $fileName, $fileRef)); 149 } 150 151 // Analyze file reference 152 if (str_starts_with($fileRef, Environment::getFrameworkBasePath() . '/')) { 153 // Is system 154 $validatedPrefix = Environment::getFrameworkBasePath() . '/'; 155 } elseif (str_starts_with($fileRef, Environment::getBackendPath() . '/ext/')) { 156 // Is global 157 $validatedPrefix = Environment::getBackendPath() . '/ext/'; 158 } elseif (str_starts_with($fileRef, Environment::getExtensionsPath() . '/')) { 159 // Is local 160 $validatedPrefix = Environment::getExtensionsPath() . '/'; 161 } else { 162 $validatedPrefix = ''; 163 } 164 if ($validatedPrefix) { 165 // Divide file reference into extension key, directory (if any) and base name: 166 [$extensionKey, $file_extPath] = explode('/', substr($fileRef, strlen($validatedPrefix)), 2); 167 $temp = GeneralUtility::revExplode('/', $file_extPath, 2); 168 if (count($temp) === 1) { 169 array_unshift($temp, ''); 170 } 171 // Add empty first-entry if not there. 172 [$file_extPath, $file_fileName] = $temp; 173 // The filename is prefixed with "[language key]." because it prevents the llxmltranslate tool from detecting it. 174 return Environment::getLabelsPath() . '/' . $language . '/' . $extensionKey . '/' . ($file_extPath ? $file_extPath . '/' : '') . $language . '.' . $file_fileName; 175 } 176 return ''; 177 } 178 179 /** 180 * Returns array representation of XML data, starting from a root node. 181 * 182 * @param \SimpleXMLElement $root A root node 183 * @return array An array representing the parsed XML file 184 */ 185 abstract protected function doParsingFromRoot(\SimpleXMLElement $root); 186} 187