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