]> git.mxchange.org Git - friendica.git/blob - include/api.php
287ae255468ff057e4034ffd36d526ec9d1f0b44
[friendica.git] / include / api.php
1 <?php
2 /**
3  * @file include/api.php
4  * Friendica implementation of statusnet/twitter API
5  *
6  * @todo Automatically detect if incoming data is HTML or BBCode
7  */
8
9 use \Friendica\Core\Config;
10
11 require_once 'include/HTTPExceptions.php';
12 require_once 'include/bbcode.php';
13 require_once 'include/datetime.php';
14 require_once 'include/conversation.php';
15 require_once 'include/oauth.php';
16 require_once 'include/html2plain.php';
17 require_once 'mod/share.php';
18 require_once 'include/Photo.php';
19 require_once 'mod/item.php';
20 require_once 'include/security.php';
21 require_once 'include/contact_selectors.php';
22 require_once 'include/html2bbcode.php';
23 require_once 'mod/wall_upload.php';
24 require_once 'mod/proxy.php';
25 require_once 'include/message.php';
26 require_once 'include/group.php';
27 require_once 'include/like.php';
28 require_once 'include/NotificationsManager.php';
29 require_once 'include/plaintext.php';
30 require_once 'include/xml.php';
31
32 define('API_METHOD_ANY', '*');
33 define('API_METHOD_GET', 'GET');
34 define('API_METHOD_POST', 'POST,PUT');
35 define('API_METHOD_DELETE', 'POST,DELETE');
36
37 $API = array();
38 $called_api = null;
39
40 /// @TODO Fix intending
41         /**
42          * @brief Auth API user
43          *
44          * It is not sufficient to use local_user() to check whether someone is allowed to use the API,
45          * because this will open CSRF holes (just embed an image with src=friendicasite.com/api/statuses/update?status=CSRF
46          * into a page, and visitors will post something without noticing it).
47          */
48         function api_user() {
49                 if (x($_SESSION, 'allow_api')) {
50                         return local_user();
51                 }
52
53                 return false;
54         }
55
56         /**
57          * @brief Get source name from API client
58          *
59          * Clients can send 'source' parameter to be show in post metadata
60          * as "sent via <source>".
61          * Some clients doesn't send a source param, we support ones we know
62          * (only Twidere, atm)
63          *
64          * @return string
65          *              Client source name, default to "api" if unset/unknown
66          */
67         function api_source() {
68                 if (requestdata('source')) {
69                         return requestdata('source');
70                 }
71
72                 // Support for known clients that doesn't send a source name
73                 if (strpos($_SERVER['HTTP_USER_AGENT'], "Twidere") !== false) {
74                         return "Twidere";
75                 }
76
77                 logger("Unrecognized user-agent ".$_SERVER['HTTP_USER_AGENT'], LOGGER_DEBUG);
78
79                 return "api";
80         }
81
82         /**
83          * @brief Format date for API
84          *
85          * @param string $str Source date, as UTC
86          * @return string Date in UTC formatted as "D M d H:i:s +0000 Y"
87          */
88         function api_date($str) {
89                 // Wed May 23 06:01:13 +0000 2007
90                 return datetime_convert('UTC', 'UTC', $str, "D M d H:i:s +0000 Y" );
91         }
92
93         /**
94          * @brief Register API endpoint
95          *
96          * Register a function to be the endpont for defined API path.
97          *
98          * @param string $path API URL path, relative to App::get_baseurl()
99          * @param string $func Function name to call on path request
100          * @param bool $auth API need logged user
101          * @param string $method HTTP method reqiured to call this endpoint.
102          *
103          * One of API_METHOD_ANY, API_METHOD_GET, API_METHOD_POST.
104          * Default to API_METHOD_ANY
105          */
106         function api_register_func($path, $func, $auth = false, $method = API_METHOD_ANY) {
107                 global $API;
108
109                 $API[$path] = array(
110                         'func'   => $func,
111                         'auth'   => $auth,
112                         'method' => $method,
113                 );
114
115                 // Workaround for hotot
116                 $path = str_replace("api/", "api/1.1/", $path);
117
118                 $API[$path] = array(
119                         'func'   => $func,
120                         'auth'   => $auth,
121                         'method' => $method,
122                 );
123         }
124
125         /**
126          * @brief Login API user
127          *
128          * Log in user via OAuth1 or Simple HTTP Auth.
129          * Simple Auth allow username in form of <pre>user@server</pre>, ignoring server part
130          *
131          * @param App $a
132          * @hook 'authenticate'
133          *              array $addon_auth
134          *                      'username' => username from login form
135          *                      'password' => password from login form
136          *                      'authenticated' => return status,
137          *                      'user_record' => return authenticated user record
138          * @hook 'logged_in'
139          *              array $user     logged user record
140          */
141         function api_login(App $a) {
142                 // login with oauth
143                 try {
144                         $oauth = new FKOAuth1();
145                         list($consumer,$token) = $oauth->verify_request(OAuthRequest::from_request());
146                         if (!is_null($token)) {
147                                 $oauth->loginUser($token->uid);
148                                 call_hooks('logged_in', $a->user);
149                                 return;
150                         }
151                         echo __FILE__.__LINE__.__FUNCTION__ . "<pre>";
152                         var_dump($consumer, $token);
153                         die();
154                 } catch (Exception $e) {
155                         logger($e);
156                 }
157
158                 // workaround for HTTP-auth in CGI mode
159                 if (x($_SERVER, 'REDIRECT_REMOTE_USER')) {
160                         $userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"], 6)) ;
161                         if (strlen($userpass)) {
162                                 list($name, $password) = explode(':', $userpass);
163                                 $_SERVER['PHP_AUTH_USER'] = $name;
164                                 $_SERVER['PHP_AUTH_PW'] = $password;
165                         }
166                 }
167
168                 if (!x($_SERVER, 'PHP_AUTH_USER')) {
169                         logger('API_login: ' . print_r($_SERVER,true), LOGGER_DEBUG);
170                         header('WWW-Authenticate: Basic realm="Friendica"');
171                         throw new UnauthorizedException("This API requires login");
172                 }
173
174                 $user = $_SERVER['PHP_AUTH_USER'];
175                 $password = $_SERVER['PHP_AUTH_PW'];
176                 $encrypted = hash('whirlpool', trim($password));
177
178                 // allow "user@server" login (but ignore 'server' part)
179                 $at = strstr($user, "@", true);
180                 if ($at) {
181                         $user = $at;
182                 }
183
184                 // next code from mod/auth.php. needs better solution
185                 $record = null;
186
187                 $addon_auth = array(
188                         'username' => trim($user),
189                         'password' => trim($password),
190                         'authenticated' => 0,
191                         'user_record' => null,
192                 );
193
194                 /*
195                  * A plugin indicates successful login by setting 'authenticated' to non-zero value and returning a user record
196                  * Plugins should never set 'authenticated' except to indicate success - as hooks may be chained
197                  * and later plugins should not interfere with an earlier one that succeeded.
198                  */
199                 call_hooks('authenticate', $addon_auth);
200
201                 if (($addon_auth['authenticated']) && (count($addon_auth['user_record']))) {
202                         $record = $addon_auth['user_record'];
203                 } else {
204                         // process normal login request
205                         $r = q("SELECT * FROM `user` WHERE (`email` = '%s' OR `nickname` = '%s')
206                                 AND `password` = '%s' AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified` LIMIT 1",
207                                 dbesc(trim($user)),
208                                 dbesc(trim($user)),
209                                 dbesc($encrypted)
210                         );
211                         if (dbm::is_result($r)) {
212                                 $record = $r[0];
213                         }
214                 }
215
216                 if ((! $record) || (! count($record))) {
217                         logger('API_login failure: ' . print_r($_SERVER, true), LOGGER_DEBUG);
218                         header('WWW-Authenticate: Basic realm="Friendica"');
219                         //header('HTTP/1.0 401 Unauthorized');
220                         //die('This api requires login');
221                         throw new UnauthorizedException("This API requires login");
222                 }
223
224                 authenticate_success($record);
225
226                 $_SESSION["allow_api"] = true;
227
228                 call_hooks('logged_in', $a->user);
229
230         }
231
232         /**
233          * @brief Check HTTP method of called API
234          *
235          * API endpoints can define which HTTP method to accept when called.
236          * This function check the current HTTP method agains endpoint
237          * registered method.
238          *
239          * @param string $method Required methods, uppercase, separated by comma
240          * @return bool
241          */
242          function api_check_method($method) {
243                 if ($method == "*") {
244                         return true;
245                 }
246                 return (strpos($method, $_SERVER['REQUEST_METHOD']) !== false);
247          }
248
249         /**
250          * @brief Main API entry point
251          *
252          * Authenticate user, call registered API function, set HTTP headers
253          *
254          * @param App $a
255          * @return string API call result
256          */
257         function api_call(App $a) {
258                 global $API, $called_api;
259
260                 $type = "json";
261                 if (strpos($a->query_string, ".xml") > 0) {
262                         $type = "xml";
263                 }
264                 if (strpos($a->query_string, ".json") > 0) {
265                         $type = "json";
266                 }
267                 if (strpos($a->query_string, ".rss") > 0) {
268                         $type = "rss";
269                 }
270                 if (strpos($a->query_string, ".atom") > 0) {
271                         $type = "atom";
272                 }
273
274                 try {
275                         foreach ($API as $p => $info) {
276                                 if (strpos($a->query_string, $p) === 0) {
277                                         if (!api_check_method($info['method'])) {
278                                                 throw new MethodNotAllowedException();
279                                         }
280
281                                         $called_api = explode("/", $p);
282                                         //unset($_SERVER['PHP_AUTH_USER']);
283
284                                         /// @TODO should be "true ==[=] $info['auth']", if you miss only one = character, you assign a variable (only with ==). Let's make all this even.
285                                         if ($info['auth'] === true && api_user() === false) {
286                                                 api_login($a);
287                                         }
288
289                                         logger('API call for ' . $a->user['username'] . ': ' . $a->query_string);
290                                         logger('API parameters: ' . print_r($_REQUEST, true));
291
292                                         $stamp =  microtime(true);
293                                         $r = call_user_func($info['func'], $type);
294                                         $duration = (float) (microtime(true) - $stamp);
295                                         logger("API call duration: " . round($duration, 2) . "\t" . $a->query_string, LOGGER_DEBUG);
296
297                                         if (get_config("system", "profiler")) {
298                                                 $duration = microtime(true)-$a->performance["start"];
299
300                                                 /// @TODO round() really everywhere?
301                                                 logger(parse_url($a->query_string, PHP_URL_PATH) . ": " . sprintf("Database: %s/%s, Network: %s, I/O: %s, Other: %s, Total: %s",
302                                                         round($a->performance["database"] - $a->performance["database_write"], 3),
303                                                         round($a->performance["database_write"], 3),
304                                                         round($a->performance["network"], 2),
305                                                         round($a->performance["file"], 2),
306                                                         round($duration - ($a->performance["database"] + $a->performance["network"]
307                                                                 + $a->performance["file"]), 2),
308                                                         round($duration, 2)),
309                                                         LOGGER_DEBUG
310                                                 );
311
312                                                 if (get_config("rendertime", "callstack")) {
313                                                         $o = "Database Read:\n";
314                                                         foreach ($a->callstack["database"] AS $func => $time) {
315                                                                 $time = round($time, 3);
316                                                                 if ($time > 0) {
317                                                                         $o .= $func . ": " . $time . "\n";
318                                                                 }
319                                                         }
320                                                         $o .= "\nDatabase Write:\n";
321                                                         foreach ($a->callstack["database_write"] AS $func => $time) {
322                                                                 $time = round($time, 3);
323                                                                 if ($time > 0) {
324                                                                         $o .= $func . ": " . $time . "\n";
325                                                                 }
326                                                         }
327
328                                                         $o .= "\nNetwork:\n";
329                                                         foreach ($a->callstack["network"] AS $func => $time) {
330                                                                 $time = round($time, 3);
331                                                                 if ($time > 0) {
332                                                                         $o .= $func . ": " . $time . "\n";
333                                                                 }
334                                                         }
335                                                         logger($o, LOGGER_DEBUG);
336                                                 }
337                                         }
338
339                                         if (false === $r) {
340                                                 /*
341                                                  * api function returned false withour throw an
342                                                  * exception. This should not happend, throw a 500
343                                                  */
344                                                 throw new InternalServerErrorException();
345                                         }
346
347                                         switch ($type) {
348                                                 case "xml":
349                                                         header ("Content-Type: text/xml");
350                                                         return $r;
351                                                         break;
352                                                 case "json":
353                                                         header ("Content-Type: application/json");
354                                                         foreach ($r as $rr)
355                                                                 $json = json_encode($rr);
356                                                                 if (x($_GET, 'callback')) {
357                                                                         $json = $_GET['callback'] . "(" . $json . ")";
358                                                                 }
359                                                                 return $json;
360                                                         break;
361                                                 case "rss":
362                                                         header ("Content-Type: application/rss+xml");
363                                                         return '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $r;
364                                                         break;
365                                                 case "atom":
366                                                         header ("Content-Type: application/atom+xml");
367                                                         return '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $r;
368                                                         break;
369                                         }
370                                 }
371                         }
372
373                         logger('API call not implemented: ' . $a->query_string);
374                         throw new NotImplementedException();
375                 } catch (HTTPException $e) {
376                         header("HTTP/1.1 {$e->httpcode} {$e->httpdesc}");
377                         return api_error($type, $e);
378                 }
379         }
380
381         /**
382          * @brief Format API error string
383          *
384          * @param string $type Return type (xml, json, rss, as)
385          * @param HTTPException $error Error object
386          * @return strin error message formatted as $type
387          */
388         function api_error($type, $e) {
389
390                 $a = get_app();
391
392                 $error = ($e->getMessage() !== "" ? $e->getMessage() : $e->httpdesc);
393                 /// @TODO:  https://dev.twitter.com/overview/api/response-codes
394
395                 $error = array("error" => $error,
396                                 "code" => $e->httpcode . " " . $e->httpdesc,
397                                 "request" => $a->query_string);
398
399                 $ret = api_format_data('status', $type, array('status' => $error));
400
401                 switch ($type) {
402                         case "xml":
403                                 header ("Content-Type: text/xml");
404                                 return $ret;
405                                 break;
406                         case "json":
407                                 header ("Content-Type: application/json");
408                                 return json_encode($ret);
409                                 break;
410                         case "rss":
411                                 header ("Content-Type: application/rss+xml");
412                                 return $ret;
413                                 break;
414                         case "atom":
415                                 header ("Content-Type: application/atom+xml");
416                                 return $ret;
417                                 break;
418                 }
419         }
420
421         /**
422          * @brief Set values for RSS template
423          *
424          * @param App $a
425          * @param array $arr Array to be passed to template
426          * @param array $user_info
427          * @return array
428          * @todo find proper type-hints
429          */
430         function api_rss_extra(App $a, $arr, $user_info) {
431                 if (is_null($user_info)) {
432                         $user_info = api_get_user($a);
433                 }
434
435                 $arr['$user'] = $user_info;
436                 $arr['$rss'] = array(
437                         'alternate'    => $user_info['url'],
438                         'self'         => App::get_baseurl() . "/" . $a->query_string,
439                         'base'         => App::get_baseurl(),
440                         'updated'      => api_date(null),
441                         'atom_updated' => datetime_convert('UTC', 'UTC', 'now', ATOM_TIME),
442                         'language'     => $user_info['language'],
443                         'logo'         => App::get_baseurl() . "/images/friendica-32.png",
444                 );
445
446                 return $arr;
447         }
448
449
450         /**
451          * @brief Unique contact to contact url.
452          *
453          * @param int $id Contact id
454          * @return bool|string
455          *              Contact url or False if contact id is unknown
456          */
457         function api_unique_id_to_url($id) {
458                 $r = q("SELECT `url` FROM `contact` WHERE `uid` = 0 AND `id` = %d LIMIT 1",
459                         intval($id));
460
461                 return (dbm::is_result($r) && $r[0]["url"]);
462         }
463
464         /**
465          * @brief Get user info array.
466          *
467          * @param Api $a
468          * @param int|string $contact_id Contact ID or URL
469          * @param string $type Return type (for errors)
470          */
471         function api_get_user(App $a, $contact_id = null, $type = "json") {
472                 global $called_api;
473
474                 $user = null;
475                 $extra_query = "";
476                 $url = "";
477                 $nick = "";
478
479                 logger("api_get_user: Fetching user data for user ".$contact_id, LOGGER_DEBUG);
480
481                 // Searching for contact URL
482                 if (!is_null($contact_id) AND (intval($contact_id) == 0)) {
483                         $user = dbesc(normalise_link($contact_id));
484                         $url = $user;
485                         $extra_query = "AND `contact`.`nurl` = '%s' ";
486                         if (api_user() !== false) {
487                                 $extra_query .= "AND `contact`.`uid`=" . intval(api_user());
488                         }
489                 }
490
491                 // Searching for contact id with uid = 0
492                 if (!is_null($contact_id) AND (intval($contact_id) != 0)) {
493                         $user = dbesc(api_unique_id_to_url($contact_id));
494
495                         if ($user == "") {
496                                 throw new BadRequestException("User not found.");
497                         }
498
499                         $url = $user;
500                         $extra_query = "AND `contact`.`nurl` = '%s' ";
501                         if (api_user() !== false) {
502                                 $extra_query .= "AND `contact`.`uid`=" . intval(api_user());
503                         }
504                 }
505
506                 if (is_null($user) && x($_GET, 'user_id')) {
507                         $user = dbesc(api_unique_id_to_url($_GET['user_id']));
508
509                         if ($user == "") {
510                                 throw new BadRequestException("User not found.");
511                         }
512
513                         $url = $user;
514                         $extra_query = "AND `contact`.`nurl` = '%s' ";
515                         if (api_user() !== false) {
516                                 $extra_query .= "AND `contact`.`uid`=" . intval(api_user());
517                         }
518                 }
519                 if (is_null($user) && x($_GET, 'screen_name')) {
520                         $user = dbesc($_GET['screen_name']);
521                         $nick = $user;
522                         $extra_query = "AND `contact`.`nick` = '%s' ";
523                         if (api_user() !== false) {
524                                 $extra_query .= "AND `contact`.`uid`=".intval(api_user());
525                         }
526                 }
527
528                 if (is_null($user) AND ($a->argc > (count($called_api) - 1)) AND (count($called_api) > 0)) {
529                         $argid = count($called_api);
530                         list($user, $null) = explode(".", $a->argv[$argid]);
531                         if (is_numeric($user)) {
532                                 $user = dbesc(api_unique_id_to_url($user));
533
534                                 if ($user == "") {
535                                         return false;
536                                 }
537
538                                 $url = $user;
539                                 $extra_query = "AND `contact`.`nurl` = '%s' ";
540                                 if (api_user() !== false) {
541                                         $extra_query .= "AND `contact`.`uid`=" . intval(api_user());
542                                 }
543                         } else {
544                                 $user = dbesc($user);
545                                 $nick = $user;
546                                 $extra_query = "AND `contact`.`nick` = '%s' ";
547                                 if (api_user() !== false) {
548                                         $extra_query .= "AND `contact`.`uid`=" . intval(api_user());
549                                 }
550                         }
551                 }
552
553                 logger("api_get_user: user ".$user, LOGGER_DEBUG);
554
555                 if (!$user) {
556                         if (api_user() === false) {
557                                 api_login($a);
558                                 return false;
559                         } else {
560                                 $user = $_SESSION['uid'];
561                                 $extra_query = "AND `contact`.`uid` = %d AND `contact`.`self` ";
562                         }
563
564                 }
565
566                 logger('api_user: ' . $extra_query . ', user: ' . $user);
567
568                 // user info
569                 $uinfo = q("SELECT *, `contact`.`id` AS `cid` FROM `contact`
570                                 WHERE 1
571                                 $extra_query",
572                                 $user
573                 );
574
575                 // Selecting the id by priority, friendica first
576                 api_best_nickname($uinfo);
577
578                 // if the contact wasn't found, fetch it from the contacts with uid = 0
579                 if (!dbm::is_result($uinfo)) {
580                         $r = array();
581
582                         if ($url != "") {
583                                 $r = q("SELECT * FROM `contact` WHERE `uid` = 0 AND `nurl` = '%s' LIMIT 1", dbesc(normalise_link($url)));
584                         }
585
586                         if (dbm::is_result($r)) {
587                                 $network_name = network_to_name($r[0]['network'], $r[0]['url']);
588
589                                 // If no nick where given, extract it from the address
590                                 if (($r[0]['nick'] == "") OR ($r[0]['name'] == $r[0]['nick'])) {
591                                         $r[0]['nick'] = api_get_nick($r[0]["url"]);
592                                 }
593
594                                 $ret = array(
595                                         'id' => $r[0]["id"],
596                                         'id_str' => (string) $r[0]["id"],
597                                         'name' => $r[0]["name"],
598                                         'screen_name' => (($r[0]['nick']) ? $r[0]['nick'] : $r[0]['name']),
599                                         'location' => ($r[0]["location"] != "") ? $r[0]["location"] : $network_name,
600                                         'description' => $r[0]["about"],
601                                         'profile_image_url' => $r[0]["micro"],
602                                         'profile_image_url_https' => $r[0]["micro"],
603                                         'url' => $r[0]["url"],
604                                         'protected' => false,
605                                         'followers_count' => 0,
606                                         'friends_count' => 0,
607                                         'listed_count' => 0,
608                                         'created_at' => api_date($r[0]["created"]),
609                                         'favourites_count' => 0,
610                                         'utc_offset' => 0,
611                                         'time_zone' => 'UTC',
612                                         'geo_enabled' => false,
613                                         'verified' => false,
614                                         'statuses_count' => 0,
615                                         'lang' => '',
616                                         'contributors_enabled' => false,
617                                         'is_translator' => false,
618                                         'is_translation_enabled' => false,
619                                         'following' => false,
620                                         'follow_request_sent' => false,
621                                         'statusnet_blocking' => false,
622                                         'notifications' => false,
623                                         'statusnet_profile_url' => $r[0]["url"],
624                                         'uid' => 0,
625                                         'cid' => get_contact($r[0]["url"], api_user(), true),
626                                         'self' => 0,
627                                         'network' => $r[0]["network"],
628                                 );
629
630                                 return $ret;
631                         } else {
632                                 throw new BadRequestException("User not found.");
633                         }
634                 }
635
636                 if ($uinfo[0]['self']) {
637
638                         if ($uinfo[0]['network'] == "") {
639                                 $uinfo[0]['network'] = NETWORK_DFRN;
640                         }
641
642                         $usr = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
643                                 intval(api_user())
644                         );
645                         $profile = q("SELECT * FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1",
646                                 intval(api_user())
647                         );
648
649                         /// @TODO old-lost code? (twice)
650                         // Counting is deactivated by now, due to performance issues
651                         // count public wall messages
652                         //$r = q("SELECT COUNT(*) as `count` FROM `item` WHERE `uid` = %d AND `wall`",
653                         //              intval($uinfo[0]['uid'])
654                         //);
655                         //$countitms = $r[0]['count'];
656                         $countitms = 0;
657                 } else {
658                         // Counting is deactivated by now, due to performance issues
659                         //$r = q("SELECT count(*) as `count` FROM `item`
660                         //              WHERE  `contact-id` = %d",
661                         //              intval($uinfo[0]['id'])
662                         //);
663                         //$countitms = $r[0]['count'];
664                         $countitms = 0;
665                 }
666
667                 /// @TODO old-lost code? (twice)
668 /*
669                 // Counting is deactivated by now, due to performance issues
670                 // count friends
671                 $r = q("SELECT count(*) as `count` FROM `contact`
672                                 WHERE  `uid` = %d AND `rel` IN ( %d, %d )
673                                 AND `self`=0 AND NOT `blocked` AND NOT `pending` AND `hidden`=0",
674                                 intval($uinfo[0]['uid']),
675                                 intval(CONTACT_IS_SHARING),
676                                 intval(CONTACT_IS_FRIEND)
677                 );
678                 $countfriends = $r[0]['count'];
679
680                 $r = q("SELECT count(*) as `count` FROM `contact`
681                                 WHERE  `uid` = %d AND `rel` IN ( %d, %d )
682                                 AND `self`=0 AND NOT `blocked` AND NOT `pending` AND `hidden`=0",
683                                 intval($uinfo[0]['uid']),
684                                 intval(CONTACT_IS_FOLLOWER),
685                                 intval(CONTACT_IS_FRIEND)
686                 );
687                 $countfollowers = $r[0]['count'];
688
689                 $r = q("SELECT count(*) as `count` FROM item where starred = 1 and uid = %d and deleted = 0",
690                         intval($uinfo[0]['uid'])
691                 );
692                 $starred = $r[0]['count'];
693
694
695                 if (! $uinfo[0]['self']) {
696                         $countfriends = 0;
697                         $countfollowers = 0;
698                         $starred = 0;
699                 }
700 */
701                 $countfriends = 0;
702                 $countfollowers = 0;
703                 $starred = 0;
704
705                 // Add a nick if it isn't present there
706                 if (($uinfo[0]['nick'] == "") OR ($uinfo[0]['name'] == $uinfo[0]['nick'])) {
707                         $uinfo[0]['nick'] = api_get_nick($uinfo[0]["url"]);
708                 }
709
710                 $network_name = network_to_name($uinfo[0]['network'], $uinfo[0]['url']);
711
712                 $pcontact_id  = get_contact($uinfo[0]['url'], 0, true);
713
714                 $ret = array(
715                         'id' => intval($pcontact_id),
716                         'id_str' => (string) intval($pcontact_id),
717                         'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']),
718                         'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']),
719                         'location' => ($usr) ? $usr[0]['default-location'] : $network_name,
720                         'description' => (($profile) ? $profile[0]['pdesc'] : NULL),
721                         'profile_image_url' => $uinfo[0]['micro'],
722                         'profile_image_url_https' => $uinfo[0]['micro'],
723                         'url' => $uinfo[0]['url'],
724                         'protected' => false,
725                         'followers_count' => intval($countfollowers),
726                         'friends_count' => intval($countfriends),
727                         'listed_count' => 0,
728                         'created_at' => api_date($uinfo[0]['created']),
729                         'favourites_count' => intval($starred),
730                         'utc_offset' => "0",
731                         'time_zone' => 'UTC',
732                         'geo_enabled' => false,
733                         'verified' => true,
734                         'statuses_count' => intval($countitms),
735                         'lang' => '',
736                         'contributors_enabled' => false,
737                         'is_translator' => false,
738                         'is_translation_enabled' => false,
739                         'following' => (($uinfo[0]['rel'] == CONTACT_IS_FOLLOWER) OR ($uinfo[0]['rel'] == CONTACT_IS_FRIEND)),
740                         'follow_request_sent' => false,
741                         'statusnet_blocking' => false,
742                         'notifications' => false,
743                         /// @TODO old way?
744                         //'statusnet_profile_url' => App::get_baseurl()."/contacts/".$uinfo[0]['cid'],
745                         'statusnet_profile_url' => $uinfo[0]['url'],
746                         'uid' => intval($uinfo[0]['uid']),
747                         'cid' => intval($uinfo[0]['cid']),
748                         'self' => $uinfo[0]['self'],
749                         'network' => $uinfo[0]['network'],
750                 );
751
752                 return $ret;
753
754         }
755
756         /**
757          * @brief return api-formatted array for item's author and owner
758          *
759          * @param App $a
760          * @param array $item : item from db
761          * @return array(array:author, array:owner)
762          */
763         function api_item_get_user(App $a, $item) {
764
765                 $status_user = api_get_user($a, $item["author-link"]);
766
767                 $status_user["protected"] = (($item["allow_cid"] != "") OR
768                                                 ($item["allow_gid"] != "") OR
769                                                 ($item["deny_cid"] != "") OR
770                                                 ($item["deny_gid"] != "") OR
771                                                 $item["private"]);
772
773                 $owner_user = api_get_user($a, $item["owner-link"]);
774
775                 return (array($status_user, $owner_user));
776         }
777
778         /**
779          * @brief walks recursively through an array with the possibility to change value and key
780          *
781          * @param array $array The array to walk through
782          * @param string $callback The callback function
783          *
784          * @return array the transformed array
785          */
786         function api_walk_recursive(array &$array, callable $callback) {
787
788                 $new_array = array();
789
790                 foreach ($array as $k => $v) {
791                         if (is_array($v)) {
792                                 if ($callback($v, $k)) {
793                                         $new_array[$k] = api_walk_recursive($v, $callback);
794                                 }
795                         } else {
796                                 if ($callback($v, $k)) {
797                                         $new_array[$k] = $v;
798                                 }
799                         }
800                 }
801                 $array = $new_array;
802
803                 return $array;
804         }
805
806         /**
807          * @brief Callback function to transform the array in an array that can be transformed in a XML file
808          *
809          * @param variant $item Array item value
810          * @param string $key Array key
811          *
812          * @return boolean Should the array item be deleted?
813          */
814         function api_reformat_xml(&$item, &$key) {
815                 if (is_bool($item)) {
816                         $item = ($item ? "true" : "false");
817                 }
818
819                 if (substr($key, 0, 10) == "statusnet_") {
820                         $key = "statusnet:".substr($key, 10);
821                 } elseif (substr($key, 0, 10) == "friendica_") {
822                         $key = "friendica:".substr($key, 10);
823                 }
824                 /// @TODO old-lost code?
825                 //else
826                 //      $key = "default:".$key;
827
828                 return true;
829         }
830
831         /**
832          * @brief Creates the XML from a JSON style array
833          *
834          * @param array $data JSON style array
835          * @param string $root_element Name of the root element
836          *
837          * @return string The XML data
838          */
839         function api_create_xml($data, $root_element) {
840                 $childname = key($data);
841                 $data2 = array_pop($data);
842                 $key = key($data2);
843
844                 $namespaces = array("" => "http://api.twitter.com",
845                                         "statusnet" => "http://status.net/schema/api/1/",
846                                         "friendica" => "http://friendi.ca/schema/api/1/",
847                                         "georss" => "http://www.georss.org/georss");
848
849                 /// @todo Auto detection of needed namespaces
850                 if (in_array($root_element, array("ok", "hash", "config", "version", "ids", "notes", "photos"))) {
851                         $namespaces = array();
852                 }
853
854                 if (is_array($data2)) {
855                         api_walk_recursive($data2, "api_reformat_xml");
856                 }
857
858                 if ($key == "0") {
859                         $data4 = array();
860                         $i = 1;
861
862                         foreach ($data2 AS $item) {
863                                 $data4[$i++.":".$childname] = $item;
864                         }
865
866                         $data2 = $data4;
867                 }
868
869                 $data3 = array($root_element => $data2);
870
871                 $ret = xml::from_array($data3, $xml, false, $namespaces);
872                 return $ret;
873         }
874
875         /**
876          * @brief Formats the data according to the data type
877          *
878          * @param string $root_element Name of the root element
879          * @param string $type Return type (atom, rss, xml, json)
880          * @param array $data JSON style array
881          *
882          * @return (string|object) XML data or JSON data
883          */
884         function api_format_data($root_element, $type, $data) {
885
886                 $a = get_app();
887
888                 switch ($type) {
889                         case "atom":
890                         case "rss":
891                         case "xml":
892                                 $ret = api_create_xml($data, $root_element);
893                                 break;
894                         case "json":
895                                 $ret = $data;
896                                 break;
897                 }
898
899                 return $ret;
900         }
901
902         /**
903          ** TWITTER API
904          */
905
906         /**
907          * Returns an HTTP 200 OK response code and a representation of the requesting user if authentication was successful;
908          * returns a 401 status code and an error message if not.
909          * http://developer.twitter.com/doc/get/account/verify_credentials
910          */
911         function api_account_verify_credentials($type) {
912
913                 $a = get_app();
914
915                 if (api_user() === false) {
916                         throw new ForbiddenException();
917                 }
918
919                 unset($_REQUEST["user_id"]);
920                 unset($_GET["user_id"]);
921
922                 unset($_REQUEST["screen_name"]);
923                 unset($_GET["screen_name"]);
924
925                 $skip_status = (x($_REQUEST, 'skip_status')?$_REQUEST['skip_status'] : false);
926
927                 $user_info = api_get_user($a);
928
929                 // "verified" isn't used here in the standard
930                 unset($user_info["verified"]);
931
932                 // - Adding last status
933                 if (!$skip_status) {
934                         $user_info["status"] = api_status_show("raw");
935                         if (!count($user_info["status"])) {
936                                 unset($user_info["status"]);
937                         } else {
938                                 unset($user_info["status"]["user"]);
939                         }
940                 }
941
942                 // "uid" and "self" are only needed for some internal stuff, so remove it from here
943                 unset($user_info["uid"]);
944                 unset($user_info["self"]);
945
946                 return api_format_data("user", $type, array('user' => $user_info));
947
948         }
949
950         /// @TODO move to top of file or somwhere better
951         api_register_func('api/account/verify_credentials','api_account_verify_credentials', true);
952
953         /**
954          * get data from $_POST or $_GET
955          */
956         function requestdata($k) {
957                 if (x($_POST, $k)) {
958                         return $_POST[$k];
959                 }
960                 if (x($_GET, $k)) {
961                         return $_GET[$k];
962                 }
963                 return null;
964         }
965
966 /*Waitman Gobble Mod*/
967         function api_statuses_mediap($type) {
968
969                 $a = get_app();
970
971                 if (api_user() === false) {
972                         logger('api_statuses_update: no user');
973                         throw new ForbiddenException();
974                 }
975                 $user_info = api_get_user($a);
976
977                 $_REQUEST['type'] = 'wall';
978                 $_REQUEST['profile_uid'] = api_user();
979                 $_REQUEST['api_source'] = true;
980                 $txt = requestdata('status');
981                 /// @TODO old-lost code?
982                 //$txt = urldecode(requestdata('status'));
983
984                 if ((strpos($txt,'<') !== false) || (strpos($txt,'>') !== false)) {
985
986                         $txt = html2bb_video($txt);
987                         $config = HTMLPurifier_Config::createDefault();
988                         $config->set('Cache.DefinitionImpl', null);
989                         $purifier = new HTMLPurifier($config);
990                         $txt = $purifier->purify($txt);
991                 }
992                 $txt = html2bbcode($txt);
993
994                 $a->argv[1]=$user_info['screen_name']; //should be set to username?
995
996                 // tell wall_upload function to return img info instead of echo
997                 $_REQUEST['hush'] = 'yeah';
998                 $bebop = wall_upload_post($a);
999
1000                 // now that we have the img url in bbcode we can add it to the status and insert the wall item.
1001                 $_REQUEST['body'] = $txt . "\n\n" . $bebop;
1002                 item_post($a);
1003
1004                 // this should output the last post (the one we just posted).
1005                 return api_status_show($type);
1006         }
1007
1008         /// @TODO move this to top of file or somewhere better!
1009         api_register_func('api/statuses/mediap','api_statuses_mediap', true, API_METHOD_POST);
1010
1011         function api_statuses_update($type) {
1012
1013                 $a = get_app();
1014
1015                 if (api_user() === false) {
1016                         logger('api_statuses_update: no user');
1017                         throw new ForbiddenException();
1018                 }
1019
1020                 $user_info = api_get_user($a);
1021
1022                 // convert $_POST array items to the form we use for web posts.
1023
1024                 // logger('api_post: ' . print_r($_POST,true));
1025
1026                 if (requestdata('htmlstatus')) {
1027                         $txt = requestdata('htmlstatus');
1028                         if ((strpos($txt, '<') !== false) || (strpos($txt, '>') !== false)) {
1029                                 $txt = html2bb_video($txt);
1030
1031                                 $config = HTMLPurifier_Config::createDefault();
1032                                 $config->set('Cache.DefinitionImpl', null);
1033
1034                                 $purifier = new HTMLPurifier($config);
1035                                 $txt = $purifier->purify($txt);
1036
1037                                 $_REQUEST['body'] = html2bbcode($txt);
1038                         }
1039
1040                 } else {
1041                         $_REQUEST['body'] = requestdata('status');
1042                 }
1043
1044                 $_REQUEST['title'] = requestdata('title');
1045
1046                 $parent = requestdata('in_reply_to_status_id');
1047
1048                 // Twidere sends "-1" if it is no reply ...
1049                 if ($parent == -1) {
1050                         $parent = "";
1051                 }
1052
1053                 if (ctype_digit($parent)) {
1054                         $_REQUEST['parent'] = $parent;
1055                 } else {
1056                         $_REQUEST['parent_uri'] = $parent;
1057                 }
1058
1059                 if (requestdata('lat') && requestdata('long')) {
1060                         $_REQUEST['coord'] = sprintf("%s %s", requestdata('lat'), requestdata('long'));
1061                 }
1062                 $_REQUEST['profile_uid'] = api_user();
1063
1064                 if ($parent) {
1065                         $_REQUEST['type'] = 'net-comment';
1066                 } else {
1067                         // Check for throttling (maximum posts per day, week and month)
1068                         $throttle_day = get_config('system','throttle_limit_day');
1069                         if ($throttle_day > 0) {
1070                                 $datefrom = date("Y-m-d H:i:s", time() - 24*60*60);
1071
1072                                 $r = q("SELECT COUNT(*) AS `posts_day` FROM `item` WHERE `uid`=%d AND `wall`
1073                                         AND `created` > '%s' AND `id` = `parent`",
1074                                         intval(api_user()), dbesc($datefrom));
1075
1076                                 if (dbm::is_result($r)) {
1077                                         $posts_day = $r[0]["posts_day"];
1078                                 } else {
1079                                         $posts_day = 0;
1080                                 }
1081
1082                                 if ($posts_day > $throttle_day) {
1083                                         logger('Daily posting limit reached for user '.api_user(), LOGGER_DEBUG);
1084                                         // die(api_error($type, sprintf(t("Daily posting limit of %d posts reached. The post was rejected."), $throttle_day)));
1085                                         throw new TooManyRequestsException(sprintf(t("Daily posting limit of %d posts reached. The post was rejected."), $throttle_day));
1086                                 }
1087                         }
1088
1089                         $throttle_week = get_config('system','throttle_limit_week');
1090                         if ($throttle_week > 0) {
1091                                 $datefrom = date("Y-m-d H:i:s", time() - 24*60*60*7);
1092
1093                                 $r = q("SELECT COUNT(*) AS `posts_week` FROM `item` WHERE `uid`=%d AND `wall`
1094                                         AND `created` > '%s' AND `id` = `parent`",
1095                                         intval(api_user()), dbesc($datefrom));
1096
1097                                 if (dbm::is_result($r)) {
1098                                         $posts_week = $r[0]["posts_week"];
1099                                 } else {
1100                                         $posts_week = 0;
1101                                 }
1102
1103                                 if ($posts_week > $throttle_week) {
1104                                         logger('Weekly posting limit reached for user '.api_user(), LOGGER_DEBUG);
1105                                         // die(api_error($type, sprintf(t("Weekly posting limit of %d posts reached. The post was rejected."), $throttle_week)));
1106                                         throw new TooManyRequestsException(sprintf(t("Weekly posting limit of %d posts reached. The post was rejected."), $throttle_week));
1107                                 }
1108                         }
1109
1110                         $throttle_month = get_config('system','throttle_limit_month');
1111                         if ($throttle_month > 0) {
1112                                 $datefrom = date("Y-m-d H:i:s", time() - 24*60*60*30);
1113
1114                                 $r = q("SELECT COUNT(*) AS `posts_month` FROM `item` WHERE `uid`=%d AND `wall`
1115                                         AND `created` > '%s' AND `id` = `parent`",
1116                                         intval(api_user()), dbesc($datefrom));
1117
1118                                 if (dbm::is_result($r)) {
1119                                         $posts_month = $r[0]["posts_month"];
1120                                 } else {
1121                                         $posts_month = 0;
1122                                 }
1123
1124                                 if ($posts_month > $throttle_month) {
1125                                         logger('Monthly posting limit reached for user '.api_user(), LOGGER_DEBUG);
1126                                         // die(api_error($type, sprintf(t("Monthly posting limit of %d posts reached. The post was rejected."), $throttle_month)));
1127                                         throw new TooManyRequestsException(sprintf(t("Monthly posting limit of %d posts reached. The post was rejected."), $throttle_month));
1128                                 }
1129                         }
1130
1131                         $_REQUEST['type'] = 'wall';
1132                 }
1133
1134                 if (x($_FILES, 'media')) {
1135                         // upload the image if we have one
1136                         $_REQUEST['hush'] = 'yeah'; //tell wall_upload function to return img info instead of echo
1137                         $media = wall_upload_post($a);
1138                         if (strlen($media) > 0) {
1139                                 $_REQUEST['body'] .= "\n\n" . $media;
1140                         }
1141                 }
1142
1143                 // To-Do: Multiple IDs
1144                 if (requestdata('media_ids')) {
1145                         $r = q("SELECT `resource-id`, `scale`, `nickname`, `type` FROM `photo` INNER JOIN `user` ON `user`.`uid` = `photo`.`uid` WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = %d) AND `scale` > 0 AND `photo`.`uid` = %d ORDER BY `photo`.`width` DESC LIMIT 1",
1146                                 intval(requestdata('media_ids')), api_user());
1147                         if (dbm::is_result($r)) {
1148                                 $phototypes = Photo::supportedTypes();
1149                                 $ext = $phototypes[$r[0]['type']];
1150                                 $_REQUEST['body'] .= "\n\n" . '[url=' . App::get_baseurl() . '/photos/' . $r[0]['nickname'] . '/image/' . $r[0]['resource-id'] . ']';
1151                                 $_REQUEST['body'] .= '[img]' . App::get_baseurl() . '/photo/' . $r[0]['resource-id'] . '-' . $r[0]['scale'] . '.' . $ext . '[/img][/url]';
1152                         }
1153                 }
1154
1155                 // set this so that the item_post() function is quiet and doesn't redirect or emit json
1156
1157                 $_REQUEST['api_source'] = true;
1158
1159                 if (!x($_REQUEST, "source")) {
1160                         $_REQUEST["source"] = api_source();
1161                 }
1162
1163                 // call out normal post function
1164                 item_post($a);
1165
1166                 // this should output the last post (the one we just posted).
1167                 return api_status_show($type);
1168         }
1169
1170         /// @TODO move to top of file or somwhere better
1171         api_register_func('api/statuses/update','api_statuses_update', true, API_METHOD_POST);
1172         api_register_func('api/statuses/update_with_media','api_statuses_update', true, API_METHOD_POST);
1173
1174         function api_media_upload($type) {
1175
1176                 $a = get_app();
1177
1178                 if (api_user() === false) {
1179                         logger('no user');
1180                         throw new ForbiddenException();
1181                 }
1182
1183                 $user_info = api_get_user($a);
1184
1185                 if (!x($_FILES, 'media')) {
1186                         // Output error
1187                         throw new BadRequestException("No media.");
1188                 }
1189
1190                 $media = wall_upload_post($a, false);
1191                 if (!$media) {
1192                         // Output error
1193                         throw new InternalServerErrorException();
1194                 }
1195
1196                 $returndata = array();
1197                 $returndata["media_id"] = $media["id"];
1198                 $returndata["media_id_string"] = (string)$media["id"];
1199                 $returndata["size"] = $media["size"];
1200                 $returndata["image"] = array("w" => $media["width"],
1201                                                 "h" => $media["height"],
1202                                                 "image_type" => $media["type"]);
1203
1204                 logger("Media uploaded: " . print_r($returndata, true), LOGGER_DEBUG);
1205
1206                 return array("media" => $returndata);
1207         }
1208
1209         /// @TODO move to top of file or somwhere better
1210         api_register_func('api/media/upload','api_media_upload', true, API_METHOD_POST);
1211
1212         function api_status_show($type) {
1213
1214                 $a = get_app();
1215
1216                 $user_info = api_get_user($a);
1217
1218                 logger('api_status_show: user_info: '.print_r($user_info, true), LOGGER_DEBUG);
1219
1220                 if ($type == "raw") {
1221                         $privacy_sql = "AND `item`.`allow_cid`='' AND `item`.`allow_gid`='' AND `item`.`deny_cid`='' AND `item`.`deny_gid`=''";
1222                 } else {
1223                         $privacy_sql = "";
1224                 }
1225
1226                 // get last public wall message
1227                 $lastwall = q("SELECT `item`.*
1228                                 FROM `item`
1229                                 WHERE `item`.`contact-id` = %d AND `item`.`uid` = %d
1230                                         AND ((`item`.`author-link` IN ('%s', '%s')) OR (`item`.`owner-link` IN ('%s', '%s')))
1231                                         AND `item`.`type` != 'activity' $privacy_sql
1232                                 ORDER BY `item`.`id` DESC
1233                                 LIMIT 1",
1234                                 intval($user_info['cid']),
1235                                 intval(api_user()),
1236                                 dbesc($user_info['url']),
1237                                 dbesc(normalise_link($user_info['url'])),
1238                                 dbesc($user_info['url']),
1239                                 dbesc(normalise_link($user_info['url']))
1240                 );
1241
1242                 if (dbm::is_result($lastwall)) {
1243                         $lastwall = $lastwall[0];
1244
1245                         $in_reply_to = api_in_reply_to($lastwall);
1246
1247                         $converted = api_convert_item($lastwall);
1248
1249                         if ($type == "xml") {
1250                                 $geo = "georss:point";
1251                         } else {
1252                                 $geo = "geo";
1253                         }
1254
1255                         $status_info = array(
1256                                 'created_at' => api_date($lastwall['created']),
1257                                 'id' => intval($lastwall['id']),
1258                                 'id_str' => (string) $lastwall['id'],
1259                                 'text' => $converted["text"],
1260                                 'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
1261                                 'truncated' => false,
1262                                 'in_reply_to_status_id' => $in_reply_to['status_id'],
1263                                 'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
1264                                 'in_reply_to_user_id' => $in_reply_to['user_id'],
1265                                 'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
1266                                 'in_reply_to_screen_name' => $in_reply_to['screen_name'],
1267                                 'user' => $user_info,
1268                                 $geo => NULL,
1269                                 'coordinates' => "",
1270                                 'place' => "",
1271                                 'contributors' => "",
1272                                 'is_quote_status' => false,
1273                                 'retweet_count' => 0,
1274                                 'favorite_count' => 0,
1275                                 'favorited' => $lastwall['starred'] ? true : false,
1276                                 'retweeted' => false,
1277                                 'possibly_sensitive' => false,
1278                                 'lang' => "",
1279                                 'statusnet_html'                => $converted["html"],
1280                                 'statusnet_conversation_id'     => $lastwall['parent'],
1281                         );
1282
1283                         if (count($converted["attachments"]) > 0) {
1284                                 $status_info["attachments"] = $converted["attachments"];
1285                         }
1286
1287                         if (count($converted["entities"]) > 0) {
1288                                 $status_info["entities"] = $converted["entities"];
1289                         }
1290
1291                         if (($lastwall['item_network'] != "") AND ($status["source"] == 'web')) {
1292                                 $status_info["source"] = network_to_name($lastwall['item_network'], $user_info['url']);
1293                         } elseif (($lastwall['item_network'] != "") AND (network_to_name($lastwall['item_network'], $user_info['url']) != $status_info["source"])) {
1294                                 $status_info["source"] = trim($status_info["source"].' ('.network_to_name($lastwall['item_network'], $user_info['url']).')');
1295                         }
1296
1297                         // "uid" and "self" are only needed for some internal stuff, so remove it from here
1298                         unset($status_info["user"]["uid"]);
1299                         unset($status_info["user"]["self"]);
1300                 }
1301
1302                 logger('status_info: '.print_r($status_info, true), LOGGER_DEBUG);
1303
1304                 if ($type == "raw") {
1305                         return $status_info;
1306                 }
1307
1308                 return api_format_data("statuses", $type, array('status' => $status_info));
1309
1310         }
1311
1312         /**
1313          * Returns extended information of a given user, specified by ID or screen name as per the required id parameter.
1314          * The author's most recent status will be returned inline.
1315          * http://developer.twitter.com/doc/get/users/show
1316          */
1317         function api_users_show($type) {
1318
1319                 $a = get_app();
1320
1321                 $user_info = api_get_user($a);
1322                 $lastwall = q("SELECT `item`.*
1323                                 FROM `item`
1324                                 INNER JOIN `contact` ON `contact`.`id`=`item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
1325                                 WHERE `item`.`uid` = %d AND `verb` = '%s' AND `item`.`contact-id` = %d
1326                                         AND ((`item`.`author-link` IN ('%s', '%s')) OR (`item`.`owner-link` IN ('%s', '%s')))
1327                                         AND `type`!='activity'
1328                                         AND `item`.`allow_cid`='' AND `item`.`allow_gid`='' AND `item`.`deny_cid`='' AND `item`.`deny_gid`=''
1329                                 ORDER BY `id` DESC
1330                                 LIMIT 1",
1331                                 intval(api_user()),
1332                                 dbesc(ACTIVITY_POST),
1333                                 intval($user_info['cid']),
1334                                 dbesc($user_info['url']),
1335                                 dbesc(normalise_link($user_info['url'])),
1336                                 dbesc($user_info['url']),
1337                                 dbesc(normalise_link($user_info['url']))
1338                 );
1339
1340                 if (dbm::is_result($lastwall)) {
1341                         $lastwall = $lastwall[0];
1342
1343                         $in_reply_to = api_in_reply_to($lastwall);
1344
1345                         $converted = api_convert_item($lastwall);
1346
1347                         if ($type == "xml") {
1348                                 $geo = "georss:point";
1349                         } else {
1350                                 $geo = "geo";
1351                         }
1352
1353                         $user_info['status'] = array(
1354                                 'text' => $converted["text"],
1355                                 'truncated' => false,
1356                                 'created_at' => api_date($lastwall['created']),
1357                                 'in_reply_to_status_id' => $in_reply_to['status_id'],
1358                                 'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
1359                                 'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
1360                                 'id' => intval($lastwall['contact-id']),
1361                                 'id_str' => (string) $lastwall['contact-id'],
1362                                 'in_reply_to_user_id' => $in_reply_to['user_id'],
1363                                 'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
1364                                 'in_reply_to_screen_name' => $in_reply_to['screen_name'],
1365                                 $geo => NULL,
1366                                 'favorited' => $lastwall['starred'] ? true : false,
1367                                 'statusnet_html' => $converted["html"],
1368                                 'statusnet_conversation_id'     => $lastwall['parent'],
1369                         );
1370
1371                         if (count($converted["attachments"]) > 0) {
1372                                 $user_info["status"]["attachments"] = $converted["attachments"];
1373                         }
1374
1375                         if (count($converted["entities"]) > 0) {
1376                                 $user_info["status"]["entities"] = $converted["entities"];
1377                         }
1378
1379                         if (($lastwall['item_network'] != "") AND ($user_info["status"]["source"] == 'web')) {
1380                                 $user_info["status"]["source"] = network_to_name($lastwall['item_network'], $user_info['url']);
1381                         }
1382
1383                         if (($lastwall['item_network'] != "") AND (network_to_name($lastwall['item_network'], $user_info['url']) != $user_info["status"]["source"])) {
1384                                 $user_info["status"]["source"] = trim($user_info["status"]["source"] . ' (' . network_to_name($lastwall['item_network'], $user_info['url']) . ')');
1385                         }
1386
1387                 }
1388
1389                 // "uid" and "self" are only needed for some internal stuff, so remove it from here
1390                 unset($user_info["uid"]);
1391                 unset($user_info["self"]);
1392
1393                 return api_format_data("user", $type, array('user' => $user_info));
1394
1395         }
1396
1397         /// @TODO move to top of file or somewhere better
1398         api_register_func('api/users/show','api_users_show');
1399
1400         function api_users_search($type) {
1401
1402                 $a = get_app();
1403
1404                 $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] - 1 : 0);
1405
1406                 $userlist = array();
1407
1408                 if (x($_GET, 'q')) {
1409                         $r = q("SELECT id FROM `contact` WHERE `uid` = 0 AND `name` = '%s'", dbesc($_GET["q"]));
1410
1411                         if (!dbm::is_result($r)) {
1412                                 $r = q("SELECT `id` FROM `contact` WHERE `uid` = 0 AND `nick` = '%s'", dbesc($_GET["q"]));
1413                         }
1414
1415                         if (dbm::is_result($r)) {
1416                                 $k = 0;
1417                                 foreach ($r AS $user) {
1418                                         $user_info = api_get_user($a, $user["id"], "json");
1419
1420                                         if ($type == "xml") {
1421                                                 $userlist[$k++.":user"] = $user_info;
1422                                         } else {
1423                                                 $userlist[] = $user_info;
1424                                         }
1425                                 }
1426                                 $userlist = array("users" => $userlist);
1427                         } else {
1428                                 throw new BadRequestException("User not found.");
1429                         }
1430                 } else {
1431                         throw new BadRequestException("User not found.");
1432                 }
1433                 return api_format_data("users", $type, $userlist);
1434         }
1435
1436         /// @TODO move to top of file or somewhere better
1437         api_register_func('api/users/search','api_users_search');
1438
1439         /**
1440          *
1441          * http://developer.twitter.com/doc/get/statuses/home_timeline
1442          *
1443          * TODO: Optional parameters
1444          * TODO: Add reply info
1445          */
1446         function api_statuses_home_timeline($type) {
1447
1448                 $a = get_app();
1449
1450                 if (api_user() === false) {
1451                         throw new ForbiddenException();
1452                 }
1453
1454                 unset($_REQUEST["user_id"]);
1455                 unset($_GET["user_id"]);
1456
1457                 unset($_REQUEST["screen_name"]);
1458                 unset($_GET["screen_name"]);
1459
1460                 $user_info = api_get_user($a);
1461                 // get last newtork messages
1462
1463                 // params
1464                 $count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20);
1465                 $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] - 1 : 0);
1466                 if ($page < 0) {
1467                         $page = 0;
1468                 }
1469                 $since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0);
1470                 $max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0);
1471                 //$since_id = 0;//$since_id = (x($_REQUEST, 'since_id')?$_REQUEST['since_id'] : 0);
1472                 $exclude_replies = (x($_REQUEST, 'exclude_replies') ? 1 : 0);
1473                 $conversation_id = (x($_REQUEST, 'conversation_id') ? $_REQUEST['conversation_id'] : 0);
1474
1475                 $start = $page * $count;
1476
1477                 $sql_extra = '';
1478                 if ($max_id > 0) {
1479                         $sql_extra .= ' AND `item`.`id` <= ' . intval($max_id);
1480                 }
1481                 if ($exclude_replies > 0) {
1482                         $sql_extra .= ' AND `item`.`parent` = `item`.`id`';
1483                 }
1484                 if ($conversation_id > 0) {
1485                         $sql_extra .= ' AND `item`.`parent` = ' . intval($conversation_id);
1486                 }
1487
1488                 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`,
1489                         `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
1490                         `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
1491                         `contact`.`id` AS `cid`
1492                         FROM `item`
1493                         STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
1494                                 AND (NOT `contact`.`blocked` OR `contact`.`pending`)
1495                         WHERE `item`.`uid` = %d AND `verb` = '%s'
1496                         AND `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted`
1497                         $sql_extra
1498                         AND `item`.`id`>%d
1499                         ORDER BY `item`.`id` DESC LIMIT %d ,%d ",
1500                         intval(api_user()),
1501                         dbesc(ACTIVITY_POST),
1502                         intval($since_id),
1503                         intval($start), intval($count)
1504                 );
1505
1506                 $ret = api_format_items($r, $user_info, false, $type);
1507
1508                 // Set all posts from the query above to seen
1509                 $idarray = array();
1510                 foreach ($r AS $item) {
1511                         $idarray[] = intval($item["id"]);
1512                 }
1513
1514                 $idlist = implode(",", $idarray);
1515
1516                 if ($idlist != "") {
1517                         $unseen = q("SELECT `id` FROM `item` WHERE `unseen` AND `id` IN (%s)", $idlist);
1518
1519                         if ($unseen) {
1520                                 $r = q("UPDATE `item` SET `unseen` = 0 WHERE `unseen` AND `id` IN (%s)", $idlist);
1521                         }
1522                 }
1523
1524                 $data = array('status' => $ret);
1525                 switch ($type) {
1526                         case "atom":
1527                         case "rss":
1528                                 $data = api_rss_extra($a, $data, $user_info);
1529                                 break;
1530                 }
1531
1532                 return api_format_data("statuses", $type, $data);
1533         }
1534
1535         /// @TODO move to top of file or somewhere better
1536         api_register_func('api/statuses/home_timeline','api_statuses_home_timeline', true);
1537         api_register_func('api/statuses/friends_timeline','api_statuses_home_timeline', true);
1538
1539         function api_statuses_public_timeline($type) {
1540
1541                 $a = get_app();
1542
1543                 if (api_user() === false) {
1544                         throw new ForbiddenException();
1545                 }
1546
1547                 $user_info = api_get_user($a);
1548                 // get last newtork messages
1549
1550                 // params
1551                 $count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20);
1552                 $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] -1 : 0);
1553                 if ($page < 0) {
1554                         $page = 0;
1555                 }
1556                 $since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0);
1557                 $max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0);
1558                 //$since_id = 0;//$since_id = (x($_REQUEST, 'since_id')?$_REQUEST['since_id'] : 0);
1559                 $exclude_replies = (x($_REQUEST, 'exclude_replies') ? 1 : 0);
1560                 $conversation_id = (x($_REQUEST, 'conversation_id') ? $_REQUEST['conversation_id'] : 0);
1561
1562                 $start = $page * $count;
1563
1564                 if ($max_id > 0) {
1565                         $sql_extra = 'AND `item`.`id` <= ' . intval($max_id);
1566                 }
1567                 if ($exclude_replies > 0) {
1568                         $sql_extra .= ' AND `item`.`parent` = `item`.`id`';
1569                 }
1570                 if ($conversation_id > 0) {
1571                         $sql_extra .= ' AND `item`.`parent` = ' . intval($conversation_id);
1572                 }
1573
1574                 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`,
1575                         `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
1576                         `contact`.`network`, `contact`.`thumb`, `contact`.`self`, `contact`.`writable`,
1577                         `contact`.`id` AS `cid`,
1578                         `user`.`nickname`, `user`.`hidewall`
1579                         FROM `item`
1580                         STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
1581                                 AND (NOT `contact`.`blocked` OR `contact`.`pending`)
1582                         STRAIGHT_JOIN `user` ON `user`.`uid` = `item`.`uid`
1583                                 AND NOT `user`.`hidewall`
1584                         WHERE `verb` = '%s' AND `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated`
1585                         AND `item`.`allow_cid` = ''  AND `item`.`allow_gid` = ''
1586                         AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = ''
1587                         AND NOT `item`.`private` AND `item`.`wall`
1588                         $sql_extra
1589                         AND `item`.`id`>%d
1590                         ORDER BY `item`.`id` DESC LIMIT %d, %d ",
1591                         dbesc(ACTIVITY_POST),
1592                         intval($since_id),
1593                         intval($start),
1594                         intval($count));
1595
1596                 $ret = api_format_items($r, $user_info, false, $type);
1597
1598                 $data = array('status' => $ret);
1599                 switch ($type) {
1600                         case "atom":
1601                         case "rss":
1602                                 $data = api_rss_extra($a, $data, $user_info);
1603                                 break;
1604                 }
1605
1606                 return api_format_data("statuses", $type, $data);
1607         }
1608
1609         /// @TODO move to top of file or somewhere better
1610         api_register_func('api/statuses/public_timeline','api_statuses_public_timeline', true);
1611
1612         /**
1613          * @TODO nothing to say?
1614          */
1615         function api_statuses_show($type) {
1616
1617                 $a = get_app();
1618
1619                 if (api_user() === false) {
1620                         throw new ForbiddenException();
1621                 }
1622
1623                 $user_info = api_get_user($a);
1624
1625                 // params
1626                 $id = intval($a->argv[3]);
1627
1628                 if ($id == 0) {
1629                         $id = intval($_REQUEST["id"]);
1630                 }
1631
1632                 // Hotot workaround
1633                 if ($id == 0) {
1634                         $id = intval($a->argv[4]);
1635                 }
1636
1637                 logger('API: api_statuses_show: ' . $id);
1638
1639                 $conversation = (x($_REQUEST, 'conversation') ? 1 : 0);
1640
1641                 $sql_extra = '';
1642                 if ($conversation) {
1643                         $sql_extra .= " AND `item`.`parent` = %d ORDER BY `id` ASC ";
1644                 } else {
1645                         $sql_extra .= " AND `item`.`id` = %d";
1646                 }
1647
1648                 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`,
1649                         `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
1650                         `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
1651                         `contact`.`id` AS `cid`
1652                         FROM `item`
1653                         INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
1654                                 AND (NOT `contact`.`blocked` OR `contact`.`pending`)
1655                         WHERE `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted`
1656                         AND `item`.`uid` = %d AND `item`.`verb` = '%s'
1657                         $sql_extra",
1658                         intval(api_user()),
1659                         dbesc(ACTIVITY_POST),
1660                         intval($id)
1661                 );
1662
1663                 /// @TODO How about copying this to above methods which don't check $r ?
1664                 if (!dbm::is_result($r)) {
1665                         throw new BadRequestException("There is no status with this id.");
1666                 }
1667
1668                 $ret = api_format_items($r,$user_info, false, $type);
1669
1670                 if ($conversation) {
1671                         $data = array('status' => $ret);
1672                         return api_format_data("statuses", $type, $data);
1673                 } else {
1674                         $data = array('status' => $ret[0]);
1675                         return api_format_data("status", $type, $data);
1676                 }
1677         }
1678
1679         /// @TODO move to top of file or somewhere better
1680         api_register_func('api/statuses/show','api_statuses_show', true);
1681
1682         /**
1683          * @TODO nothing to say?
1684          */
1685         function api_conversation_show($type) {
1686
1687                 $a = get_app();
1688
1689                 if (api_user() === false) {
1690                         throw new ForbiddenException();
1691                 }
1692
1693                 $user_info = api_get_user($a);
1694
1695                 // params
1696                 $id = intval($a->argv[3]);
1697                 $count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20);
1698                 $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] - 1 : 0);
1699                 if ($page < 0) {
1700                         $page = 0;
1701                 }
1702                 $since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0);
1703                 $max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0);
1704
1705                 $start = $page*$count;
1706
1707                 if ($id == 0) {
1708                         $id = intval($_REQUEST["id"]);
1709                 }
1710
1711                 // Hotot workaround
1712                 if ($id == 0) {
1713                         $id = intval($a->argv[4]);
1714                 }
1715
1716                 logger('API: api_conversation_show: '.$id);
1717
1718                 $r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($id));
1719                 if (dbm::is_result($r)) {
1720                         $id = $r[0]["parent"];
1721                 }
1722
1723                 $sql_extra = '';
1724
1725                 if ($max_id > 0) {
1726                         $sql_extra = ' AND `item`.`id` <= ' . intval($max_id);
1727                 }
1728
1729                 // Not sure why this query was so complicated. We should keep it here for a while,
1730                 // just to make sure that we really don't need it.
1731                 //      FROM `item` INNER JOIN (SELECT `uri`,`parent` FROM `item` WHERE `id` = %d) AS `temp1`
1732                 //      ON (`item`.`thr-parent` = `temp1`.`uri` AND `item`.`parent` = `temp1`.`parent`)
1733
1734                 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`,
1735                         `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
1736                         `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
1737                         `contact`.`id` AS `cid`
1738                         FROM `item`
1739                         STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
1740                                 AND (NOT `contact`.`blocked` OR `contact`.`pending`)
1741                         WHERE `item`.`parent` = %d AND `item`.`visible`
1742                         AND NOT `item`.`moderated` AND NOT `item`.`deleted`
1743                         AND `item`.`uid` = %d AND `item`.`verb` = '%s'
1744                         AND `item`.`id`>%d $sql_extra
1745                         ORDER BY `item`.`id` DESC LIMIT %d ,%d",
1746                         intval($id), intval(api_user()),
1747                         dbesc(ACTIVITY_POST),
1748                         intval($since_id),
1749                         intval($start), intval($count)
1750                 );
1751
1752                 if (!dbm::is_result($r)) {
1753                         throw new BadRequestException("There is no status with this id.");
1754                 }
1755
1756                 $ret = api_format_items($r, $user_info, false, $type);
1757
1758                 $data = array('status' => $ret);
1759                 return api_format_data("statuses", $type, $data);
1760         }
1761
1762         /// @TODO move to top of file or somewhere better
1763         api_register_func('api/conversation/show','api_conversation_show', true);
1764         api_register_func('api/statusnet/conversation','api_conversation_show', true);
1765
1766         /**
1767          * @TODO nothing to say?
1768          */
1769         function api_statuses_repeat($type) {
1770                 global $called_api;
1771
1772                 $a = get_app();
1773
1774                 if (api_user() === false) {
1775                         throw new ForbiddenException();
1776                 }
1777
1778                 $user_info = api_get_user($a);
1779
1780                 // params
1781                 $id = intval($a->argv[3]);
1782
1783                 if ($id == 0) {
1784                         $id = intval($_REQUEST["id"]);
1785                 }
1786
1787                 // Hotot workaround
1788                 if ($id == 0) {
1789                         $id = intval($a->argv[4]);
1790                 }
1791
1792                 logger('API: api_statuses_repeat: '.$id);
1793
1794                 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`, `contact`.`nick` as `reply_author`,
1795                         `contact`.`name`, `contact`.`photo` as `reply_photo`, `contact`.`url` as `reply_url`, `contact`.`rel`,
1796                         `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
1797                         `contact`.`id` AS `cid`
1798                         FROM `item`
1799                         INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
1800                                 AND (NOT `contact`.`blocked` OR `contact`.`pending`)
1801                         WHERE `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted`
1802                         AND NOT `item`.`private` AND `item`.`allow_cid` = '' AND `item`.`allow`.`gid` = ''
1803                         AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1804                         $sql_extra
1805                         AND `item`.`id`=%d",
1806                         intval($id)
1807                 );
1808
1809                 /// @TODO other style than above functions!
1810                 if (dbm::is_result($r) && $r[0]['body'] != "") {
1811                         if (strpos($r[0]['body'], "[/share]") !== false) {
1812                                 $pos = strpos($r[0]['body'], "[share");
1813                                 $post = substr($r[0]['body'], $pos);
1814                         } else {
1815                                 $post = share_header($r[0]['author-name'], $r[0]['author-link'], $r[0]['author-avatar'], $r[0]['guid'], $r[0]['created'], $r[0]['plink']);
1816
1817                                 $post .= $r[0]['body'];
1818                                 $post .= "[/share]";
1819                         }
1820                         $_REQUEST['body'] = $post;
1821                         $_REQUEST['profile_uid'] = api_user();
1822                         $_REQUEST['type'] = 'wall';
1823                         $_REQUEST['api_source'] = true;
1824
1825                         if (!x($_REQUEST, "source")) {
1826                                 $_REQUEST["source"] = api_source();
1827                         }
1828
1829                         item_post($a);
1830                 } else {
1831                         throw new ForbiddenException();
1832                 }
1833
1834                 // this should output the last post (the one we just posted).
1835                 $called_api = null;
1836                 return api_status_show($type);
1837         }
1838
1839         /// @TODO move to top of file or somewhere better
1840         api_register_func('api/statuses/retweet','api_statuses_repeat', true, API_METHOD_POST);
1841
1842         /**
1843          * @TODO nothing to say?
1844          */
1845         function api_statuses_destroy($type) {
1846
1847                 $a = get_app();
1848
1849                 if (api_user() === false) {
1850                         throw new ForbiddenException();
1851                 }
1852
1853                 $user_info = api_get_user($a);
1854
1855                 // params
1856                 $id = intval($a->argv[3]);
1857
1858                 if ($id == 0) {
1859                         $id = intval($_REQUEST["id"]);
1860                 }
1861
1862                 // Hotot workaround
1863                 if ($id == 0) {
1864                         $id = intval($a->argv[4]);
1865                 }
1866
1867                 logger('API: api_statuses_destroy: '.$id);
1868
1869                 $ret = api_statuses_show($type);
1870
1871                 drop_item($id, false);
1872
1873                 return $ret;
1874         }
1875
1876         /// @TODO move to top of file or somewhere better
1877         api_register_func('api/statuses/destroy','api_statuses_destroy', true, API_METHOD_DELETE);
1878
1879         /**
1880          * @TODO Nothing more than an URL to say?
1881          * http://developer.twitter.com/doc/get/statuses/mentions
1882          */
1883         function api_statuses_mentions($type) {
1884
1885                 $a = get_app();
1886
1887                 if (api_user() === false) {
1888                         throw new ForbiddenException();
1889                 }
1890
1891                 unset($_REQUEST["user_id"]);
1892                 unset($_GET["user_id"]);
1893
1894                 unset($_REQUEST["screen_name"]);
1895                 unset($_GET["screen_name"]);
1896
1897                 $user_info = api_get_user($a);
1898                 // get last newtork messages
1899
1900
1901                 // params
1902                 $count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20);
1903                 $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] -1 : 0);
1904                 if ($page < 0) {
1905                         $page = 0;
1906                 }
1907                 $since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0);
1908                 $max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0);
1909                 //$since_id = 0;//$since_id = (x($_REQUEST, 'since_id')?$_REQUEST['since_id'] : 0);
1910
1911                 $start = $page * $count;
1912
1913                 // Ugly code - should be changed
1914                 $myurl = App::get_baseurl() . '/profile/'. $a->user['nickname'];
1915                 $myurl = substr($myurl,strpos($myurl, '://') + 3);
1916                 //$myurl = str_replace(array('www.','.'),array('','\\.'),$myurl);
1917                 $myurl = str_replace('www.', '', $myurl);
1918                 $diasp_url = str_replace('/profile/', '/u/', $myurl);
1919
1920                 if ($max_id > 0) {
1921                         $sql_extra = ' AND `item`.`id` <= ' . intval($max_id);
1922                 }
1923
1924                 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`,
1925                         `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
1926                         `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
1927                         `contact`.`id` AS `cid`
1928                         FROM `item` FORCE INDEX (`uid_id`)
1929                         STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
1930                                 AND (NOT `contact`.`blocked` OR `contact`.`pending`)
1931                         WHERE `item`.`uid` = %d AND `verb` = '%s'
1932                         AND NOT (`item`.`author-link` IN ('https://%s', 'http://%s'))
1933                         AND `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted`
1934                         AND `item`.`parent` IN (SELECT `iid` FROM `thread` WHERE `uid` = %d AND `mention` AND !`ignored`)
1935                         $sql_extra
1936                         AND `item`.`id`>%d
1937                         ORDER BY `item`.`id` DESC LIMIT %d ,%d ",
1938                         intval(api_user()),
1939                         dbesc(ACTIVITY_POST),
1940                         dbesc(protect_sprintf($myurl)),
1941                         dbesc(protect_sprintf($myurl)),
1942                         intval(api_user()),
1943                         intval($since_id),
1944                         intval($start), intval($count)
1945                 );
1946
1947                 $ret = api_format_items($r, $user_info, false, $type);
1948
1949                 $data = array('status' => $ret);
1950                 switch ($type) {
1951                         case "atom":
1952                         case "rss":
1953                                 $data = api_rss_extra($a, $data, $user_info);
1954                                 break;
1955                 }
1956
1957                 return api_format_data("statuses", $type, $data);
1958         }
1959
1960         /// @TODO move to top of file or somewhere better
1961         api_register_func('api/statuses/mentions','api_statuses_mentions', true);
1962         api_register_func('api/statuses/replies','api_statuses_mentions', true);
1963
1964         function api_statuses_user_timeline($type) {
1965
1966                 $a = get_app();
1967
1968                 if (api_user() === false) {
1969                         throw new ForbiddenException();
1970                 }
1971
1972                 $user_info = api_get_user($a);
1973                 // get last network messages
1974
1975                 logger("api_statuses_user_timeline: api_user: ". api_user() .
1976                            "\nuser_info: ".print_r($user_info, true) .
1977                            "\n_REQUEST:  ".print_r($_REQUEST, true),
1978                            LOGGER_DEBUG);
1979
1980                 // params
1981                 $count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20);
1982                 $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] -1 : 0);
1983                 if ($page < 0) {
1984                         $page = 0;
1985                 }
1986                 $since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0);
1987                 //$since_id = 0;//$since_id = (x($_REQUEST, 'since_id')?$_REQUEST['since_id'] : 0);
1988                 $exclude_replies = (x($_REQUEST, 'exclude_replies') ? 1 : 0);
1989                 $conversation_id = (x($_REQUEST, 'conversation_id') ? $_REQUEST['conversation_id'] : 0);
1990
1991                 $start = $page * $count;
1992
1993                 $sql_extra = '';
1994                 if ($user_info['self'] == 1) {
1995                         $sql_extra .= " AND `item`.`wall` = 1 ";
1996                 }
1997
1998                 if ($exclude_replies > 0) {
1999                         $sql_extra .= ' AND `item`.`parent` = `item`.`id`';
2000                 }
2001                 if ($conversation_id > 0) {
2002                         $sql_extra .= ' AND `item`.`parent` = ' . intval($conversation_id);
2003                 }
2004
2005                 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`,
2006                         `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
2007                         `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
2008                         `contact`.`id` AS `cid`
2009                         FROM `item` FORCE INDEX (`uid_contactid_id`)
2010                         STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
2011                                 AND (NOT `contact`.`blocked` OR `contact`.`pending`)
2012                         WHERE `item`.`uid` = %d AND `verb` = '%s'
2013                         AND `item`.`contact-id` = %d
2014                         AND `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted`
2015                         $sql_extra
2016                         AND `item`.`id`>%d
2017                         ORDER BY `item`.`id` DESC LIMIT %d ,%d ",
2018                         intval(api_user()),
2019                         dbesc(ACTIVITY_POST),
2020                         intval($user_info['cid']),
2021                         intval($since_id),
2022                         intval($start), intval($count)
2023                 );
2024
2025                 $ret = api_format_items($r, $user_info, true, $type);
2026
2027                 $data = array('status' => $ret);
2028                 switch ($type) {
2029                         case "atom":
2030                         case "rss":
2031                                 $data = api_rss_extra($a, $data, $user_info);
2032                                 break;
2033                 }
2034
2035                 return api_format_data("statuses", $type, $data);
2036         }
2037
2038         /// @TODO move to top of file or somwhere better
2039         api_register_func('api/statuses/user_timeline','api_statuses_user_timeline', true);
2040
2041         /**
2042          * Star/unstar an item
2043          * param: id : id of the item
2044          *
2045          * api v1 : https://web.archive.org/web/20131019055350/https://dev.twitter.com/docs/api/1/post/favorites/create/%3Aid
2046          */
2047         function api_favorites_create_destroy($type) {
2048
2049                 $a = get_app();
2050
2051                 if (api_user() === false) {
2052                         throw new ForbiddenException();
2053                 }
2054
2055                 // for versioned api.
2056                 /// @TODO We need a better global soluton
2057                 $action_argv_id = 2;
2058                 if ($a->argv[1] == "1.1") {
2059                         $action_argv_id = 3;
2060                 }
2061
2062                 if ($a->argc <= $action_argv_id) {
2063                         throw new BadRequestException("Invalid request.");
2064                 }
2065                 $action = str_replace("." . $type, "", $a->argv[$action_argv_id]);
2066                 if ($a->argc == $action_argv_id + 2) {
2067                         $itemid = intval($a->argv[$action_argv_id + 1]);
2068                 } else {
2069                         ///  @TODO use x() to check if _REQUEST contains 'id'
2070                         $itemid = intval($_REQUEST['id']);
2071                 }
2072
2073                 $item = q("SELECT * FROM `item` WHERE `id`=%d AND `uid`=%d LIMIT 1",
2074                                 $itemid, api_user());
2075
2076                 if (!dbm::is_result($item) || count($item) == 0) {
2077                         throw new BadRequestException("Invalid item.");
2078                 }
2079
2080                 switch ($action) {
2081                         case "create":
2082                                 $item[0]['starred'] = 1;
2083                                 break;
2084                         case "destroy":
2085                                 $item[0]['starred'] = 0;
2086                                 break;
2087                         default:
2088                                 throw new BadRequestException("Invalid action ".$action);
2089                 }
2090
2091                 $r = q("UPDATE item SET starred=%d WHERE id=%d AND uid=%d",
2092                                 $item[0]['starred'], $itemid, api_user());
2093
2094                 q("UPDATE thread SET starred=%d WHERE iid=%d AND uid=%d",
2095                         $item[0]['starred'], $itemid, api_user());
2096
2097                 if ($r === false) {
2098                         throw new InternalServerErrorException("DB error");
2099                 }
2100
2101
2102                 $user_info = api_get_user($a);
2103                 $rets = api_format_items($item, $user_info, false, $type);
2104                 $ret = $rets[0];
2105
2106                 $data = array('status' => $ret);
2107                 switch ($type) {
2108                         case "atom":
2109                         case "rss":
2110                                 $data = api_rss_extra($a, $data, $user_info);
2111                 }
2112
2113                 return api_format_data("status", $type, $data);
2114         }
2115
2116         /// @TODO move to top of file or somwhere better
2117         api_register_func('api/favorites/create', 'api_favorites_create_destroy', true, API_METHOD_POST);
2118         api_register_func('api/favorites/destroy', 'api_favorites_create_destroy', true, API_METHOD_DELETE);
2119
2120         function api_favorites($type) {
2121                 global $called_api;
2122
2123                 $a = get_app();
2124
2125                 if (api_user() === false) {
2126                         throw new ForbiddenException();
2127                 }
2128
2129                 $called_api = array();
2130
2131                 $user_info = api_get_user($a);
2132
2133                 // in friendica starred item are private
2134                 // return favorites only for self
2135                 logger('api_favorites: self:' . $user_info['self']);
2136
2137                 if ($user_info['self'] == 0) {
2138                         $ret = array();
2139                 } else {
2140                         $sql_extra = "";
2141
2142                         // params
2143                         $since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0);
2144                         $max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0);
2145                         $count = (x($_GET, 'count') ? $_GET['count'] : 20);
2146                         $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] -1 : 0);
2147                         if ($page < 0) {
2148                                 $page = 0;
2149                         }
2150
2151                         $start = $page*$count;
2152
2153                         if ($max_id > 0) {
2154                                 $sql_extra .= ' AND `item`.`id` <= ' . intval($max_id);
2155                         }
2156
2157                         $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`,
2158                                 `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
2159                                 `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
2160                                 `contact`.`id` AS `cid`
2161                                 FROM `item`, `contact`
2162                                 WHERE `item`.`uid` = %d
2163                                 AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0
2164                                 AND `item`.`starred` = 1
2165                                 AND `contact`.`id` = `item`.`contact-id`
2166                                 AND (NOT `contact`.`blocked` OR `contact`.`pending`)
2167                                 $sql_extra
2168                                 AND `item`.`id`>%d
2169                                 ORDER BY `item`.`id` DESC LIMIT %d ,%d ",
2170                                 intval(api_user()),
2171                                 intval($since_id),
2172                                 intval($start), intval($count)
2173                         );
2174
2175                         $ret = api_format_items($r,$user_info, false, $type);
2176
2177                 }
2178
2179                 $data = array('status' => $ret);
2180                 switch ($type) {
2181                         case "atom":
2182                         case "rss":
2183                                 $data = api_rss_extra($a, $data, $user_info);
2184                 }
2185
2186                 return api_format_data("statuses", $type, $data);
2187         }
2188
2189         /// @TODO move to top of file or somwhere better
2190         api_register_func('api/favorites','api_favorites', true);
2191
2192         function api_format_messages($item, $recipient, $sender) {
2193                 // standard meta information
2194                 $ret=Array(
2195                                 'id'                    => $item['id'],
2196                                 'sender_id'             => $sender['id'] ,
2197                                 'text'                  => "",
2198                                 'recipient_id'          => $recipient['id'],
2199                                 'created_at'            => api_date($item['created']),
2200                                 'sender_screen_name'    => $sender['screen_name'],
2201                                 'recipient_screen_name' => $recipient['screen_name'],
2202                                 'sender'                => $sender,
2203                                 'recipient'             => $recipient,
2204                                 'title'                 => "",
2205                                 'friendica_seen'        => $item['seen'],
2206                                 'friendica_parent_uri'  => $item['parent-uri'],
2207                 );
2208
2209                 // "uid" and "self" are only needed for some internal stuff, so remove it from here
2210                 unset($ret["sender"]["uid"]);
2211                 unset($ret["sender"]["self"]);
2212                 unset($ret["recipient"]["uid"]);
2213                 unset($ret["recipient"]["self"]);
2214
2215                 //don't send title to regular StatusNET requests to avoid confusing these apps
2216                 if (x($_GET, 'getText')) {
2217                         $ret['title'] = $item['title'] ;
2218                         if ($_GET['getText'] == 'html') {
2219                                 $ret['text'] = bbcode($item['body'], false, false);
2220                         } elseif ($_GET['getText'] == 'plain') {
2221                                 //$ret['text'] = html2plain(bbcode($item['body'], false, false, true), 0);
2222                                 $ret['text'] = trim(html2plain(bbcode(api_clean_plain_items($item['body']), false, false, 2, true), 0));
2223                         }
2224                 } else {
2225                         $ret['text'] = $item['title'] . "\n" . html2plain(bbcode(api_clean_plain_items($item['body']), false, false, 2, true), 0);
2226                 }
2227                 if (x($_GET, 'getUserObjects') && $_GET['getUserObjects'] == 'false') {
2228                         unset($ret['sender']);
2229                         unset($ret['recipient']);
2230                 }
2231
2232                 return $ret;
2233         }
2234
2235         function api_convert_item($item) {
2236                 $body = $item['body'];
2237                 $attachments = api_get_attachments($body);
2238
2239                 // Workaround for ostatus messages where the title is identically to the body
2240                 $html = bbcode(api_clean_plain_items($body), false, false, 2, true);
2241                 $statusbody = trim(html2plain($html, 0));
2242
2243                 // handle data: images
2244                 $statusbody = api_format_items_embeded_images($item,$statusbody);
2245
2246                 $statustitle = trim($item['title']);
2247
2248                 if (($statustitle != '') and (strpos($statusbody, $statustitle) !== false)) {
2249                         $statustext = trim($statusbody);
2250                 } else {
2251                         $statustext = trim($statustitle."\n\n".$statusbody);
2252                 }
2253
2254                 if (($item["network"] == NETWORK_FEED) and (strlen($statustext)> 1000)) {
2255                         $statustext = substr($statustext, 0, 1000)."... \n".$item["plink"];
2256                 }
2257
2258                 $statushtml = trim(bbcode($body, false, false));
2259
2260                 $search = array("<br>", "<blockquote>", "</blockquote>",
2261                                 "<h1>", "</h1>", "<h2>", "</h2>",
2262                                 "<h3>", "</h3>", "<h4>", "</h4>",
2263                                 "<h5>", "</h5>", "<h6>", "</h6>");
2264                 $replace = array("<br>\n", "\n<blockquote>", "</blockquote>\n",
2265                                 "\n<h1>", "</h1>\n", "\n<h2>", "</h2>\n",
2266                                 "\n<h3>", "</h3>\n", "\n<h4>", "</h4>\n",
2267                                 "\n<h5>", "</h5>\n", "\n<h6>", "</h6>\n");
2268                 $statushtml = str_replace($search, $replace, $statushtml);
2269
2270                 if ($item['title'] != "") {
2271                         $statushtml = "<h4>" . bbcode($item['title']) . "</h4>\n" . $statushtml;
2272                 }
2273
2274                 $entities = api_get_entitities($statustext, $body);
2275
2276                 return array(
2277                         "text" => $statustext,
2278                         "html" => $statushtml,
2279                         "attachments" => $attachments,
2280                         "entities" => $entities
2281                 );
2282         }
2283
2284         function api_get_attachments(&$body) {
2285
2286                 $text = $body;
2287                 $text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $text);
2288
2289                 $URLSearchString = "^\[\]";
2290                 $ret = preg_match_all("/\[img\]([$URLSearchString]*)\[\/img\]/ism", $text, $images);
2291
2292                 if (!$ret) {
2293                         return false;
2294                 }
2295
2296                 $attachments = array();
2297
2298                 foreach ($images[1] AS $image) {
2299                         $imagedata = get_photo_info($image);
2300
2301                         if ($imagedata) {
2302                                 $attachments[] = array("url" => $image, "mimetype" => $imagedata["mime"], "size" => $imagedata["size"]);
2303                         }
2304                 }
2305
2306                 if (strstr($_SERVER['HTTP_USER_AGENT'], "AndStatus")) {
2307                         foreach ($images[0] AS $orig) {
2308                                 $body = str_replace($orig, "", $body);
2309                         }
2310                 }
2311
2312                 return $attachments;
2313         }
2314
2315         function api_get_entitities(&$text, $bbcode) {
2316                 /*
2317                 To-Do:
2318                 * Links at the first character of the post
2319                 */
2320
2321                 $a = get_app();
2322
2323                 $include_entities = strtolower(x($_REQUEST, 'include_entities') ? $_REQUEST['include_entities'] : "false");
2324
2325                 if ($include_entities != "true") {
2326
2327                         preg_match_all("/\[img](.*?)\[\/img\]/ism", $bbcode, $images);
2328
2329                         foreach ($images[1] AS $image) {
2330                                 $replace = proxy_url($image);
2331                                 $text = str_replace($image, $replace, $text);
2332                         }
2333                         return array();
2334                 }
2335
2336                 $bbcode = bb_CleanPictureLinks($bbcode);
2337
2338                 // Change pure links in text to bbcode uris
2339                 $bbcode = preg_replace("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url=$2]$2[/url]', $bbcode);
2340
2341                 $entities = array();
2342                 $entities["hashtags"] = array();
2343                 $entities["symbols"] = array();
2344                 $entities["urls"] = array();
2345                 $entities["user_mentions"] = array();
2346
2347                 $URLSearchString = "^\[\]";
2348
2349                 $bbcode = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",'#$2',$bbcode);
2350
2351                 $bbcode = preg_replace("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",'[url=$1]$2[/url]',$bbcode);
2352                 //$bbcode = preg_replace("/\[url\](.*?)\[\/url\]/ism",'[url=$1]$1[/url]',$bbcode);
2353                 $bbcode = preg_replace("/\[video\](.*?)\[\/video\]/ism",'[url=$1]$1[/url]',$bbcode);
2354
2355                 $bbcode = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism",
2356                                         '[url=https://www.youtube.com/watch?v=$1]https://www.youtube.com/watch?v=$1[/url]', $bbcode);
2357                 $bbcode = preg_replace("/\[youtube\](.*?)\[\/youtube\]/ism",'[url=$1]$1[/url]',$bbcode);
2358
2359                 $bbcode = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism",
2360                                         '[url=https://vimeo.com/$1]https://vimeo.com/$1[/url]', $bbcode);
2361                 $bbcode = preg_replace("/\[vimeo\](.*?)\[\/vimeo\]/ism",'[url=$1]$1[/url]',$bbcode);
2362
2363                 $bbcode = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $bbcode);
2364
2365                 //preg_match_all("/\[url\]([$URLSearchString]*)\[\/url\]/ism", $bbcode, $urls1);
2366                 preg_match_all("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $bbcode, $urls);
2367
2368                 $ordered_urls = array();
2369                 foreach ($urls[1] AS $id => $url) {
2370                         //$start = strpos($text, $url, $offset);
2371                         $start = iconv_strpos($text, $url, 0, "UTF-8");
2372                         if (!($start === false)) {
2373                                 $ordered_urls[$start] = array("url" => $url, "title" => $urls[2][$id]);
2374                         }
2375                 }
2376
2377                 ksort($ordered_urls);
2378
2379                 $offset = 0;
2380                 //foreach ($urls[1] AS $id=>$url) {
2381                 foreach ($ordered_urls AS $url) {
2382                         if ((substr($url["title"], 0, 7) != "http://") AND (substr($url["title"], 0, 8) != "https://") AND
2383                                 !strpos($url["title"], "http://") AND !strpos($url["title"], "https://"))
2384                                 $display_url = $url["title"];
2385                         else {
2386                                 $display_url = str_replace(array("http://www.", "https://www."), array("", ""), $url["url"]);
2387                                 $display_url = str_replace(array("http://", "https://"), array("", ""), $display_url);
2388
2389                                 if (strlen($display_url) > 26)
2390                                         $display_url = substr($display_url, 0, 25)."…";
2391                         }
2392
2393                         //$start = strpos($text, $url, $offset);
2394                         $start = iconv_strpos($text, $url["url"], $offset, "UTF-8");
2395                         if (!($start === false)) {
2396                                 $entities["urls"][] = array("url" => $url["url"],
2397                                                                 "expanded_url" => $url["url"],
2398                                                                 "display_url" => $display_url,
2399                                                                 "indices" => array($start, $start+strlen($url["url"])));
2400                                 $offset = $start + 1;
2401                         }
2402                 }
2403
2404                 preg_match_all("/\[img](.*?)\[\/img\]/ism", $bbcode, $images);
2405                 $ordered_images = array();
2406                 foreach ($images[1] AS $image) {
2407                         //$start = strpos($text, $url, $offset);
2408                         $start = iconv_strpos($text, $image, 0, "UTF-8");
2409                         if (!($start === false))
2410                                 $ordered_images[$start] = $image;
2411                 }
2412                 //$entities["media"] = array();
2413                 $offset = 0;
2414
2415                 foreach ($ordered_images AS $url) {
2416                         $display_url = str_replace(array("http://www.", "https://www."), array("", ""), $url);
2417                         $display_url = str_replace(array("http://", "https://"), array("", ""), $display_url);
2418
2419                         if (strlen($display_url) > 26)
2420                                 $display_url = substr($display_url, 0, 25)."…";
2421
2422                         $start = iconv_strpos($text, $url, $offset, "UTF-8");
2423                         if (!($start === false)) {
2424                                 $image = get_photo_info($url);
2425                                 if ($image) {
2426                                         // If image cache is activated, then use the following sizes:
2427                                         // thumb  (150), small (340), medium (600) and large (1024)
2428                                         if (!get_config("system", "proxy_disabled")) {
2429                                                 $media_url = proxy_url($url);
2430
2431                                                 $sizes = array();
2432                                                 $scale = scale_image($image[0], $image[1], 150);
2433                                                 $sizes["thumb"] = array("w" => $scale["width"], "h" => $scale["height"], "resize" => "fit");
2434
2435                                                 if (($image[0] > 150) OR ($image[1] > 150)) {
2436                                                         $scale = scale_image($image[0], $image[1], 340);
2437                                                         $sizes["small"] = array("w" => $scale["width"], "h" => $scale["height"], "resize" => "fit");
2438                                                 }
2439
2440                                                 $scale = scale_image($image[0], $image[1], 600);
2441                                                 $sizes["medium"] = array("w" => $scale["width"], "h" => $scale["height"], "resize" => "fit");
2442
2443                                                 if (($image[0] > 600) OR ($image[1] > 600)) {
2444                                                         $scale = scale_image($image[0], $image[1], 1024);
2445                                                         $sizes["large"] = array("w" => $scale["width"], "h" => $scale["height"], "resize" => "fit");
2446                                                 }
2447                                         } else {
2448                                                 $media_url = $url;
2449                                                 $sizes["medium"] = array("w" => $image[0], "h" => $image[1], "resize" => "fit");
2450                                         }
2451
2452                                         $entities["media"][] = array(
2453                                                                 "id" => $start+1,
2454                                                                 "id_str" => (string)$start+1,
2455                                                                 "indices" => array($start, $start+strlen($url)),
2456                                                                 "media_url" => normalise_link($media_url),
2457                                                                 "media_url_https" => $media_url,
2458                                                                 "url" => $url,
2459                                                                 "display_url" => $display_url,
2460                                                                 "expanded_url" => $url,
2461                                                                 "type" => "photo",
2462                                                                 "sizes" => $sizes);
2463                                 }
2464                                 $offset = $start + 1;
2465                         }
2466                 }
2467
2468                 return $entities;
2469         }
2470         function api_format_items_embeded_images(&$item, $text) {
2471                 $text = preg_replace_callback(
2472                                 "|data:image/([^;]+)[^=]+=*|m",
2473                                 function($match) use ($item) {
2474                                         return App::get_baseurl()."/display/".$item['guid'];
2475                                 },
2476                                 $text);
2477                 return $text;
2478         }
2479
2480
2481         /**
2482          * @brief return <a href='url'>name</a> as array
2483          *
2484          * @param string $txt
2485          * @return array
2486          *                      name => 'name'
2487          *                      'url => 'url'
2488          */
2489         function api_contactlink_to_array($txt) {
2490                 $match = array();
2491                 $r = preg_match_all('|<a href="([^"]*)">([^<]*)</a>|', $txt, $match);
2492                 if ($r && count($match)==3) {
2493                         $res = array(
2494                                 'name' => $match[2],
2495                                 'url' => $match[1]
2496                         );
2497                 } else {
2498                         $res = array(
2499                                 'name' => $text,
2500                                 'url' => ""
2501                         );
2502                 }
2503                 return $res;
2504         }
2505
2506
2507         /**
2508          * @brief return likes, dislikes and attend status for item
2509          *
2510          * @param array $item
2511          * @return array
2512          *                      likes => int count
2513          *                      dislikes => int count
2514          */
2515         function api_format_items_activities(&$item, $type = "json") {
2516
2517                 $a = get_app();
2518
2519                 $activities = array(
2520                         'like' => array(),
2521                         'dislike' => array(),
2522                         'attendyes' => array(),
2523                         'attendno' => array(),
2524                         'attendmaybe' => array(),
2525                 );
2526
2527                 $items = q('SELECT * FROM item
2528                                         WHERE uid=%d AND `thr-parent`="%s" AND visible AND NOT deleted',
2529                                         intval($item['uid']),
2530                                         dbesc($item['uri']));
2531
2532                 foreach ($items as $i) {
2533                         // not used as result should be structured like other user data
2534                         //builtin_activity_puller($i, $activities);
2535
2536                         // get user data and add it to the array of the activity
2537                         $user = api_get_user($a, $i['author-link']);
2538                         switch ($i['verb']) {
2539                                 case ACTIVITY_LIKE:
2540                                         $activities['like'][] = $user;
2541                                         break;
2542                                 case ACTIVITY_DISLIKE:
2543                                         $activities['dislike'][] = $user;
2544                                         break;
2545                                 case ACTIVITY_ATTEND:
2546                                         $activities['attendyes'][] = $user;
2547                                         break;
2548                                 case ACTIVITY_ATTENDNO:
2549                                         $activities['attendno'][] = $user;
2550                                         break;
2551                                 case ACTIVITY_ATTENDMAYBE:
2552                                         $activities['attendmaybe'][] = $user;
2553                                         break;
2554                                 default:
2555                                         break;
2556                         }
2557                 }
2558
2559                 if ($type == "xml") {
2560                         $xml_activities = array();
2561                         foreach ($activities as $k => $v) {
2562                                 // change xml element from "like" to "friendica:like"
2563                                 $xml_activities["friendica:".$k] = $v;
2564                                 // add user data into xml output
2565                                 $k_user = 0;
2566                                 foreach ($v as $user)
2567                                         $xml_activities["friendica:".$k][$k_user++.":user"] = $user;
2568                         }
2569                         $activities = $xml_activities;
2570                 }
2571
2572                 return $activities;
2573
2574         }
2575
2576
2577         /**
2578          * @brief return data from profiles
2579          *
2580          * @param array $profile array containing data from db table 'profile'
2581          * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
2582          * @return array
2583          */
2584         function api_format_items_profiles(&$profile = null, $type = "json") {
2585                 if ($profile != null) {
2586                         $profile = array('profile_id' => $profile['id'],
2587                                                         'profile_name' => $profile['profile-name'],
2588                                                         'is_default' => $profile['is-default'] ? true : false,
2589                                                         'hide_friends'=> $profile['hide-friends'] ? true : false,
2590                                                         'profile_photo' => $profile['photo'],
2591                                                         'profile_thumb' => $profile['thumb'],
2592                                                         'publish' => $profile['publish'] ? true : false,
2593                                                         'net_publish' => $profile['net-publish'] ? true : false,
2594                                                         'description' => $profile['pdesc'],
2595                                                         'date_of_birth' => $profile['dob'],
2596                                                         'address' => $profile['address'],
2597                                                         'city' => $profile['locality'],
2598                                                         'region' => $profile['region'],
2599                                                         'postal_code' => $profile['postal-code'],
2600                                                         'country' => $profile['country-name'],
2601                                                         'hometown' => $profile['hometown'],
2602                                                         'gender' => $profile['gender'],
2603                                                         'marital' => $profile['marital'],
2604                                                         'marital_with' => $profile['with'],
2605                                                         'marital_since' => $profile['howlong'],
2606                                                         'sexual' => $profile['sexual'],
2607                                                         'politic' => $profile['politic'],
2608                                                         'religion' => $profile['religion'],
2609                                                         'public_keywords' => $profile['pub_keywords'],
2610                                                         'private_keywords' => $profile['prv_keywords'],
2611                                                         'likes' => bbcode(api_clean_plain_items($profile['likes']), false, false, 2, false),
2612                                                         'dislikes' => bbcode(api_clean_plain_items($profile['dislikes']), false, false, 2, false),
2613                                                         'about' => bbcode(api_clean_plain_items($profile['about']), false, false, 2, false),
2614                                                         'music' => bbcode(api_clean_plain_items($profile['music']), false, false, 2, false),
2615                                                         'book' => bbcode(api_clean_plain_items($profile['book']), false, false, 2, false),
2616                                                         'tv' => bbcode(api_clean_plain_items($profile['tv']), false, false, 2, false),
2617                                                         'film' => bbcode(api_clean_plain_items($profile['film']), false, false, 2, false),
2618                                                         'interest' => bbcode(api_clean_plain_items($profile['interest']), false, false, 2, false),
2619                                                         'romance' => bbcode(api_clean_plain_items($profile['romance']), false, false, 2, false),
2620                                                         'work' => bbcode(api_clean_plain_items($profile['work']), false, false, 2, false),
2621                                                         'education' => bbcode(api_clean_plain_items($profile['education']), false, false, 2, false),
2622                                                         'social_networks' => bbcode(api_clean_plain_items($profile['contact']), false, false, 2, false),
2623                                                         'homepage' => $profile['homepage'],
2624                                                         'users' => null);
2625                         return $profile;
2626                 }
2627         }
2628
2629         /**
2630          * @brief format items to be returned by api
2631          *
2632          * @param array $r array of items
2633          * @param array $user_info
2634          * @param bool $filter_user filter items by $user_info
2635          */
2636         function api_format_items($r,$user_info, $filter_user = false, $type = "json") {
2637
2638                 $a = get_app();
2639
2640                 $ret = array();
2641
2642                 foreach ($r as $item) {
2643
2644                         localize_item($item);
2645                         list($status_user, $owner_user) = api_item_get_user($a,$item);
2646
2647                         // Look if the posts are matching if they should be filtered by user id
2648                         if ($filter_user AND ($status_user["id"] != $user_info["id"])) {
2649                                 continue;
2650                         }
2651
2652                         $in_reply_to = api_in_reply_to($item);
2653
2654                         $converted = api_convert_item($item);
2655
2656                         if ($type == "xml") {
2657                                 $geo = "georss:point";
2658                         } else {
2659                                 $geo = "geo";
2660                         }
2661
2662                         $status = array(
2663                                 'text'          => $converted["text"],
2664                                 'truncated' => False,
2665                                 'created_at'=> api_date($item['created']),
2666                                 'in_reply_to_status_id' => $in_reply_to['status_id'],
2667                                 'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
2668                                 'source'    => (($item['app']) ? $item['app'] : 'web'),
2669                                 'id'            => intval($item['id']),
2670                                 'id_str'        => (string) intval($item['id']),
2671                                 'in_reply_to_user_id' => $in_reply_to['user_id'],
2672                                 'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
2673                                 'in_reply_to_screen_name' => $in_reply_to['screen_name'],
2674                                 $geo => NULL,
2675                                 'favorited' => $item['starred'] ? true : false,
2676                                 'user' =>  $status_user ,
2677                                 'friendica_owner' => $owner_user,
2678                                 //'entities' => NULL,
2679                                 'statusnet_html'                => $converted["html"],
2680                                 'statusnet_conversation_id'     => $item['parent'],
2681                                 'friendica_activities' => api_format_items_activities($item, $type),
2682                         );
2683
2684                         if (count($converted["attachments"]) > 0) {
2685                                 $status["attachments"] = $converted["attachments"];
2686                         }
2687
2688                         if (count($converted["entities"]) > 0) {
2689                                 $status["entities"] = $converted["entities"];
2690                         }
2691
2692                         if (($item['item_network'] != "") AND ($status["source"] == 'web')) {
2693                                 $status["source"] = network_to_name($item['item_network'], $user_info['url']);
2694                         } elseif (($item['item_network'] != "") AND (network_to_name($item['item_network'], $user_info['url']) != $status["source"])) {
2695                                 $status["source"] = trim($status["source"].' ('.network_to_name($item['item_network'], $user_info['url']).')');
2696                         }
2697
2698
2699                         // Retweets are only valid for top postings
2700                         // It doesn't work reliable with the link if its a feed
2701                         //$IsRetweet = ($item['owner-link'] != $item['author-link']);
2702                         //if ($IsRetweet)
2703                         //      $IsRetweet = (($item['owner-name'] != $item['author-name']) OR ($item['owner-avatar'] != $item['author-avatar']));
2704
2705
2706                         if ($item["id"] == $item["parent"]) {
2707                                 $retweeted_item = api_share_as_retweet($item);
2708                                 if ($retweeted_item !== false) {
2709                                         $retweeted_status = $status;
2710                                         try {
2711                                                 $retweeted_status["user"] = api_get_user($a,$retweeted_item["author-link"]);
2712                                         } catch( BadRequestException $e ) {
2713                                                 // user not found. should be found?
2714                                                 /// @todo check if the user should be always found
2715                                                 $retweeted_status["user"] = array();
2716                                         }
2717
2718                                         $rt_converted = api_convert_item($retweeted_item);
2719
2720                                         $retweeted_status['text'] = $rt_converted["text"];
2721                                         $retweeted_status['statusnet_html'] = $rt_converted["html"];
2722                                         $retweeted_status['friendica_activities'] = api_format_items_activities($retweeted_item, $type);
2723                                         $retweeted_status['created_at'] =  api_date($retweeted_item['created']);
2724                                         $status['retweeted_status'] = $retweeted_status;
2725                                 }
2726                         }
2727
2728                         // "uid" and "self" are only needed for some internal stuff, so remove it from here
2729                         unset($status["user"]["uid"]);
2730                         unset($status["user"]["self"]);
2731
2732                         if ($item["coord"] != "") {
2733                                 $coords = explode(' ',$item["coord"]);
2734                                 if (count($coords) == 2) {
2735                                         if ($type == "json")
2736                                                 $status["geo"] = array('type' => 'Point',
2737                                                                 'coordinates' => array((float) $coords[0],
2738                                                                                         (float) $coords[1]));
2739                                         else // Not sure if this is the official format - if someone founds a documentation we can check
2740                                                 $status["georss:point"] = $item["coord"];
2741                                 }
2742                         }
2743                         $ret[] = $status;
2744                 };
2745                 return $ret;
2746         }
2747
2748         function api_account_rate_limit_status($type) {
2749
2750                 if ($type == "xml") {
2751                         $hash = array(
2752                                         'remaining-hits' => '150',
2753                                         '@attributes' => array("type" => "integer"),
2754                                         'hourly-limit' => '150',
2755                                         '@attributes2' => array("type" => "integer"),
2756                                         'reset-time' => datetime_convert('UTC', 'UTC','now + 1 hour',ATOM_TIME),
2757                                         '@attributes3' => array("type" => "datetime"),
2758                                         'reset_time_in_seconds' => strtotime('now + 1 hour'),
2759                                         '@attributes4' => array("type" => "integer"),
2760                                 );
2761                 } else {
2762                         $hash = array(
2763                                         'reset_time_in_seconds' => strtotime('now + 1 hour'),
2764                                         'remaining_hits' => '150',
2765                                         'hourly_limit' => '150',
2766                                         'reset_time' => api_date(datetime_convert('UTC', 'UTC','now + 1 hour',ATOM_TIME)),
2767                                 );
2768                 }
2769
2770                 return api_format_data('hash', $type, array('hash' => $hash));
2771         }
2772
2773         /// @TODO move to top of file or somwhere better
2774         api_register_func('api/account/rate_limit_status','api_account_rate_limit_status',true);
2775
2776         function api_help_test($type) {
2777                 if ($type == 'xml') {
2778                         $ok = "true";
2779                 } else {
2780                         $ok = "ok";
2781                 }
2782
2783                 return api_format_data('ok', $type, array("ok" => $ok));
2784         }
2785
2786         /// @TODO move to top of file or somwhere better
2787         api_register_func('api/help/test','api_help_test', false);
2788
2789         function api_lists($type) {
2790                 $ret = array();
2791                 /// @TODO $ret is not filled here?
2792                 return api_format_data('lists', $type, array("lists_list" => $ret));
2793         }
2794
2795         /// @TODO move to top of file or somwhere better
2796         api_register_func('api/lists','api_lists',true);
2797
2798         function api_lists_list($type) {
2799                 $ret = array();
2800                 /// @TODO $ret is not filled here?
2801                 return api_format_data('lists', $type, array("lists_list" => $ret));
2802         }
2803
2804         /// @TODO move to top of file or somwhere better
2805         api_register_func('api/lists/list','api_lists_list',true);
2806
2807         /**
2808          * https://dev.twitter.com/docs/api/1/get/statuses/friends
2809          * This function is deprecated by Twitter
2810          * returns: json, xml
2811          */
2812         function api_statuses_f($type, $qtype) {
2813
2814                 $a = get_app();
2815
2816                 if (api_user() === false) {
2817                         throw new ForbiddenException();
2818                 }
2819
2820                 $user_info = api_get_user($a);
2821
2822                 if (x($_GET, 'cursor') && $_GET['cursor']=='undefined') {
2823                         /* this is to stop Hotot to load friends multiple times
2824                         *  I'm not sure if I'm missing return something or
2825                         *  is a bug in hotot. Workaround, meantime
2826                         */
2827
2828                         /*$ret=Array();
2829                         return array('$users' => $ret);*/
2830                         return false;
2831                 }
2832
2833                 if ($qtype == 'friends') {
2834                         $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_SHARING), intval(CONTACT_IS_FRIEND));
2835                 }
2836                 if ($qtype == 'followers') {
2837                         $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_FOLLOWER), intval(CONTACT_IS_FRIEND));
2838                 }
2839
2840                 // friends and followers only for self
2841                 if ($user_info['self'] == 0) {
2842                         $sql_extra = " AND false ";
2843                 }
2844
2845                 $r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND NOT `self` AND (NOT `blocked` OR `pending`) $sql_extra",
2846                         intval(api_user())
2847                 );
2848
2849                 $ret = array();
2850                 foreach ($r as $cid) {
2851                         $user = api_get_user($a, $cid['nurl']);
2852                         // "uid" and "self" are only needed for some internal stuff, so remove it from here
2853                         unset($user["uid"]);
2854                         unset($user["self"]);
2855
2856                         if ($user) {
2857                                 $ret[] = $user;
2858                         }
2859                 }
2860
2861                 return array('user' => $ret);
2862
2863         }
2864
2865         function api_statuses_friends($type) {
2866                 $data =  api_statuses_f($type, "friends");
2867                 if ($data === false) {
2868                         return false;
2869                 }
2870                 return api_format_data("users", $type, $data);
2871         }
2872
2873         function api_statuses_followers($type) {
2874                 $data = api_statuses_f($type, "followers");
2875                 if ($data === false) {
2876                         return false;
2877                 }
2878                 return api_format_data("users", $type, $data);
2879         }
2880
2881         /// @TODO move to top of file or somewhere better
2882         api_register_func('api/statuses/friends','api_statuses_friends',true);
2883         api_register_func('api/statuses/followers','api_statuses_followers',true);
2884
2885         function api_statusnet_config($type) {
2886
2887                 $a = get_app();
2888
2889                 $name = $a->config['sitename'];
2890                 $server = $a->get_hostname();
2891                 $logo = App::get_baseurl() . '/images/friendica-64.png';
2892                 $email = $a->config['admin_email'];
2893                 $closed = (($a->config['register_policy'] == REGISTER_CLOSED) ? 'true' : 'false');
2894                 $private = ((Config::get('system', 'block_public')) ? 'true' : 'false');
2895                 $textlimit = (string) (($a->config['max_import_size']) ? $a->config['max_import_size'] : 200000);
2896                 if ($a->config['api_import_size']) {
2897                         $texlimit = string($a->config['api_import_size']);
2898                 }
2899                 $ssl = ((Config::get('system', 'have_ssl')) ? 'true' : 'false');
2900                 $sslserver = (($ssl === 'true') ? str_replace('http:','https:',App::get_baseurl()) : '');
2901
2902                 $config = array(
2903                         'site' => array('name' => $name,'server' => $server, 'theme' => 'default', 'path' => '',
2904                                 'logo' => $logo, 'fancy' => true, 'language' => 'en', 'email' => $email, 'broughtby' => '',
2905                                 'broughtbyurl' => '', 'timezone' => 'UTC', 'closed' => $closed, 'inviteonly' => false,
2906                                 'private' => $private, 'textlimit' => $textlimit, 'sslserver' => $sslserver, 'ssl' => $ssl,
2907                                 'shorturllength' => '30',
2908                                 'friendica' => array(
2909                                                 'FRIENDICA_PLATFORM' => FRIENDICA_PLATFORM,
2910                                                 'FRIENDICA_VERSION' => FRIENDICA_VERSION,
2911                                                 'DFRN_PROTOCOL_VERSION' => DFRN_PROTOCOL_VERSION,
2912                                                 'DB_UPDATE_VERSION' => DB_UPDATE_VERSION
2913                                                 )
2914                         ),
2915                 );
2916
2917                 return api_format_data('config', $type, array('config' => $config));
2918
2919         }
2920
2921         /// @TODO move to top of file or somewhere better
2922         api_register_func('api/gnusocial/config','api_statusnet_config', false);
2923         api_register_func('api/statusnet/config','api_statusnet_config', false);
2924
2925         function api_statusnet_version($type) {
2926                 // liar
2927                 $fake_statusnet_version = "0.9.7";
2928
2929                 return api_format_data('version', $type, array('version' => $fake_statusnet_version));
2930         }
2931
2932         /// @TODO move to top of file or somewhere better
2933         api_register_func('api/gnusocial/version','api_statusnet_version', false);
2934         api_register_func('api/statusnet/version','api_statusnet_version', false);
2935
2936         /**
2937          * @todo use api_format_data() to return data
2938          */
2939         function api_ff_ids($type,$qtype) {
2940
2941                 $a = get_app();
2942
2943                 if (! api_user()) {
2944                         throw new ForbiddenException();
2945                 }
2946
2947                 $user_info = api_get_user($a);
2948
2949                 if ($qtype == 'friends') {
2950                         $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_SHARING), intval(CONTACT_IS_FRIEND));
2951                 }
2952                 if ($qtype == 'followers') {
2953                         $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_FOLLOWER), intval(CONTACT_IS_FRIEND));
2954                 }
2955
2956                 if (!$user_info["self"]) {
2957                         $sql_extra = " AND false ";
2958                 }
2959
2960                 $stringify_ids = (x($_REQUEST, 'stringify_ids') ? $_REQUEST['stringify_ids'] : false);
2961
2962                 $r = q("SELECT `pcontact`.`id` FROM `contact`
2963                                 INNER JOIN `contact` AS `pcontact` ON `contact`.`nurl` = `pcontact`.`nurl` AND `pcontact`.`uid` = 0
2964                                 WHERE `contact`.`uid` = %s AND NOT `contact`.`self`",
2965                         intval(api_user())
2966                 );
2967
2968                 if (!dbm::is_result($r)) {
2969                         return;
2970                 }
2971
2972                 $ids = array();
2973                 foreach ($r as $rr)
2974                         if ($stringify_ids)
2975                                 $ids[] = $rr['id'];
2976                         } else {
2977                                 $ids[] = intval($rr['id']);
2978                         }
2979
2980                 return api_format_data("ids", $type, array('id' => $ids));
2981         }
2982
2983         function api_friends_ids($type) {
2984                 return api_ff_ids($type,'friends');
2985         }
2986
2987         function api_followers_ids($type) {
2988                 return api_ff_ids($type,'followers');
2989         }
2990
2991         /// @TODO move to top of file or somewhere better
2992         api_register_func('api/friends/ids','api_friends_ids',true);
2993         api_register_func('api/followers/ids','api_followers_ids',true);
2994
2995         function api_direct_messages_new($type) {
2996
2997                 $a = get_app();
2998
2999                 if (api_user() === false) throw new ForbiddenException();
3000
3001                 if (!x($_POST, "text") OR (!x($_POST,"screen_name") AND !x($_POST,"user_id"))) return;
3002
3003                 $sender = api_get_user($a);
3004
3005                 if ($_POST['screen_name']) {
3006                         $r = q("SELECT `id`, `nurl`, `network` FROM `contact` WHERE `uid`=%d AND `nick`='%s'",
3007                                         intval(api_user()),
3008                                         dbesc($_POST['screen_name']));
3009
3010                         // Selecting the id by priority, friendica first
3011                         api_best_nickname($r);
3012
3013                         $recipient = api_get_user($a, $r[0]['nurl']);
3014                 } else
3015                         $recipient = api_get_user($a, $_POST['user_id']);
3016
3017                 $replyto = '';
3018                 $sub     = '';
3019                 if (x($_REQUEST, 'replyto')) {
3020                         $r = q('SELECT `parent-uri`, `title` FROM `mail` WHERE `uid`=%d AND `id`=%d',
3021                                         intval(api_user()),
3022                                         intval($_REQUEST['replyto']));
3023                         $replyto = $r[0]['parent-uri'];
3024                         $sub     = $r[0]['title'];
3025                 } else {
3026                         if (x($_REQUEST, 'title')) {
3027                                 $sub = $_REQUEST['title'];
3028                         } else {
3029                                 $sub = ((strlen($_POST['text'])>10) ? substr($_POST['text'],0,10)."...":$_POST['text']);
3030                         }
3031                 }
3032
3033                 $id = send_message($recipient['cid'], $_POST['text'], $sub, $replyto);
3034
3035                 if ($id > -1) {
3036                         $r = q("SELECT * FROM `mail` WHERE id=%d", intval($id));
3037                         $ret = api_format_messages($r[0], $recipient, $sender);
3038                 } else {
3039                         $ret = array("error"=>$id);
3040                 }
3041
3042                 $data = array('direct_message'=>$ret);
3043
3044                 switch ($type) {
3045                         case "atom":
3046                         case "rss":
3047                                 $data = api_rss_extra($a, $data, $user_info);
3048                 }
3049
3050                 return api_format_data("direct-messages", $type, $data);
3051
3052         }
3053
3054         /// @TODO move to top of file or somewhere better
3055         api_register_func('api/direct_messages/new','api_direct_messages_new',true, API_METHOD_POST);
3056
3057         /**
3058          * @brief delete a direct_message from mail table through api
3059          *
3060          * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
3061          * @return string
3062          */
3063         function api_direct_messages_destroy($type) {
3064                 $a = get_app();
3065
3066                 if (api_user() === false) {
3067                         throw new ForbiddenException();
3068                 }
3069
3070                 // params
3071                 $user_info = api_get_user($a);
3072                 //required
3073                 $id = (x($_REQUEST, 'id') ? $_REQUEST['id'] : 0);
3074                 // optional
3075                 $parenturi = (x($_REQUEST, 'friendica_parenturi') ? $_REQUEST['friendica_parenturi'] : "");
3076                 $verbose = (x($_GET, 'friendica_verbose') ? strtolower($_GET['friendica_verbose']) : "false");
3077                 /// @todo optional parameter 'include_entities' from Twitter API not yet implemented
3078
3079                 $uid = $user_info['uid'];
3080                 // error if no id or parenturi specified (for clients posting parent-uri as well)
3081                 if ($verbose == "true" && ($id == 0 || $parenturi == "")) {
3082                         $answer = array('result' => 'error', 'message' => 'message id or parenturi not specified');
3083                         return api_format_data("direct_messages_delete", $type, array('$result' => $answer));
3084                 }
3085
3086                 // BadRequestException if no id specified (for clients using Twitter API)
3087                 if ($id == 0) {
3088                         throw new BadRequestException('Message id not specified');
3089                 }
3090
3091                 // add parent-uri to sql command if specified by calling app
3092                 $sql_extra = ($parenturi != "" ? " AND `parent-uri` = '" . dbesc($parenturi) . "'" : "");
3093
3094                 // get data of the specified message id
3095                 $r = q("SELECT `id` FROM `mail` WHERE `uid` = %d AND `id` = %d" . $sql_extra,
3096                         intval($uid),
3097                         intval($id));
3098
3099                 // error message if specified id is not in database
3100                 if (!dbm::is_result($r)) {
3101                         if ($verbose == "true") {
3102                                 $answer = array('result' => 'error', 'message' => 'message id not in database');
3103                                 return api_format_data("direct_messages_delete", $type, array('$result' => $answer));
3104                         }
3105                         /// @todo BadRequestException ok for Twitter API clients?
3106                         throw new BadRequestException('message id not in database');
3107                 }
3108
3109                 // delete message
3110                 $result = q("DELETE FROM `mail` WHERE `uid` = %d AND `id` = %d" . $sql_extra,
3111                         intval($uid),
3112                         intval($id));
3113
3114                 if ($verbose == "true") {
3115                         if ($result) {
3116                                 // return success
3117                                 $answer = array('result' => 'ok', 'message' => 'message deleted');
3118                                 return api_format_data("direct_message_delete", $type, array('$result' => $answer));
3119                         } else {
3120                                 $answer = array('result' => 'error', 'message' => 'unknown error');
3121                                 return api_format_data("direct_messages_delete", $type, array('$result' => $answer));
3122                         }
3123                 }
3124                 /// @todo return JSON data like Twitter API not yet implemented
3125
3126         }
3127
3128         /// @TODO move to top of file or somewhere better
3129         api_register_func('api/direct_messages/destroy', 'api_direct_messages_destroy', true, API_METHOD_DELETE);
3130
3131         function api_direct_messages_box($type, $box, $verbose) {
3132
3133                 $a = get_app();
3134
3135                 if (api_user() === false) {
3136                         throw new ForbiddenException();
3137                 }
3138
3139                 // params
3140                 $count = (x($_GET, 'count') ? $_GET['count'] : 20);
3141                 $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] -1 : 0);
3142                 if ($page < 0) {
3143                         $page = 0;
3144                 }
3145
3146                 $since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0);
3147                 $max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0);
3148
3149                 $user_id = (x($_REQUEST, 'user_id') ? $_REQUEST['user_id'] : "");
3150                 $screen_name = (x($_REQUEST, 'screen_name') ? $_REQUEST['screen_name'] : "");
3151
3152                 //  caller user info
3153                 unset($_REQUEST["user_id"]);
3154                 unset($_GET["user_id"]);
3155
3156                 unset($_REQUEST["screen_name"]);
3157                 unset($_GET["screen_name"]);
3158
3159                 $user_info = api_get_user($a);
3160                 $profile_url = $user_info["url"];
3161
3162                 // pagination
3163                 $start = $page * $count;
3164
3165                 // filters
3166                 if ($box=="sentbox") {
3167                         $sql_extra = "`mail`.`from-url`='" . dbesc( $profile_url ) . "'";
3168                 } elseif ($box == "conversation") {
3169                         $sql_extra = "`mail`.`parent-uri`='" . dbesc( $_GET["uri"] )  . "'";
3170                 } elseif ($box == "all") {
3171                         $sql_extra = "true";
3172                 } elseif ($box == "inbox") {
3173                         $sql_extra = "`mail`.`from-url`!='" . dbesc( $profile_url ) . "'";
3174                 }
3175
3176                 if ($max_id > 0) {
3177                         $sql_extra .= ' AND `mail`.`id` <= ' . intval($max_id);
3178                 }
3179
3180                 if ($user_id != "") {
3181                         $sql_extra .= ' AND `mail`.`contact-id` = ' . intval($user_id);
3182                 } elseif ($screen_name !="") {
3183                         $sql_extra .= " AND `contact`.`nick` = '" . dbesc($screen_name). "'";
3184                 }
3185
3186                 $r = q("SELECT `mail`.*, `contact`.`nurl` AS `contact-url` FROM `mail`,`contact` WHERE `mail`.`contact-id` = `contact`.`id` AND `mail`.`uid`=%d AND $sql_extra AND `mail`.`id` > %d ORDER BY `mail`.`id` DESC LIMIT %d,%d",
3187                                 intval(api_user()),
3188                                 intval($since_id),
3189                                 intval($start), intval($count)
3190                 );
3191                 if ($verbose == "true" && !dbm::is_result($r)) {
3192                         $answer = array('result' => 'error', 'message' => 'no mails available');
3193                         return api_format_data("direct_messages_all", $type, array('$result' => $answer));
3194                 }
3195
3196                 $ret = array();
3197                 foreach ($r as $item) {
3198                         if ($box == "inbox" || $item['from-url'] != $profile_url) {
3199                                 $recipient = $user_info;
3200                                 $sender = api_get_user($a,normalise_link($item['contact-url']));
3201                         } elseif ($box == "sentbox" || $item['from-url'] == $profile_url) {
3202                                 $recipient = api_get_user($a,normalise_link($item['contact-url']));
3203                                 $sender = $user_info;
3204                         }
3205
3206                         $ret[] = api_format_messages($item, $recipient, $sender);
3207                 }
3208
3209
3210                 $data = array('direct_message' => $ret);
3211                 switch ($type) {
3212                         case "atom":
3213                         case "rss":
3214                                 $data = api_rss_extra($a, $data, $user_info);
3215                 }
3216
3217                 return api_format_data("direct-messages", $type, $data);
3218
3219         }
3220
3221         function api_direct_messages_sentbox($type) {
3222                 $verbose = (x($_GET, 'friendica_verbose') ? strtolower($_GET['friendica_verbose']) : "false");
3223                 return api_direct_messages_box($type, "sentbox", $verbose);
3224         }
3225
3226         function api_direct_messages_inbox($type) {
3227                 $verbose = (x($_GET, 'friendica_verbose') ? strtolower($_GET['friendica_verbose']) : "false");
3228                 return api_direct_messages_box($type, "inbox", $verbose);
3229         }
3230
3231         function api_direct_messages_all($type) {
3232                 $verbose = (x($_GET, 'friendica_verbose') ? strtolower($_GET['friendica_verbose']) : "false");
3233                 return api_direct_messages_box($type, "all", $verbose);
3234         }
3235
3236         function api_direct_messages_conversation($type) {
3237                 $verbose = (x($_GET, 'friendica_verbose') ? strtolower($_GET['friendica_verbose']) : "false");
3238                 return api_direct_messages_box($type, "conversation", $verbose);
3239         }
3240
3241         /// @TODO move to top of file or somewhere better
3242         api_register_func('api/direct_messages/conversation','api_direct_messages_conversation',true);
3243         api_register_func('api/direct_messages/all','api_direct_messages_all',true);
3244         api_register_func('api/direct_messages/sent','api_direct_messages_sentbox',true);
3245         api_register_func('api/direct_messages','api_direct_messages_inbox',true);
3246
3247         function api_oauth_request_token($type) {
3248                 try {
3249                         $oauth = new FKOAuth1();
3250                         $r = $oauth->fetch_request_token(OAuthRequest::from_request());
3251                 } catch (Exception $e) {
3252                         echo "error=" . OAuthUtil::urlencode_rfc3986($e->getMessage());
3253                         killme();
3254                 }
3255                 echo $r;
3256                 killme();
3257         }
3258
3259         function api_oauth_access_token($type) {
3260                 try {
3261                         $oauth = new FKOAuth1();
3262                         $r = $oauth->fetch_access_token(OAuthRequest::from_request());
3263                 } catch (Exception $e) {
3264                         echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); killme();
3265                 }
3266                 echo $r;
3267                 killme();
3268         }
3269
3270         /// @TODO move to top of file or somewhere better
3271         api_register_func('api/oauth/request_token', 'api_oauth_request_token', false);
3272         api_register_func('api/oauth/access_token', 'api_oauth_access_token', false);
3273
3274         function api_fr_photos_list($type) {
3275                 if (api_user() === false) {
3276                         throw new ForbiddenException();
3277                 }
3278
3279                 $r = q("SELECT `resource-id`, MAX(`scale`) AS `scale`, `album`, `filename`, `type`
3280                                 FROM `photo`
3281                                 WHERE `uid` = %d AND `album` != 'Contact Photos' GROUP BY `resource-id`",
3282                         intval(local_user())
3283                 );
3284                 $typetoext = array(
3285                         'image/jpeg' => 'jpg',
3286                         'image/png' => 'png',
3287                         'image/gif' => 'gif'
3288                 );
3289                 $data = array('photo' => array());
3290                 if (dbm::is_result($r)) {
3291                         foreach ($r as $rr) {
3292                                 $photo = array();
3293                                 $photo['id'] = $rr['resource-id'];
3294                                 $photo['album'] = $rr['album'];
3295                                 $photo['filename'] = $rr['filename'];
3296                                 $photo['type'] = $rr['type'];
3297                                 $thumb = App::get_baseurl() . "/photo/" . $rr['resource-id'] . "-" . $rr['scale'] . "." . $typetoext[$rr['type']];
3298
3299                                 if ($type == "xml") {
3300                                         $data['photo'][] = array("@attributes" => $photo, "1" => $thumb);
3301                                 } else {
3302                                         $photo['thumb'] = $thumb;
3303                                         $data['photo'][] = $photo;
3304                                 }
3305                         }
3306                 }
3307                 return api_format_data("photos", $type, $data);
3308         }
3309
3310         function api_fr_photo_detail($type) {
3311                 if (api_user() === false) {
3312                         throw new ForbiddenException();
3313                 } elseif (!x($_REQUEST, 'photo_id')) {
3314                         throw new BadRequestException("No photo id.");
3315                 }
3316
3317                 $scale = (x($_REQUEST, 'scale') ? intval($_REQUEST['scale']) : false);
3318                 $scale_sql = ($scale === false ? "" : sprintf("and scale=%d", intval($scale)));
3319                 $data_sql = ($scale === false ? "" : "data, ");
3320
3321                 $r = q("SELECT %s `resource-id`, `created`, `edited`, `title`, `desc`, `album`, `filename`,
3322                                                 `type`, `height`, `width`, `datasize`, `profile`, MIN(`scale`) AS `minscale`, MAX(`scale`) AS `maxscale`
3323                                 FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' %s GROUP BY `resource-id`",
3324                         $data_sql,
3325                         intval(local_user()),
3326                         dbesc($_REQUEST['photo_id']),
3327                         $scale_sql
3328                 );
3329
3330                 $typetoext = array(
3331                         'image/jpeg' => 'jpg',
3332                         'image/png' => 'png',
3333                         'image/gif' => 'gif'
3334                 );
3335
3336                 if (dbm::is_result($r)) {
3337                         $data = array('photo' => $r[0]);
3338                         $data['photo']['id'] = $data['photo']['resource-id'];
3339                         if ($scale !== false) {
3340                                 $data['photo']['data'] = base64_encode($data['photo']['data']);
3341                         } else {
3342                                 unset($data['photo']['datasize']); //needed only with scale param
3343                         }
3344                         if ($type == "xml") {
3345                                 $data['photo']['links'] = array();
3346                                 for ($k = intval($data['photo']['minscale']); $k <= intval($data['photo']['maxscale']); $k++) {
3347                                         $data['photo']['links'][$k . ":link"]["@attributes"] = array("type" => $data['photo']['type'],
3348                                                 "scale" => $k,
3349                                                 "href" => App::get_baseurl() . "/photo/" . $data['photo']['resource-id'] . "-" . $k . "." . $typetoext[$data['photo']['type']]);
3350                                 }
3351                         } else {
3352                                 $data['photo']['link'] = array();
3353                                 for ($k = intval($data['photo']['minscale']); $k <= intval($data['photo']['maxscale']); $k++) {
3354                                         $data['photo']['link'][$k] = App::get_baseurl() . "/photo/" . $data['photo']['resource-id'] . "-" . $k . "." . $typetoext[$data['photo']['type']];
3355                                 }
3356                         }
3357                         unset($data['photo']['resource-id']);
3358                         unset($data['photo']['minscale']);
3359                         unset($data['photo']['maxscale']);
3360
3361                 } else {
3362                         throw new NotFoundException();
3363                 }
3364
3365                 return api_format_data("photo_detail", $type, $data);
3366         }
3367
3368         api_register_func('api/friendica/photos/list', 'api_fr_photos_list', true);
3369         api_register_func('api/friendica/photo', 'api_fr_photo_detail', true);
3370
3371
3372
3373         /**
3374          * similar as /mod/redir.php
3375          * redirect to 'url' after dfrn auth
3376          *
3377          * why this when there is mod/redir.php already?
3378          * This use api_user() and api_login()
3379          *
3380          * params
3381          *              c_url: url of remote contact to auth to
3382          *              url: string, url to redirect after auth
3383          */
3384         function api_friendica_remoteauth() {
3385                 $url = ((x($_GET, 'url')) ? $_GET['url'] : '');
3386                 $c_url = ((x($_GET, 'c_url')) ? $_GET['c_url'] : '');
3387
3388                 if ($url === '' || $c_url === '') {
3389                         throw new BadRequestException("Wrong parameters.");
3390                 }
3391
3392                 $c_url = normalise_link($c_url);
3393
3394                 // traditional DFRN
3395
3396                 $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `nurl` = '%s' LIMIT 1",
3397                         dbesc($c_url),
3398                         intval(api_user())
3399                 );
3400
3401                 if ((! dbm::is_result($r)) || ($r[0]['network'] !== NETWORK_DFRN)) {
3402                         throw new BadRequestException("Unknown contact");
3403                 }
3404
3405                 $cid = $r[0]['id'];
3406
3407                 $dfrn_id = $orig_id = (($r[0]['issued-id']) ? $r[0]['issued-id'] : $r[0]['dfrn-id']);
3408
3409                 if ($r[0]['duplex'] && $r[0]['issued-id']) {
3410                         $orig_id = $r[0]['issued-id'];
3411                         $dfrn_id = '1:' . $orig_id;
3412                 }
3413                 if ($r[0]['duplex'] && $r[0]['dfrn-id']) {
3414                         $orig_id = $r[0]['dfrn-id'];
3415                         $dfrn_id = '0:' . $orig_id;
3416                 }
3417
3418                 $sec = random_string();
3419
3420                 q("INSERT INTO `profile_check` ( `uid`, `cid`, `dfrn_id`, `sec`, `expire`)
3421                         VALUES( %d, %s, '%s', '%s', %d )",
3422                         intval(api_user()),
3423                         intval($cid),
3424                         dbesc($dfrn_id),
3425                         dbesc($sec),
3426                         intval(time() + 45)
3427                 );
3428
3429                 logger($r[0]['name'] . ' ' . $sec, LOGGER_DEBUG);
3430                 $dest = (($url) ? '&destination_url=' . $url : '');
3431                 goaway ($r[0]['poll'] . '?dfrn_id=' . $dfrn_id
3432                                 . '&dfrn_version=' . DFRN_PROTOCOL_VERSION
3433                                 . '&type=profile&sec=' . $sec . $dest . $quiet );
3434         }
3435         api_register_func('api/friendica/remoteauth', 'api_friendica_remoteauth', true);
3436
3437         /**
3438          * @brief Return the item shared, if the item contains only the [share] tag
3439          *
3440          * @param array $item Sharer item
3441          * @return array Shared item or false if not a reshare
3442          */
3443         function api_share_as_retweet(&$item) {
3444                 $body = trim($item["body"]);
3445
3446                 if (Diaspora::is_reshare($body, false)===false) {
3447                         return false;
3448                 }
3449
3450                 /// @TODO "$1" should maybe mean '$1' ?
3451                 $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$1", $body);
3452                 /*
3453                  * Skip if there is no shared message in there
3454                  * we already checked this in diaspora::is_reshare()
3455                  * but better one more than one less...
3456                  */
3457                 if ($body == $attributes) {
3458                         return false;
3459                 }
3460
3461
3462                 // build the fake reshared item
3463                 $reshared_item = $item;
3464
3465                 $author = "";
3466                 preg_match("/author='(.*?)'/ism", $attributes, $matches);
3467                 if ($matches[1] != "") {
3468                         $author = html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8');
3469                 }
3470
3471                 preg_match('/author="(.*?)"/ism', $attributes, $matches);
3472                 if ($matches[1] != "") {
3473                         $author = $matches[1];
3474                 }
3475
3476                 $profile = "";
3477                 preg_match("/profile='(.*?)'/ism", $attributes, $matches);
3478                 if ($matches[1] != "") {
3479                         $profile = $matches[1];
3480                 }
3481
3482                 preg_match('/profile="(.*?)"/ism', $attributes, $matches);
3483                 if ($matches[1] != "") {
3484                         $profile = $matches[1];
3485                 }
3486
3487                 $avatar = "";
3488                 preg_match("/avatar='(.*?)'/ism", $attributes, $matches);
3489                 if ($matches[1] != "") {
3490                         $avatar = $matches[1];
3491                 }
3492
3493                 preg_match('/avatar="(.*?)"/ism', $attributes, $matches);
3494                 if ($matches[1] != "") {
3495                         $avatar = $matches[1];
3496                 }
3497
3498                 $link = "";
3499                 preg_match("/link='(.*?)'/ism", $attributes, $matches);
3500                 if ($matches[1] != "") {
3501                         $link = $matches[1];
3502                 }
3503
3504                 preg_match('/link="(.*?)"/ism', $attributes, $matches);
3505                 if ($matches[1] != "") {
3506                         $link = $matches[1];
3507                 }
3508
3509                 $posted = "";
3510                 preg_match("/posted='(.*?)'/ism", $attributes, $matches);
3511                 if ($matches[1] != "")
3512                         $posted = $matches[1];
3513
3514                 preg_match('/posted="(.*?)"/ism', $attributes, $matches);
3515                 if ($matches[1] != "") {
3516                         $posted = $matches[1];
3517                 }
3518
3519                 $shared_body = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$2",$body);
3520
3521                 if (($shared_body == "") || ($profile == "") || ($author == "") || ($avatar == "") || ($posted == "")) {
3522                         return false;
3523                 }
3524
3525                 $reshared_item["body"] = $shared_body;
3526                 $reshared_item["author-name"] = $author;
3527                 $reshared_item["author-link"] = $profile;
3528                 $reshared_item["author-avatar"] = $avatar;
3529                 $reshared_item["plink"] = $link;
3530                 $reshared_item["created"] = $posted;
3531                 $reshared_item["edited"] = $posted;
3532
3533                 return $reshared_item;
3534
3535         }
3536
3537         function api_get_nick($profile) {
3538                 /* To-Do:
3539                  - remove trailing junk from profile url
3540                  - pump.io check has to check the website
3541                 */
3542
3543                 $nick = "";
3544
3545                 $r = q("SELECT `nick` FROM `contact` WHERE `uid` = 0 AND `nurl` = '%s'",
3546                         dbesc(normalise_link($profile)));
3547
3548                 if (dbm::is_result($r)) {
3549                         $nick = $r[0]["nick"];
3550                 }
3551
3552                 if (!$nick == "") {
3553                         $r = q("SELECT `nick` FROM `contact` WHERE `uid` = 0 AND `nurl` = '%s'",
3554                                 dbesc(normalise_link($profile)));
3555
3556                         if (dbm::is_result($r)) {
3557                                 $nick = $r[0]["nick"];
3558                         }
3559                 }
3560
3561                 if (!$nick == "") {
3562                         $friendica = preg_replace("=https?://(.*)/profile/(.*)=ism", "$2", $profile);
3563                         if ($friendica != $profile) {
3564                                 $nick = $friendica;
3565                         }
3566                 }
3567
3568                 if (!$nick == "") {
3569                         $diaspora = preg_replace("=https?://(.*)/u/(.*)=ism", "$2", $profile);
3570                         if ($diaspora != $profile) {
3571                                 $nick = $diaspora;
3572                         }
3573                 }
3574
3575                 if (!$nick == "") {
3576                         $twitter = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $profile);
3577                         if ($twitter != $profile) {
3578                                 $nick = $twitter;
3579                         }
3580                 }
3581
3582
3583                 if (!$nick == "") {
3584                         $StatusnetHost = preg_replace("=https?://(.*)/user/(.*)=ism", "$1", $profile);
3585                         if ($StatusnetHost != $profile) {
3586                                 $StatusnetUser = preg_replace("=https?://(.*)/user/(.*)=ism", "$2", $profile);
3587                                 if ($StatusnetUser != $profile) {
3588                                         $UserData = fetch_url("http://".$StatusnetHost."/api/users/show.json?user_id=".$StatusnetUser);
3589                                         $user = json_decode($UserData);
3590                                         if ($user) {
3591                                                 $nick = $user->screen_name;
3592                                         }
3593                                 }
3594                         }
3595                 }
3596
3597                 // To-Do: look at the page if its really a pumpio site
3598                 //if (!$nick == "") {
3599                 //      $pumpio = preg_replace("=https?://(.*)/(.*)/=ism", "$2", $profile."/");
3600                 //      if ($pumpio != $profile)
3601                 //              $nick = $pumpio;
3602                         //      <div class="media" id="profile-block" data-profile-id="acct:kabniel@microca.st">
3603
3604                 //}
3605
3606                 if ($nick != "") {
3607                         return $nick;
3608                 }
3609
3610                 return false;
3611         }
3612
3613         function api_in_reply_to($item) {
3614                 $in_reply_to = array();
3615
3616                 $in_reply_to['status_id'] = NULL;
3617                 $in_reply_to['user_id'] = NULL;
3618                 $in_reply_to['status_id_str'] = NULL;
3619                 $in_reply_to['user_id_str'] = NULL;
3620                 $in_reply_to['screen_name'] = NULL;
3621
3622                 if (($item['thr-parent'] != $item['uri']) AND (intval($item['parent']) != intval($item['id']))) {
3623                         $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' LIMIT 1",
3624                                 intval($item['uid']),
3625                                 dbesc($item['thr-parent']));
3626
3627                         if (dbm::is_result($r)) {
3628                                 $in_reply_to['status_id'] = intval($r[0]['id']);
3629                         } else {
3630                                 $in_reply_to['status_id'] = intval($item['parent']);
3631                         }
3632
3633                         $in_reply_to['status_id_str'] = (string) intval($in_reply_to['status_id']);
3634
3635                         $r = q("SELECT `contact`.`nick`, `contact`.`name`, `contact`.`id`, `contact`.`url` FROM item
3636                                 STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`author-id`
3637                                 WHERE `item`.`id` = %d LIMIT 1",
3638                                 intval($in_reply_to['status_id'])
3639                         );
3640
3641                         if (dbm::is_result($r)) {
3642                                 if ($r[0]['nick'] == "") {
3643                                         $r[0]['nick'] = api_get_nick($r[0]["url"]);
3644                                 }
3645
3646                                 $in_reply_to['screen_name'] = (($r[0]['nick']) ? $r[0]['nick'] : $r[0]['name']);
3647                                 $in_reply_to['user_id'] = intval($r[0]['id']);
3648                                 $in_reply_to['user_id_str'] = (string) intval($r[0]['id']);
3649                         }
3650
3651                         // There seems to be situation, where both fields are identical:
3652                         // https://github.com/friendica/friendica/issues/1010
3653                         // This is a bugfix for that.
3654                         if (intval($in_reply_to['status_id']) == intval($item['id'])) {
3655                                 logger('this message should never appear: id: '.$item['id'].' similar to reply-to: '.$in_reply_to['status_id'], LOGGER_DEBUG);
3656                                 $in_reply_to['status_id'] = NULL;
3657                                 $in_reply_to['user_id'] = NULL;
3658                                 $in_reply_to['status_id_str'] = NULL;
3659                                 $in_reply_to['user_id_str'] = NULL;
3660                                 $in_reply_to['screen_name'] = NULL;
3661                         }
3662                 }
3663
3664                 return $in_reply_to;
3665         }
3666
3667         function api_clean_plain_items($Text) {
3668                 $include_entities = strtolower(x($_REQUEST, 'include_entities') ? $_REQUEST['include_entities'] : "false");
3669
3670                 $Text = bb_CleanPictureLinks($Text);
3671                 $URLSearchString = "^\[\]";
3672
3673                 $Text = preg_replace("/([!#@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",'$1$3',$Text);
3674
3675                 if ($include_entities == "true") {
3676                         $Text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",'[url=$1]$1[/url]',$Text);
3677                 }
3678
3679                 // Simplify "attachment" element
3680                 $Text = api_clean_attachments($Text);
3681
3682                 return($Text);
3683         }
3684
3685         /**
3686          * @brief Removes most sharing information for API text export
3687          *
3688          * @param string $body The original body
3689          *
3690          * @return string Cleaned body
3691          */
3692         function api_clean_attachments($body) {
3693                 $data = get_attachment_data($body);
3694
3695                 if (!$data)
3696                         return $body;
3697
3698                 $body = "";
3699
3700                 if (isset($data["text"]))
3701                         $body = $data["text"];
3702
3703                 if (($body == "") AND (isset($data["title"])))
3704                         $body = $data["title"];
3705
3706                 if (isset($data["url"]))
3707                         $body .= "\n".$data["url"];
3708
3709                 $body .= $data["after"];
3710
3711                 return $body;
3712         }
3713
3714         function api_best_nickname(&$contacts) {
3715                 $best_contact = array();
3716
3717                 if (count($contact) == 0)
3718                         return;
3719
3720                 foreach ($contacts AS $contact)
3721                         if ($contact["network"] == "") {
3722                                 $contact["network"] = "dfrn";
3723                                 $best_contact = array($contact);
3724                         }
3725
3726                 if (sizeof($best_contact) == 0)
3727                         foreach ($contacts AS $contact)
3728                                 if ($contact["network"] == "dfrn")
3729                                         $best_contact = array($contact);
3730
3731                 if (sizeof($best_contact) == 0)
3732                         foreach ($contacts AS $contact)
3733                                 if ($contact["network"] == "dspr")
3734                                         $best_contact = array($contact);
3735
3736                 if (sizeof($best_contact) == 0)
3737                         foreach ($contacts AS $contact)
3738                                 if ($contact["network"] == "stat")
3739                                         $best_contact = array($contact);
3740
3741                 if (sizeof($best_contact) == 0)
3742                         foreach ($contacts AS $contact)
3743                                 if ($contact["network"] == "pump")
3744                                         $best_contact = array($contact);
3745
3746                 if (sizeof($best_contact) == 0)
3747                         foreach ($contacts AS $contact)
3748                                 if ($contact["network"] == "twit")
3749                                         $best_contact = array($contact);
3750
3751                 if (sizeof($best_contact) == 1)
3752                         $contacts = $best_contact;
3753                 else
3754                         $contacts = array($contacts[0]);
3755         }
3756
3757         // return all or a specified group of the user with the containing contacts
3758         function api_friendica_group_show($type) {
3759
3760                 $a = get_app();
3761
3762                 if (api_user() === false) throw new ForbiddenException();
3763
3764                 // params
3765                 $user_info = api_get_user($a);
3766                 $gid = (x($_REQUEST, 'gid') ? $_REQUEST['gid'] : 0);
3767                 $uid = $user_info['uid'];
3768
3769                 // get data of the specified group id or all groups if not specified
3770                 if ($gid != 0) {
3771                         $r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d AND `id` = %d",
3772                                 intval($uid),
3773                                 intval($gid));
3774                         // error message if specified gid is not in database
3775                         if (!dbm::is_result($r))
3776                                 throw new BadRequestException("gid not available");
3777                 }
3778                 else
3779                         $r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d",
3780                                 intval($uid));
3781
3782                 // loop through all groups and retrieve all members for adding data in the user array
3783                 foreach ($r as $rr) {
3784                         $members = group_get_members($rr['id']);
3785                         $users = array();
3786
3787                         if ($type == "xml") {
3788                                 $user_element = "users";
3789                                 $k = 0;
3790                                 foreach ($members as $member) {
3791                                         $user = api_get_user($a, $member['nurl']);
3792                                         $users[$k++.":user"] = $user;
3793                                 }
3794                         } else {
3795                                 $user_element = "user";
3796                                 foreach ($members as $member) {
3797                                         $user = api_get_user($a, $member['nurl']);
3798                                         $users[] = $user;
3799                                 }
3800                         }
3801                         $grps[] = array('name' => $rr['name'], 'gid' => $rr['id'], $user_element => $users);
3802                 }
3803                 return api_format_data("groups", $type, array('group' => $grps));
3804         }
3805         api_register_func('api/friendica/group_show', 'api_friendica_group_show', true);
3806
3807
3808         // delete the specified group of the user
3809         function api_friendica_group_delete($type) {
3810
3811                 $a = get_app();
3812
3813                 if (api_user() === false) throw new ForbiddenException();
3814
3815                 // params
3816                 $user_info = api_get_user($a);
3817                 $gid = (x($_REQUEST, 'gid') ? $_REQUEST['gid'] : 0);
3818                 $name = (x($_REQUEST, 'name') ? $_REQUEST['name'] : "");
3819                 $uid = $user_info['uid'];
3820
3821                 // error if no gid specified
3822                 if ($gid == 0 || $name == "")
3823                         throw new BadRequestException('gid or name not specified');
3824
3825                 // get data of the specified group id
3826                 $r = q("SELECT * FROM `group` WHERE `uid` = %d AND `id` = %d",
3827                         intval($uid),
3828                         intval($gid));
3829                 // error message if specified gid is not in database
3830                 if (!dbm::is_result($r))
3831                         throw new BadRequestException('gid not available');
3832
3833                 // get data of the specified group id and group name
3834                 $rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `id` = %d AND `name` = '%s'",
3835                         intval($uid),
3836                         intval($gid),
3837                         dbesc($name));
3838                 // error message if specified gid is not in database
3839                 if (!dbm::is_result($rname))
3840                         throw new BadRequestException('wrong group name');
3841
3842                 // delete group
3843                 $ret = group_rmv($uid, $name);
3844                 if ($ret) {
3845                         // return success
3846                         $success = array('success' => $ret, 'gid' => $gid, 'name' => $name, 'status' => 'deleted', 'wrong users' => array());
3847                         return api_format_data("group_delete", $type, array('result' => $success));
3848                 }
3849                 else
3850                         throw new BadRequestException('other API error');
3851         }
3852         api_register_func('api/friendica/group_delete', 'api_friendica_group_delete', true, API_METHOD_DELETE);
3853
3854
3855         // create the specified group with the posted array of contacts
3856         function api_friendica_group_create($type) {
3857
3858                 $a = get_app();
3859
3860                 if (api_user() === false) throw new ForbiddenException();
3861
3862                 // params
3863                 $user_info = api_get_user($a);
3864                 $name = (x($_REQUEST, 'name') ? $_REQUEST['name'] : "");
3865                 $uid = $user_info['uid'];
3866                 $json = json_decode($_POST['json'], true);
3867                 $users = $json['user'];
3868
3869                 // error if no name specified
3870                 if ($name == "")
3871                         throw new BadRequestException('group name not specified');
3872
3873                 // get data of the specified group name
3874                 $rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `name` = '%s' AND `deleted` = 0",
3875                         intval($uid),
3876                         dbesc($name));
3877                 // error message if specified group name already exists
3878                 if (dbm::is_result($rname))
3879                         throw new BadRequestException('group name already exists');
3880
3881                 // check if specified group name is a deleted group
3882                 $rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `name` = '%s' AND `deleted` = 1",
3883                         intval($uid),
3884                         dbesc($name));
3885                 // error message if specified group name already exists
3886                 if (dbm::is_result($rname))
3887                         $reactivate_group = true;
3888
3889                 // create group
3890                 $ret = group_add($uid, $name);
3891                 if ($ret)
3892                         $gid = group_byname($uid, $name);
3893                 else
3894                         throw new BadRequestException('other API error');
3895
3896                 // add members
3897                 $erroraddinguser = false;
3898                 $errorusers = array();
3899                 foreach ($users as $user) {
3900                         $cid = $user['cid'];
3901                         // check if user really exists as contact
3902                         $contact = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d",
3903                                 intval($cid),
3904                                 intval($uid));
3905                         if (count($contact))
3906                                 $result = group_add_member($uid, $name, $cid, $gid);
3907                         else {
3908                                 $erroraddinguser = true;
3909                                 $errorusers[] = $cid;
3910                         }
3911                 }
3912
3913                 // return success message incl. missing users in array
3914                 $status = ($erroraddinguser ? "missing user" : ($reactivate_group ? "reactivated" : "ok"));
3915                 $success = array('success' => true, 'gid' => $gid, 'name' => $name, 'status' => $status, 'wrong users' => $errorusers);
3916                 return api_format_data("group_create", $type, array('result' => $success));
3917         }
3918         api_register_func('api/friendica/group_create', 'api_friendica_group_create', true, API_METHOD_POST);
3919
3920
3921         // update the specified group with the posted array of contacts
3922         function api_friendica_group_update($type) {
3923
3924                 $a = get_app();
3925
3926                 if (api_user() === false) throw new ForbiddenException();
3927
3928                 // params
3929                 $user_info = api_get_user($a);
3930                 $uid = $user_info['uid'];
3931                 $gid = (x($_REQUEST, 'gid') ? $_REQUEST['gid'] : 0);
3932                 $name = (x($_REQUEST, 'name') ? $_REQUEST['name'] : "");
3933                 $json = json_decode($_POST['json'], true);
3934                 $users = $json['user'];
3935
3936                 // error if no name specified
3937                 if ($name == "")
3938                         throw new BadRequestException('group name not specified');
3939
3940                 // error if no gid specified
3941                 if ($gid == "")
3942                         throw new BadRequestException('gid not specified');
3943
3944                 // remove members
3945                 $members = group_get_members($gid);
3946                 foreach ($members as $member) {
3947                         $cid = $member['id'];
3948                         foreach ($users as $user) {
3949                                 $found = ($user['cid'] == $cid ? true : false);
3950                         }
3951                         if (!$found) {
3952                                 $ret = group_rmv_member($uid, $name, $cid);
3953                         }
3954                 }
3955
3956                 // add members
3957                 $erroraddinguser = false;
3958                 $errorusers = array();
3959                 foreach ($users as $user) {
3960                         $cid = $user['cid'];
3961                         // check if user really exists as contact
3962                         $contact = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d",
3963                                 intval($cid),
3964                                 intval($uid));
3965                         if (count($contact))
3966                                 $result = group_add_member($uid, $name, $cid, $gid);
3967                         else {
3968                                 $erroraddinguser = true;
3969                                 $errorusers[] = $cid;
3970                         }
3971                 }
3972
3973                 // return success message incl. missing users in array
3974                 $status = ($erroraddinguser ? "missing user" : "ok");
3975                 $success = array('success' => true, 'gid' => $gid, 'name' => $name, 'status' => $status, 'wrong users' => $errorusers);
3976                 return api_format_data("group_update", $type, array('result' => $success));
3977         }
3978
3979         api_register_func('api/friendica/group_update', 'api_friendica_group_update', true, API_METHOD_POST);
3980
3981         function api_friendica_activity($type) {
3982
3983                 $a = get_app();
3984
3985                 if (api_user() === false) throw new ForbiddenException();
3986                 $verb = strtolower($a->argv[3]);
3987                 $verb = preg_replace("|\..*$|", "", $verb);
3988
3989                 $id = (x($_REQUEST, 'id') ? $_REQUEST['id'] : 0);
3990
3991                 $res = do_like($id, $verb);
3992
3993                 if ($res) {
3994                         if ($type == "xml")
3995                                 $ok = "true";
3996                         else
3997                                 $ok = "ok";
3998                         return api_format_data('ok', $type, array('ok' => $ok));
3999                 } else {
4000                         throw new BadRequestException('Error adding activity');
4001                 }
4002
4003         }
4004
4005         /// @TODO move to top of file or somwhere better
4006         api_register_func('api/friendica/activity/like', 'api_friendica_activity', true, API_METHOD_POST);
4007         api_register_func('api/friendica/activity/dislike', 'api_friendica_activity', true, API_METHOD_POST);
4008         api_register_func('api/friendica/activity/attendyes', 'api_friendica_activity', true, API_METHOD_POST);
4009         api_register_func('api/friendica/activity/attendno', 'api_friendica_activity', true, API_METHOD_POST);
4010         api_register_func('api/friendica/activity/attendmaybe', 'api_friendica_activity', true, API_METHOD_POST);
4011         api_register_func('api/friendica/activity/unlike', 'api_friendica_activity', true, API_METHOD_POST);
4012         api_register_func('api/friendica/activity/undislike', 'api_friendica_activity', true, API_METHOD_POST);
4013         api_register_func('api/friendica/activity/unattendyes', 'api_friendica_activity', true, API_METHOD_POST);
4014         api_register_func('api/friendica/activity/unattendno', 'api_friendica_activity', true, API_METHOD_POST);
4015         api_register_func('api/friendica/activity/unattendmaybe', 'api_friendica_activity', true, API_METHOD_POST);
4016
4017         /**
4018          * @brief Returns notifications
4019          *
4020          * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
4021          * @return string
4022         */
4023         function api_friendica_notification($type) {
4024
4025                 $a = get_app();
4026
4027                 if (api_user() === false) throw new ForbiddenException();
4028                 if ($a->argc!==3) throw new BadRequestException("Invalid argument count");
4029                 $nm = new NotificationsManager();
4030
4031                 $notes = $nm->getAll(array(), "+seen -date", 50);
4032
4033                 if ($type == "xml") {
4034                         $xmlnotes = array();
4035                         foreach ($notes AS $note)
4036                                 $xmlnotes[] = array("@attributes" => $note);
4037
4038                         $notes = $xmlnotes;
4039                 }
4040
4041                 return api_format_data("notes", $type, array('note' => $notes));
4042         }
4043
4044         /**
4045          * @brief Set notification as seen and returns associated item (if possible)
4046          *
4047          * POST request with 'id' param as notification id
4048          *
4049          * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
4050          * @return string
4051          */
4052         function api_friendica_notification_seen($type) {
4053
4054                 $a = get_app();
4055
4056                 if (api_user() === false) throw new ForbiddenException();
4057                 if ($a->argc!==4) throw new BadRequestException("Invalid argument count");
4058
4059                 $id = (x($_REQUEST, 'id') ? intval($_REQUEST['id']) : 0);
4060
4061                 $nm = new NotificationsManager();
4062                 $note = $nm->getByID($id);
4063                 if (is_null($note)) throw new BadRequestException("Invalid argument");
4064
4065                 $nm->setSeen($note);
4066                 if ($note['otype']=='item') {
4067                         // would be really better with an ItemsManager and $im->getByID() :-P
4068                         $r = q("SELECT * FROM `item` WHERE `id`=%d AND `uid`=%d",
4069                                 intval($note['iid']),
4070                                 intval(local_user())
4071                         );
4072                         if ($r!==false) {
4073                                 // we found the item, return it to the user
4074                                 $user_info = api_get_user($a);
4075                                 $ret = api_format_items($r,$user_info, false, $type);
4076                                 $data = array('status' => $ret);
4077                                 return api_format_data("status", $type, $data);
4078                         }
4079                         // the item can't be found, but we set the note as seen, so we count this as a success
4080                 }
4081                 return api_format_data('result', $type, array('result' => "success"));
4082         }
4083
4084         /// @TODO move to top of file or somwhere better
4085         api_register_func('api/friendica/notification/seen', 'api_friendica_notification_seen', true, API_METHOD_POST);
4086         api_register_func('api/friendica/notification', 'api_friendica_notification', true, API_METHOD_GET);
4087
4088         /**
4089          * @brief update a direct_message to seen state
4090          *
4091          * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
4092          * @return string (success result=ok, error result=error with error message)
4093          */
4094         function api_friendica_direct_messages_setseen($type) {
4095                 $a = get_app();
4096                 if (api_user() === false) {
4097                         throw new ForbiddenException();
4098                 }
4099
4100                 // params
4101                 $user_info = api_get_user($a);
4102                 $uid = $user_info['uid'];
4103                 $id = (x($_REQUEST, 'id') ? $_REQUEST['id'] : 0);
4104
4105                 // return error if id is zero
4106                 if ($id == "") {
4107                         $answer = array('result' => 'error', 'message' => 'message id not specified');
4108                         return api_format_data("direct_messages_setseen", $type, array('$result' => $answer));
4109                 }
4110
4111                 // get data of the specified message id
4112                 $r = q("SELECT `id` FROM `mail` WHERE `id` = %d AND `uid` = %d",
4113                         intval($id),
4114                         intval($uid));
4115
4116                 // error message if specified id is not in database
4117                 if (!dbm::is_result($r)) {
4118                         $answer = array('result' => 'error', 'message' => 'message id not in database');
4119                         return api_format_data("direct_messages_setseen", $type, array('$result' => $answer));
4120                 }
4121
4122                 // update seen indicator
4123                 $result = q("UPDATE `mail` SET `seen` = 1 WHERE `id` = %d AND `uid` = %d",
4124                         intval($id),
4125                         intval($uid));
4126
4127                 if ($result) {
4128                         // return success
4129                         $answer = array('result' => 'ok', 'message' => 'message set to seen');
4130                         return api_format_data("direct_message_setseen", $type, array('$result' => $answer));
4131                 } else {
4132                         $answer = array('result' => 'error', 'message' => 'unknown error');
4133                         return api_format_data("direct_messages_setseen", $type, array('$result' => $answer));
4134                 }
4135         }
4136
4137         /// @TODO move to top of file or somwhere better
4138         api_register_func('api/friendica/direct_messages_setseen', 'api_friendica_direct_messages_setseen', true);
4139
4140         /**
4141          * @brief search for direct_messages containing a searchstring through api
4142          *
4143          * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
4144          * @return string (success: success=true if found and search_result contains found messages
4145          *                          success=false if nothing was found, search_result='nothing found',
4146          *                 error: result=error with error message)
4147          */
4148         function api_friendica_direct_messages_search($type) {
4149                 $a = get_app();
4150
4151                 if (api_user() === false) {
4152                         throw new ForbiddenException();
4153                 }
4154
4155                 // params
4156                 $user_info = api_get_user($a);
4157                 $searchstring = (x($_REQUEST, 'searchstring') ? $_REQUEST['searchstring'] : "");
4158                 $uid = $user_info['uid'];
4159
4160                 // error if no searchstring specified
4161                 if ($searchstring == "") {
4162                         $answer = array('result' => 'error', 'message' => 'searchstring not specified');
4163                         return api_format_data("direct_messages_search", $type, array('$result' => $answer));
4164                 }
4165
4166                 // get data for the specified searchstring
4167                 $r = q("SELECT `mail`.*, `contact`.`nurl` AS `contact-url` FROM `mail`,`contact` WHERE `mail`.`contact-id` = `contact`.`id` AND `mail`.`uid`=%d AND `body` LIKE '%s' ORDER BY `mail`.`id` DESC",
4168                         intval($uid),
4169                         dbesc('%'.$searchstring.'%')
4170                 );
4171
4172                 $profile_url = $user_info["url"];
4173
4174                 // message if nothing was found
4175                 if (!dbm::is_result($r)) {
4176                         $success = array('success' => false, 'search_results' => 'problem with query');
4177                 } elseif (count($r) == 0) {
4178                         $success = array('success' => false, 'search_results' => 'nothing found');
4179                 } else {
4180                         $ret = array();
4181                         foreach ($r as $item) {
4182                                 if ($box == "inbox" || $item['from-url'] != $profile_url) {
4183                                         $recipient = $user_info;
4184                                         $sender = api_get_user($a,normalise_link($item['contact-url']));
4185                                 } elseif ($box == "sentbox" || $item['from-url'] == $profile_url) {
4186                                         $recipient = api_get_user($a,normalise_link($item['contact-url']));
4187                                         $sender = $user_info;
4188                                 }
4189
4190                                 $ret[] = api_format_messages($item, $recipient, $sender);
4191                         }
4192                         $success = array('success' => true, 'search_results' => $ret);
4193                 }
4194
4195                 return api_format_data("direct_message_search", $type, array('$result' => $success));
4196         }
4197
4198         /// @TODO move to top of file or somwhere better
4199         api_register_func('api/friendica/direct_messages_search', 'api_friendica_direct_messages_search', true);
4200
4201         /**
4202          * @brief return data of all the profiles a user has to the client
4203          *
4204          * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
4205          * @return string
4206          */
4207         function api_friendica_profile_show($type) {
4208                 $a = get_app();
4209
4210                 if (api_user() === false) {
4211                         throw new ForbiddenException();
4212                 }
4213
4214                 // input params
4215                 $profileid = (x($_REQUEST, 'profile_id') ? $_REQUEST['profile_id'] : 0);
4216
4217                 // retrieve general information about profiles for user
4218                 $multi_profiles = feature_enabled(api_user(),'multi_profiles');
4219                 $directory = get_config('system', 'directory');
4220
4221                 // get data of the specified profile id or all profiles of the user if not specified
4222                 if ($profileid != 0) {
4223                         $r = q("SELECT * FROM `profile` WHERE `uid` = %d AND `id` = %d",
4224                                 intval(api_user()),
4225                                 intval($profileid));
4226
4227                         // error message if specified gid is not in database
4228                         if (!dbm::is_result($r)) {
4229                                 throw new BadRequestException("profile_id not available");
4230                         }
4231                 } else {
4232                         $r = q("SELECT * FROM `profile` WHERE `uid` = %d",
4233                                 intval(api_user()));
4234                 }
4235                 // loop through all returned profiles and retrieve data and users
4236                 $k = 0;
4237                 foreach ($r as $rr) {
4238                         $profile = api_format_items_profiles($rr, $type);
4239
4240                         // select all users from contact table, loop and prepare standard return for user data
4241                         $users = array();
4242                         $r = q("SELECT `id`, `nurl` FROM `contact` WHERE `uid`= %d AND `profile-id` = %d",
4243                                 intval(api_user()),
4244                                 intval($rr['profile_id']));
4245
4246                         foreach ($r as $rr) {
4247                                 $user = api_get_user($a, $rr['nurl']);
4248                                 ($type == "xml") ? $users[$k++ . ":user"] = $user : $users[] = $user;
4249                         }
4250                         $profile['users'] = $users;
4251
4252                         // add prepared profile data to array for final return
4253                         if ($type == "xml") {
4254                                 $profiles[$k++ . ":profile"] = $profile;
4255                         } else {
4256                                 $profiles[] = $profile;
4257                         }
4258                 }
4259
4260                 // return settings, authenticated user and profiles data
4261                 $self = q("SELECT `nurl` FROM `contact` WHERE `uid`= %d AND `self` LIMIT 1", intval(api_user()));
4262
4263                 $result = array('multi_profiles' => $multi_profiles ? true : false,
4264                                                 'global_dir' => $directory,
4265                                                 'friendica_owner' => api_get_user($a, $self[0]['nurl']),
4266                                                 'profiles' => $profiles);
4267                 return api_format_data("friendica_profiles", $type, array('$result' => $result));
4268         }
4269         api_register_func('api/friendica/profile/show', 'api_friendica_profile_show', true, API_METHOD_GET);
4270
4271 /*
4272 @TODO Maybe open to implement?
4273 To.Do:
4274     [pagename] => api/1.1/statuses/lookup.json
4275     [id] => 605138389168451584
4276     [include_cards] => true
4277     [cards_platform] => Android-12
4278     [include_entities] => true
4279     [include_my_retweet] => 1
4280     [include_rts] => 1
4281     [include_reply_count] => true
4282     [include_descendent_reply_count] => true
4283 (?)
4284
4285
4286 Not implemented by now:
4287 statuses/retweets_of_me
4288 friendships/create
4289 friendships/destroy
4290 friendships/exists
4291 friendships/show
4292 account/update_location
4293 account/update_profile_background_image
4294 account/update_profile_image
4295 blocks/create
4296 blocks/destroy
4297 friendica/profile/update
4298 friendica/profile/create
4299 friendica/profile/delete
4300
4301 Not implemented in status.net:
4302 statuses/retweeted_to_me
4303 statuses/retweeted_by_me
4304 direct_messages/destroy
4305 account/end_session
4306 account/update_delivery_device
4307 notifications/follow
4308 notifications/leave
4309 blocks/exists
4310 blocks/blocking
4311 lists
4312 */