b3babf07e73b6f239b86ef244b712decfcc784db
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / ExtDirect / ExtDirectApi.php
1 <?php
2 namespace TYPO3\CMS\Core\ExtDirect;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Routing\UriBuilder;
20 use TYPO3\CMS\Core\Cache\CacheManager;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22
23 /**
24 * Ext Direct API Generator
25 */
26 class ExtDirectApi
27 {
28 /**
29 * @var array
30 */
31 protected $settings = [];
32
33 /**
34 * Constructs this object.
35 */
36 public function __construct()
37 {
38 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'])) {
39 $this->settings = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'];
40 }
41 }
42
43 /**
44 * Parses the ExtDirect configuration array "$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect']"
45 * and feeds the given typo3 ajax instance with the resulting information. The get parameter
46 * "namespace" will be used to filter the configuration.
47 *
48 * This method makes usage of the reflection mechanism to fetch the methods inside the
49 * defined classes together with their amount of parameters. This information are building
50 * the API and are required by ExtDirect. The result is cached to improve the overall
51 * performance.
52 *
53 * @param ServerRequestInterface $request
54 * @param ResponseInterface $response
55 * @return ResponseInterface
56 */
57 public function getAPI(ServerRequestInterface $request, ResponseInterface $response)
58 {
59 $response->getBody()->write(json_encode([]));
60 return $response;
61 }
62
63 /**
64 * Get the API for a given nameapace
65 *
66 * @param array $filterNamespaces
67 * @return string
68 * @throws \InvalidArgumentException
69 */
70 public function getApiPhp(array $filterNamespaces)
71 {
72 $javascriptNamespaces = $this->getExtDirectApi($filterNamespaces);
73 // Return the generated javascript API configuration
74 if (!empty($javascriptNamespaces)) {
75 return '
76 if (!Ext.isObject(Ext.app.ExtDirectAPI)) {
77 Ext.app.ExtDirectAPI = {};
78 }
79 Ext.apply(Ext.app.ExtDirectAPI, ' . json_encode($javascriptNamespaces) . ');
80 ';
81 } else {
82 $errorMessage = $this->getNamespaceError($filterNamespaces);
83 throw new \InvalidArgumentException($errorMessage, 1297645190);
84 }
85 }
86
87 /**
88 * Generates the API that is configured inside the ExtDirect configuration
89 * array "$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect']".
90 *
91 * @param array $filterNamespaces Namespace that should be loaded like array('TYPO3.Backend')
92 * @return array Javascript API configuration
93 */
94 protected function generateAPI(array $filterNamespaces)
95 {
96 $javascriptNamespaces = [];
97 if (is_array($this->settings)) {
98 foreach ($this->settings as $javascriptName => $configuration) {
99 $splittedJavascriptName = explode('.', $javascriptName);
100 $javascriptObjectName = array_pop($splittedJavascriptName);
101 $javascriptNamespace = implode('.', $splittedJavascriptName);
102 // Only items inside the wanted namespace
103 if (!$this->findNamespace($javascriptNamespace, $filterNamespaces)) {
104 continue;
105 }
106 if (!isset($javascriptNamespaces[$javascriptNamespace])) {
107 $javascriptNamespaces[$javascriptNamespace] = [
108 'url' => $this->getRoutingUrl($javascriptNamespace),
109 'type' => 'remoting',
110 'actions' => [],
111 'namespace' => $javascriptNamespace
112 ];
113 }
114 if (is_array($configuration)) {
115 $className = $configuration['callbackClass'];
116 $serverObject = GeneralUtility::makeInstance($className);
117 $javascriptNamespaces[$javascriptNamespace]['actions'][$javascriptObjectName] = [];
118 foreach (get_class_methods($serverObject) as $methodName) {
119 $reflectionMethod = new \ReflectionMethod($serverObject, $methodName);
120 $numberOfParameters = $reflectionMethod->getNumberOfParameters();
121 $docHeader = $reflectionMethod->getDocComment();
122 $formHandler = strpos($docHeader, '@formHandler') !== false;
123 $javascriptNamespaces[$javascriptNamespace]['actions'][$javascriptObjectName][] = [
124 'name' => $methodName,
125 'len' => $numberOfParameters,
126 'formHandler' => $formHandler
127 ];
128 }
129 }
130 }
131 }
132 return $javascriptNamespaces;
133 }
134
135 /**
136 * Returns the convenient path for the routing Urls based on the TYPO3 mode.
137 *
138 * @param string $namespace
139 * @return string
140 */
141 public function getRoutingUrl($namespace)
142 {
143 if (TYPO3_MODE === 'FE') {
144 $url = GeneralUtility::locationHeaderUrl('?eID=ExtDirect&action=route&namespace=' . rawurlencode($namespace));
145 } else {
146 /** @var UriBuilder $uriBuilder */
147 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
148 $url = (string)$uriBuilder->buildUriFromRoute('ajax_ext_direct_route', ['namespace' => $namespace]);
149 }
150 return $url;
151 }
152
153 /**
154 * Generates the API or reads it from cache
155 *
156 * @param array $filterNamespaces
157 * @return string $javascriptNamespaces
158 */
159 protected function getExtDirectApi(array $filterNamespaces)
160 {
161 $noCache = (bool)GeneralUtility::_GET('no_cache');
162 // Look up into the cache
163 $cacheTag = 'ExtDirectApi';
164 $cacheIdentifier = md5($cacheTag . implode(',', $filterNamespaces) . GeneralUtility::getIndpEnv('TYPO3_SSL') . serialize($this->settings) . TYPO3_MODE . GeneralUtility::getIndpEnv('HTTP_HOST'));
165 // With no_cache always generate the javascript content
166 // Generate the javascript content if it wasn't found inside the cache and cache it!
167 /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
168 $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
169 if ($noCache || !is_array(($javascriptNamespaces = $cache->get($cacheIdentifier)))) {
170 $javascriptNamespaces = $this->generateAPI($filterNamespaces);
171 if (!empty($javascriptNamespaces)) {
172 $cache->set($cacheIdentifier, $javascriptNamespaces, [$cacheTag]);
173 }
174 }
175 return $javascriptNamespaces;
176 }
177
178 /**
179 * Generates the error message
180 *
181 * @param array $filterNamespaces
182 * @return string $errorMessage
183 */
184 protected function getNamespaceError(array $filterNamespaces)
185 {
186 if (!empty($filterNamespaces)) {
187 // Namespace error
188 $errorMessage = sprintf($GLOBALS['LANG']->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:ExtDirect.namespaceError'), __CLASS__, implode(',', $filterNamespaces));
189 } else {
190 // No namespace given
191 $errorMessage = sprintf($GLOBALS['LANG']->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:ExtDirect.noNamespace'), __CLASS__);
192 }
193 return $errorMessage;
194 }
195
196 /**
197 * Looks if the given namespace is present in $filterNamespaces
198 *
199 * @param string $namespace
200 * @param array $filterNamespaces
201 * @return bool
202 */
203 protected function findNamespace($namespace, array $filterNamespaces)
204 {
205 if ($filterNamespaces === ['TYPO3']) {
206 return true;
207 }
208 $found = false;
209 foreach ($filterNamespaces as $filter) {
210 if (GeneralUtility::isFirstPartOfStr($filter, $namespace)) {
211 $found = true;
212 break;
213 }
214 }
215 return $found;
216 }
217 }