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