1 <?php
2
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
22 abstract class rex_api_function
23 {
24 use rex_factory_trait;
25
26 const REQ_CALL_PARAM = 'rex-api-call';
27 const REQ_RESULT_PARAM = 'rex-api-result';
28
29 30 31 32 33
34 protected $published = false;
35
36 37 38 39 40
41 protected $result = null;
42
43 44 45 46 47 48 49 50 51 52
53 abstract public function execute();
54
55 56 57 58 59
60 private static $instance;
61
62 63 64 65 66 67 68
69 public static function factory()
70 {
71 if (self::$instance) {
72 return self::$instance;
73 }
74
75 $api = rex_request(self::REQ_CALL_PARAM, 'string');
76
77 if ($api) {
78 $apiClass = 'rex_api_' . $api;
79 if (class_exists($apiClass)) {
80 $apiImpl = new $apiClass();
81 if ($apiImpl instanceof self) {
82 self::$instance = $apiImpl;
83 return $apiImpl;
84 }
85 throw new rex_exception('$apiClass is expected to define a subclass of rex_api_function, "'. $apiClass .'" given!');
86 }
87 throw new rex_exception('$apiClass "' . $apiClass . '" not found!');
88 }
89
90 return null;
91 }
92
93 94 95 96 97 98 99
100 public static function getUrlParams()
101 {
102 $class = static::class;
103
104 if (self::class === $class) {
105 throw new BadMethodCallException(__FUNCTION__.' must be called on subclasses of "'.self::class.'".');
106 }
107
108
109 $name = substr($class, 8);
110
111 return [self::REQ_CALL_PARAM => $name, rex_csrf_token::PARAM => rex_csrf_token::factory($class)->getValue()];
112 }
113
114 115 116 117 118 119 120
121 public static function getHiddenFields()
122 {
123 $class = static::class;
124
125 if (self::class === $class) {
126 throw new BadMethodCallException(__FUNCTION__.' must be called on subclasses of "'.self::class.'".');
127 }
128
129
130 $name = substr($class, 8);
131
132 return sprintf('<input type="hidden" name="%s" value="%s"/>', self::REQ_CALL_PARAM, rex_escape($name))
133 .rex_csrf_token::factory($class)->getHiddenField();
134 }
135
136 137 138
139 public static function handleCall()
140 {
141 if (static::hasFactoryClass()) {
142 return static::callFactoryClass(__FUNCTION__, func_get_args());
143 }
144
145 $apiFunc = self::factory();
146
147 if ($apiFunc != null) {
148 if ($apiFunc->published !== true) {
149 if (rex::isBackend() !== true) {
150 throw new rex_http_exception(
151 new rex_api_exception('the api function ' . get_class($apiFunc) . ' is not published, therefore can only be called from the backend!'),
152 rex_response::HTTP_FORBIDDEN
153 );
154 }
155
156 if (!rex::getUser()) {
157 throw new rex_http_exception(
158 new rex_api_exception('missing backend session to call api function ' . get_class($apiFunc) . '!'),
159 rex_response::HTTP_UNAUTHORIZED
160 );
161 }
162 }
163
164 $urlResult = rex_get(self::REQ_RESULT_PARAM, 'string');
165 if ($urlResult) {
166
167 $result = rex_api_result::fromJSON($urlResult);
168 $apiFunc->result = $result;
169 } else {
170 if ($apiFunc->requiresCsrfProtection() && !rex_csrf_token::factory(get_class($apiFunc))->isValid()) {
171 $result = new rex_api_result(false, rex_i18n::msg('csrf_token_invalid'));
172 $apiFunc->result = $result;
173
174 return;
175 }
176
177 try {
178 $result = $apiFunc->execute();
179
180 if (!($result instanceof rex_api_result)) {
181 throw new rex_exception('Illegal result returned from api-function ' . rex_get(self::REQ_CALL_PARAM) .'. Expected a instance of rex_api_result but got "'. (is_object($result) ? get_class($result) : gettype($result)) .'".');
182 }
183
184 $apiFunc->result = $result;
185 if ($result->requiresReboot()) {
186 $context = rex_context::fromGet();
187
188 $context->setParam(self::REQ_RESULT_PARAM, $result->toJSON());
189
190 rex_response::sendRedirect($context->getUrl([], false));
191 }
192 } catch (rex_api_exception $e) {
193 $message = $e->getMessage();
194 $result = new rex_api_result(false, $message);
195 $apiFunc->result = $result;
196 }
197 }
198 }
199 }
200
201 public static function hasMessage()
202 {
203 $apiFunc = self::factory();
204 $result = $apiFunc->getResult();
205 return $result && null !== $result->getMessage();
206 }
207
208 public static function getMessage($formatted = true)
209 {
210 $apiFunc = self::factory();
211 $message = '';
212 if ($apiFunc) {
213 $apiResult = $apiFunc->getResult();
214 if ($apiResult) {
215 if ($formatted) {
216 $message = $apiResult->getFormattedMessage();
217 } else {
218 $message = $apiResult->getMessage();
219 }
220 }
221 }
222
223 return '<div id="rex-message-container">' . $message . '</div>';
224 }
225
226 protected function __construct()
227 {
228
229 }
230
231 232 233
234 public function getResult()
235 {
236 return $this->result;
237 }
238
239 240 241 242 243 244
245 protected function requiresCsrfProtection()
246 {
247 return false;
248 }
249 }
250
251 252 253 254 255 256 257 258 259
260 class rex_api_result
261 {
262 263 264 265 266
267 private $succeeded = false;
268
269 270 271 272 273
274 private $message;
275
276 277 278 279 280 281
282 private $requiresReboot;
283
284 public function __construct($succeeded, $message = null)
285 {
286 $this->succeeded = $succeeded;
287 $this->message = $message;
288 }
289
290 public function setRequiresReboot($requiresReboot)
291 {
292 $this->requiresReboot = $requiresReboot;
293 }
294
295 public function requiresReboot()
296 {
297 return $this->requiresReboot;
298 }
299
300 public function getFormattedMessage()
301 {
302 if (null === $this->message) {
303 return null;
304 }
305
306 if ($this->isSuccessfull()) {
307 return rex_view::success($this->message);
308 }
309 return rex_view::error($this->message);
310 }
311
312 313 314 315 316
317 public function getMessage()
318 {
319 return $this->message;
320 }
321
322 323 324 325 326
327 public function isSuccessfull()
328 {
329 return $this->succeeded;
330 }
331
332 public function toJSON()
333 {
334 $json = new stdClass();
335 foreach ($this as $key => $value) {
336 $json->$key = $value;
337 }
338 return json_encode($json);
339 }
340
341 public static function fromJSON($json)
342 {
343 $result = new self(true);
344 $json = json_decode($json, true);
345 foreach ($json as $key => $value) {
346 $result->$key = $value;
347 }
348 return $result;
349 }
350 }
351
352 353 354 355 356 357 358 359 360 361
362 class rex_api_exception extends rex_exception
363 {
364 }
365