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