[!!!][FEATURE] Introduce PSR-7-based Routing for Backend AJAX Requests
[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\Utility\BackendUtility;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * Ext Direct API Generator
24 */
25 class ExtDirectApi {
26
27 /**
28 * @var array
29 */
30 protected $settings = array();
31
32 /**
33 * Constructs this object.
34 */
35 public function __construct() {
36 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'])) {
37 $this->settings = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'];
38 }
39 }
40
41 /**
42 * Parses the ExtDirect configuration array "$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect']"
43 * and feeds the given typo3 ajax instance with the resulting information. The get parameter
44 * "namespace" will be used to filter the configuration.
45 *
46 * This method makes usage of the reflection mechanism to fetch the methods inside the
47 * defined classes together with their amount of parameters. This information are building
48 * the API and are required by ExtDirect. The result is cached to improve the overall
49 * performance.
50 *
51 * @param ServerRequestInterface $request
52 * @param ResponseInterface $response
53 * @return ResponseInterface
54 */
55 public function getAPI(ServerRequestInterface $request, ResponseInterface $response) {
56 $response->getBody()->write(json_encode([]));
57 return $response;
58 }
59
60 /**
61 * Get the API for a given nameapace
62 *
63 * @param array $filterNamespaces
64 * @return string
65 * @throws \InvalidArgumentException
66 */
67 public function getApiPhp(array $filterNamespaces) {
68 $javascriptNamespaces = $this->getExtDirectApi($filterNamespaces);
69 // Return the generated javascript API configuration
70 if (!empty($javascriptNamespaces)) {
71 return '
72 if (!Ext.isObject(Ext.app.ExtDirectAPI)) {
73 Ext.app.ExtDirectAPI = {};
74 }
75 Ext.apply(Ext.app.ExtDirectAPI, ' . json_encode($javascriptNamespaces) . ');
76 ';
77 } else {
78 $errorMessage = $this->getNamespaceError($filterNamespaces);
79 throw new \InvalidArgumentException($errorMessage, 1297645190);
80 }
81 }
82
83 /**
84 * Generates the API that is configured inside the ExtDirect configuration
85 * array "$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect']".
86 *
87 * @param array $filerNamespace Namespace that should be loaded like array('TYPO3.Backend')
88 * @return array Javascript API configuration
89 */
90 protected function generateAPI(array $filterNamespaces) {
91 $javascriptNamespaces = array();
92 if (is_array($this->settings)) {
93 foreach ($this->settings as $javascriptName => $configuration) {
94 $splittedJavascriptName = explode('.', $javascriptName);
95 $javascriptObjectName = array_pop($splittedJavascriptName);
96 $javascriptNamespace = implode('.', $splittedJavascriptName);
97 // Only items inside the wanted namespace
98 if (!$this->findNamespace($javascriptNamespace, $filterNamespaces)) {
99 continue;
100 }
101 if (!isset($javascriptNamespaces[$javascriptNamespace])) {
102 $javascriptNamespaces[$javascriptNamespace] = array(
103 'url' => $this->getRoutingUrl($javascriptNamespace),
104 'type' => 'remoting',
105 'actions' => array(),
106 'namespace' => $javascriptNamespace
107 );
108 }
109 if (is_array($configuration)) {
110 $className = $configuration['callbackClass'];
111 $serverObject = GeneralUtility::getUserObj($className);
112 $javascriptNamespaces[$javascriptNamespace]['actions'][$javascriptObjectName] = array();
113 foreach (get_class_methods($serverObject) as $methodName) {
114 $reflectionMethod = new \ReflectionMethod($serverObject, $methodName);
115 $numberOfParameters = $reflectionMethod->getNumberOfParameters();
116 $docHeader = $reflectionMethod->getDocComment();
117 $formHandler = strpos($docHeader, '@formHandler') !== FALSE;
118 $javascriptNamespaces[$javascriptNamespace]['actions'][$javascriptObjectName][] = array(
119 'name' => $methodName,
120 'len' => $numberOfParameters,
121 'formHandler' => $formHandler
122 );
123 }
124 }
125 }
126 }
127 return $javascriptNamespaces;
128 }
129
130 /**
131 * Returns the convenient path for the routing Urls based on the TYPO3 mode.
132 *
133 * @param string $namespace
134 * @return string
135 */
136 public function getRoutingUrl($namespace) {
137 if (TYPO3_MODE === 'FE') {
138 $url = GeneralUtility::locationHeaderUrl('?eID=ExtDirect&action=route&namespace=' . rawurlencode($namespace));
139 } else {
140 $url = BackendUtility::getAjaxUrl('ext_direct_route', array('namespace' => $namespace));
141 }
142 return $url;
143 }
144
145 /**
146 * Generates the API or reads it from cache
147 *
148 * @param array $filterNamespaces
149 * @return string $javascriptNamespaces
150 */
151 protected function getExtDirectApi(array $filterNamespaces) {
152 $noCache = (bool)GeneralUtility::_GET('no_cache');
153 // Look up into the cache
154 $cacheIdentifier = 'ExtDirectApi';
155 $cacheHash = md5($cacheIdentifier . implode(',', $filterNamespaces) . GeneralUtility::getIndpEnv('TYPO3_SSL') . serialize($this->settings) . TYPO3_MODE . GeneralUtility::getIndpEnv('HTTP_HOST'));
156 // With no_cache always generate the javascript content
157 // Generate the javascript content if it wasn't found inside the cache and cache it!
158 if ($noCache || !is_array(($javascriptNamespaces = \TYPO3\CMS\Frontend\Page\PageRepository::getHash($cacheHash)))) {
159 $javascriptNamespaces = $this->generateAPI($filterNamespaces);
160 if (!empty($javascriptNamespaces)) {
161 \TYPO3\CMS\Frontend\Page\PageRepository::storeHash($cacheHash, $javascriptNamespaces, $cacheIdentifier);
162 }
163 }
164 return $javascriptNamespaces;
165 }
166
167 /**
168 * Generates the error message
169 *
170 * @param array $filterNamespaces
171 * @return string $errorMessage
172 */
173 protected function getNamespaceError(array $filterNamespaces) {
174 if (!empty($filterNamespaces)) {
175 // Namespace error
176 $errorMessage = sprintf($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:ExtDirect.namespaceError'), __CLASS__, implode(',', $filterNamespaces));
177 } else {
178 // No namespace given
179 $errorMessage = sprintf($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:ExtDirect.noNamespace'), __CLASS__);
180 }
181 return $errorMessage;
182 }
183
184 /**
185 * Looks if the given namespace is present in $filterNamespaces
186 *
187 * @param string $namespace
188 * @param array $filterNamespaces
189 * @return bool
190 */
191 protected function findNamespace($namespace, array $filterNamespaces) {
192 if ($filterNamespaces === array('TYPO3')) {
193 return TRUE;
194 }
195 $found = FALSE;
196 foreach ($filterNamespaces as $filter) {
197 if (GeneralUtility::isFirstPartOfStr($filter, $namespace)) {
198 $found = TRUE;
199 break;
200 }
201 }
202 return $found;
203 }
204
205 }