]> git.mxchange.org Git - friendica.git/blob - src/Module/Api/ApiResponse.php
Tests ...
[friendica.git] / src / Module / Api / ApiResponse.php
1 <?php
2
3 namespace Friendica\Module\Api;
4
5 use Friendica\App\Arguments;
6 use Friendica\Core\L10n;
7 use Friendica\Module\BaseApi;
8 use Friendica\Util\Arrays;
9 use Friendica\Util\HTTPInputData;
10 use Friendica\Util\XML;
11 use Psr\Log\LoggerInterface;
12
13 /**
14  * This class is used to format and return API responses
15  */
16 class ApiResponse
17 {
18         /** @var L10n */
19         protected $l10n;
20         /** @var Arguments */
21         protected $args;
22         /** @var LoggerInterface */
23         protected $logger;
24
25         /**
26          * @param L10n            $l10n
27          * @param Arguments       $args
28          * @param LoggerInterface $logger
29          */
30         public function __construct(L10n $l10n, Arguments $args, LoggerInterface $logger)
31         {
32                 $this->l10n   = $l10n;
33                 $this->args   = $args;
34                 $this->logger = $logger;
35         }
36
37         /**
38          * Sets header directly
39          * mainly used to override it for tests
40          *
41          * @param string $header
42          */
43         protected function setHeader(string $header)
44         {
45                 header($header);
46         }
47
48         /**
49          * Prints output directly to the caller
50          * mainly used to override it for tests
51          *
52          * @param string $output
53          */
54         protected function printOutput(string $output)
55         {
56                 echo $output;
57                 exit;
58         }
59
60         /**
61          * Creates the XML from a JSON style array
62          *
63          * @param array  $data         JSON style array
64          * @param string $root_element Name of the root element
65          *
66          * @return string The XML data
67          */
68         public function createXML(array $data, string $root_element): string
69         {
70                 $childname = key($data);
71                 $data2     = array_pop($data);
72
73                 $namespaces = [
74                         ''          => 'http://api.twitter.com',
75                         'statusnet' => 'http://status.net/schema/api/1/',
76                         'friendica' => 'http://friendi.ca/schema/api/1/',
77                         'georss'    => 'http://www.georss.org/georss'
78                 ];
79
80                 /// @todo Auto detection of needed namespaces
81                 if (in_array($root_element, ['ok', 'hash', 'config', 'version', 'ids', 'notes', 'photos'])) {
82                         $namespaces = [];
83                 }
84
85                 if (is_array($data2)) {
86                         $key = key($data2);
87                         Arrays::walkRecursive($data2, ['Friendica\Module\Api\ApiResponse', 'reformatXML']);
88
89                         if ($key == '0') {
90                                 $data4 = [];
91                                 $i     = 1;
92
93                                 foreach ($data2 as $item) {
94                                         $data4[$i++ . ':' . $childname] = $item;
95                                 }
96
97                                 $data2 = $data4;
98                         }
99                 }
100
101                 $data3 = [$root_element => $data2];
102
103                 return XML::fromArray($data3, $xml, false, $namespaces);
104         }
105
106         /**
107          * Formats the data according to the data type
108          *
109          * @param string $root_element Name of the root element
110          * @param string $type         Return type (atom, rss, xml, json)
111          * @param array  $data         JSON style array
112          * @param int    $cid          ID of the contact for RSS
113          *
114          * @return array|string (string|array) XML data or JSON data
115          */
116         public function formatData(string $root_element, string $type, array $data, int $cid = 0)
117         {
118                 switch ($type) {
119                         case 'rss':
120                                 $data = BaseApi::addRSSValues($data, $cid);
121                         case 'atom':
122                         case 'xml':
123                                 return $this->createXML($data, $root_element);
124                         case 'json':
125                         default:
126                                 return $data;
127                 }
128         }
129
130         /**
131          * Callback function to transform the array in an array that can be transformed in a XML file
132          *
133          * @param mixed  $item Array item value
134          * @param string $key  Array key
135          *
136          * @return boolean
137          */
138         public static function reformatXML(&$item, string &$key): bool
139         {
140                 if (is_bool($item)) {
141                         $item = ($item ? 'true' : 'false');
142                 }
143
144                 if (substr($key, 0, 10) == 'statusnet_') {
145                         $key = 'statusnet:' . substr($key, 10);
146                 } elseif (substr($key, 0, 10) == 'friendica_') {
147                         $key = 'friendica:' . substr($key, 10);
148                 }
149                 return true;
150         }
151
152         /**
153          * Exit with error code
154          *
155          * @param int         $code
156          * @param string      $description
157          * @param string      $message
158          * @param string|null $format
159          *
160          * @return void
161          */
162         public function error(int $code, string $description, string $message, string $format = null)
163         {
164                 $error = [
165                         'error'   => $message ?: $description,
166                         'code'    => $code . ' ' . $description,
167                         'request' => $this->args->getQueryString()
168                 ];
169
170                 $this->setHeader(($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1') . ' ' . $code . ' ' . $description);
171
172                 $this->exit('status', ['status' => $error], $format);
173         }
174
175         /**
176          * Outputs formatted data according to the data type and then exits the execution.
177          *
178          * @param string      $root_element
179          * @param array       $data   An array with a single element containing the returned result
180          * @param string|null $format Output format (xml, json, rss, atom)
181          *
182          * @return void
183          */
184         public function exit(string $root_element, array $data, string $format = null)
185         {
186                 $format = $format ?? 'json';
187
188                 $return = $this->formatData($root_element, $format, $data);
189
190                 switch ($format) {
191                         case 'xml':
192                                 $this->setHeader('Content-Type: text/xml');
193                                 break;
194                         case 'json':
195                                 $this->setHeader('Content-Type: application/json');
196                                 if (!empty($return)) {
197                                         $json = json_encode(end($return));
198                                         if (!empty($_GET['callback'])) {
199                                                 $json = $_GET['callback'] . '(' . $json . ')';
200                                         }
201                                         $return = $json;
202                                 }
203                                 break;
204                         case 'rss':
205                                 $this->setHeader('Content-Type: application/rss+xml');
206                                 break;
207                         case 'atom':
208                                 $this->setHeader('Content-Type: application/atom+xml');
209                                 break;
210                 }
211
212                 $this->printOutput($return);
213         }
214
215         /**
216          * Quit execution with the message that the endpoint isn't implemented
217          *
218          * @param string $method
219          *
220          * @return void
221          * @throws \Exception
222          */
223         public function unsupported(string $method = 'all')
224         {
225                 $path = $this->args->getQueryString();
226                 $this->logger->info('Unimplemented API call',
227                         [
228                                 'method'  => $method,
229                                 'path'    => $path,
230                                 'agent'   => $_SERVER['HTTP_USER_AGENT'] ?? '',
231                                 'request' => HTTPInputData::process()
232                         ]);
233                 $error             = $this->l10n->t('API endpoint %s %s is not implemented', strtoupper($method), $path);
234                 $error_description = $this->l10n->t('The API endpoint is currently not implemented but might be in the future.');
235
236                 $this->exit('error', ['error' => ['error' => $error, 'error_description' => $error_description]]);
237         }
238 }