]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/util.php
e5a8eaea06250438a0299256507a77988793c860
[quix0rs-gnu-social.git] / lib / util.php
1 <?php
2 /*
3  * Laconica - a distributed open-source microblogging tool
4  * Copyright (C) 2008, 2009, Control Yourself, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /* XXX: break up into separate modules (HTTP, user, files) */
21
22 // Show a server error
23
24 function common_server_error($msg, $code=500)
25 {
26     $err = new ServerErrorAction($msg, $code);
27     $err->showPage();
28 }
29
30 // Show a user error
31 function common_user_error($msg, $code=400)
32 {
33     $err = new ClientErrorAction($msg, $code);
34     $err->showPage();
35 }
36
37 function common_init_locale($language=null)
38 {
39     if(!$language) {
40         $language = common_language();
41     }
42     putenv('LANGUAGE='.$language);
43     putenv('LANG='.$language);
44     return setlocale(LC_ALL, $language . ".utf8",
45                      $language . ".UTF8",
46                      $language . ".utf-8",
47                      $language . ".UTF-8",
48                      $language);
49 }
50
51 function common_init_language()
52 {
53     mb_internal_encoding('UTF-8');
54     $language = common_language();
55     // So we don't have to make people install the gettext locales
56     $locale_set = common_init_locale($language);
57     bindtextdomain("laconica", common_config('site','locale_path'));
58     bind_textdomain_codeset("laconica", "UTF-8");
59     textdomain("laconica");
60     setlocale(LC_CTYPE, 'C');
61     if(!$locale_set) {
62         common_log(LOG_INFO,'Language requested:'.$language.' - locale could not be set:',__FILE__);
63     }
64 }
65
66 function common_timezone()
67 {
68     if (common_logged_in()) {
69         $user = common_current_user();
70         if ($user->timezone) {
71             return $user->timezone;
72         }
73     }
74
75     return common_config('site', 'timezone');
76 }
77
78 function common_language()
79 {
80
81     // If there is a user logged in and they've set a language preference
82     // then return that one...
83     if (_have_config() && common_logged_in()) {
84         $user = common_current_user();
85         $user_language = $user->language;
86         if ($user_language)
87           return $user_language;
88     }
89
90     // Otherwise, find the best match for the languages requested by the
91     // user's browser...
92     $httplang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : null;
93     if (!empty($httplang)) {
94         $language = client_prefered_language($httplang);
95         if ($language)
96           return $language;
97     }
98
99     // Finally, if none of the above worked, use the site's default...
100     return common_config('site', 'language');
101 }
102 // salted, hashed passwords are stored in the DB
103
104 function common_munge_password($password, $id)
105 {
106     return md5($password . $id);
107 }
108
109 // check if a username exists and has matching password
110 function common_check_user($nickname, $password)
111 {
112     // NEVER allow blank passwords, even if they match the DB
113     if (mb_strlen($password) == 0) {
114         return false;
115     }
116     $user = User::staticGet('nickname', $nickname);
117     if (is_null($user)) {
118         return false;
119     } else {
120         if (0 == strcmp(common_munge_password($password, $user->id),
121                         $user->password)) {
122             return $user;
123         } else {
124             return false;
125         }
126     }
127 }
128
129 // is the current user logged in?
130 function common_logged_in()
131 {
132     return (!is_null(common_current_user()));
133 }
134
135 function common_have_session()
136 {
137     return (0 != strcmp(session_id(), ''));
138 }
139
140 function common_ensure_session()
141 {
142     $c = null;
143     if (array_key_exists(session_name, $_COOKIE)) {
144         $c = $_COOKIE[session_name()];
145     }
146     if (!common_have_session()) {
147         @session_start();
148         if (!isset($_SESSION['started'])) {
149             $_SESSION['started'] = time();
150             if (!empty($c)) {
151                 common_log(LOG_WARNING, 'Session cookie "' . $_COOKIE[session_name()] . '" ' .
152                            ' is set but started value is null');
153             }
154         }
155     }
156 }
157
158 // Three kinds of arguments:
159 // 1) a user object
160 // 2) a nickname
161 // 3) null to clear
162
163 // Initialize to false; set to null if none found
164
165 $_cur = false;
166
167 function common_set_user($user)
168 {
169
170     global $_cur;
171
172     if (is_null($user) && common_have_session()) {
173         $_cur = null;
174         unset($_SESSION['userid']);
175         return true;
176     } else if (is_string($user)) {
177         $nickname = $user;
178         $user = User::staticGet('nickname', $nickname);
179     } else if (!($user instanceof User)) {
180         return false;
181     }
182
183     if ($user) {
184         common_ensure_session();
185         $_SESSION['userid'] = $user->id;
186         $_cur = $user;
187         return $_cur;
188     }
189     return false;
190 }
191
192 function common_set_cookie($key, $value, $expiration=0)
193 {
194     $path = common_config('site', 'path');
195     $server = common_config('site', 'server');
196
197     if ($path && ($path != '/')) {
198         $cookiepath = '/' . $path . '/';
199     } else {
200         $cookiepath = '/';
201     }
202     return setcookie($key,
203                      $value,
204                      $expiration,
205                      $cookiepath,
206                      $server);
207 }
208
209 define('REMEMBERME', 'rememberme');
210 define('REMEMBERME_EXPIRY', 30 * 24 * 60 * 60); // 30 days
211
212 function common_rememberme($user=null)
213 {
214     if (!$user) {
215         $user = common_current_user();
216         if (!$user) {
217             common_debug('No current user to remember', __FILE__);
218             return false;
219         }
220     }
221
222     $rm = new Remember_me();
223
224     $rm->code = common_good_rand(16);
225     $rm->user_id = $user->id;
226
227     // Wrap the insert in some good ol' fashioned transaction code
228
229     $rm->query('BEGIN');
230
231     $result = $rm->insert();
232
233     if (!$result) {
234         common_log_db_error($rm, 'INSERT', __FILE__);
235         common_debug('Error adding rememberme record for ' . $user->nickname, __FILE__);
236         return false;
237     }
238
239     $rm->query('COMMIT');
240
241     common_debug('Inserted rememberme record (' . $rm->code . ', ' . $rm->user_id . '); result = ' . $result . '.', __FILE__);
242
243     $cookieval = $rm->user_id . ':' . $rm->code;
244
245     common_log(LOG_INFO, 'adding rememberme cookie "' . $cookieval . '" for ' . $user->nickname);
246
247     common_set_cookie(REMEMBERME, $cookieval, time() + REMEMBERME_EXPIRY);
248
249     return true;
250 }
251
252 function common_remembered_user()
253 {
254
255     $user = null;
256
257     $packed = isset($_COOKIE[REMEMBERME]) ? $_COOKIE[REMEMBERME] : null;
258
259     if (!$packed) {
260         return null;
261     }
262
263     list($id, $code) = explode(':', $packed);
264
265     if (!$id || !$code) {
266         common_log(LOG_WARNING, 'Malformed rememberme cookie: ' . $packed);
267         common_forgetme();
268         return null;
269     }
270
271     $rm = Remember_me::staticGet($code);
272
273     if (!$rm) {
274         common_log(LOG_WARNING, 'No such remember code: ' . $code);
275         common_forgetme();
276         return null;
277     }
278
279     if ($rm->user_id != $id) {
280         common_log(LOG_WARNING, 'Rememberme code for wrong user: ' . $rm->user_id . ' != ' . $id);
281         common_forgetme();
282         return null;
283     }
284
285     $user = User::staticGet($rm->user_id);
286
287     if (!$user) {
288         common_log(LOG_WARNING, 'No such user for rememberme: ' . $rm->user_id);
289         common_forgetme();
290         return null;
291     }
292
293     // successful!
294     $result = $rm->delete();
295
296     if (!$result) {
297         common_log_db_error($rm, 'DELETE', __FILE__);
298         common_log(LOG_WARNING, 'Could not delete rememberme: ' . $code);
299         common_forgetme();
300         return null;
301     }
302
303     common_log(LOG_INFO, 'logging in ' . $user->nickname . ' using rememberme code ' . $rm->code);
304
305     common_set_user($user);
306     common_real_login(false);
307
308     // We issue a new cookie, so they can log in
309     // automatically again after this session
310
311     common_rememberme($user);
312
313     return $user;
314 }
315
316 // must be called with a valid user!
317
318 function common_forgetme()
319 {
320     common_set_cookie(REMEMBERME, '', 0);
321 }
322
323 // who is the current user?
324 function common_current_user()
325 {
326     global $_cur;
327
328     if (!_have_config()) {
329         return null;
330     }
331
332     if ($_cur === false) {
333
334         if (isset($_REQUEST[session_name()]) || (isset($_SESSION['userid']) && $_SESSION['userid'])) {
335             common_ensure_session();
336             $id = isset($_SESSION['userid']) ? $_SESSION['userid'] : false;
337             if ($id) {
338                 $_cur = User::staticGet($id);
339                 return $_cur;
340             }
341         }
342
343         // that didn't work; try to remember; will init $_cur to null on failure
344         $_cur = common_remembered_user();
345
346         if ($_cur) {
347             common_debug("Got User " . $_cur->nickname);
348             common_debug("Faking session on remembered user");
349             // XXX: Is this necessary?
350             $_SESSION['userid'] = $_cur->id;
351         }
352     }
353
354     return $_cur;
355 }
356
357 // Logins that are 'remembered' aren't 'real' -- they're subject to
358 // cookie-stealing. So, we don't let them do certain things. New reg,
359 // OpenID, and password logins _are_ real.
360
361 function common_real_login($real=true)
362 {
363     common_ensure_session();
364     $_SESSION['real_login'] = $real;
365 }
366
367 function common_is_real_login()
368 {
369     return common_logged_in() && $_SESSION['real_login'];
370 }
371
372 // get canonical version of nickname for comparison
373 function common_canonical_nickname($nickname)
374 {
375     // XXX: UTF-8 canonicalization (like combining chars)
376     return strtolower($nickname);
377 }
378
379 // get canonical version of email for comparison
380 function common_canonical_email($email)
381 {
382     // XXX: canonicalize UTF-8
383     // XXX: lcase the domain part
384     return $email;
385 }
386
387 function common_render_content($text, $notice)
388 {
389     $r = common_render_text($text);
390     $id = $notice->profile_id;
391     $r = preg_replace('/(^|\s+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r);
392     $r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r);
393     $r = preg_replace('/(^|\s+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r);
394     $r = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
395     return $r;
396 }
397
398 function common_render_text($text)
399 {
400     $r = htmlspecialchars($text);
401
402     $r = preg_replace('/[\x{0}-\x{8}\x{b}-\x{c}\x{e}-\x{19}]/', '', $r);
403     $r = common_replace_urls_callback($r, 'common_linkify');
404     $r = preg_replace('/(^|\(|\[|\s+)#([A-Za-z0-9_\-\.]{1,64})/e', "'\\1#'.common_tag_link('\\2')", $r);
405     // XXX: machine tags
406     return $r;
407 }
408
409 function common_replace_urls_callback($text, $callback, $notice_id = null) {
410     // Start off with a regex
411     $regex = '#'.
412     '(?:'.
413         '(?:'.
414             '(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|xmpp|irc)://'.
415             '|'.
416             '(?:mailto|aim|tel):'.
417         ')'.
418         '[^.\s]+\.[^\s]+'.
419         '|'.
420         '(?:[^.\s/:]+\.)+'.
421         '(?:museum|travel|[a-z]{2,4})'.
422         '(?:[:/][^\s]*)?'.
423     ')'.
424     '#ix';
425     preg_match_all($regex, $text, $matches);
426
427     // Then clean up what the regex left behind
428     $offset = 0;
429     foreach($matches[0] as $orig_url) {
430         $url = htmlspecialchars_decode($orig_url);
431
432         // Make sure we didn't pick up an email address
433         if (preg_match('#^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$#i', $url)) continue;
434
435         // Remove surrounding punctuation
436         $url = trim($url, '.?!,;:\'"`([<');
437
438         // Remove surrounding parens and the like
439         preg_match('/[)\]>]+$/', $url, $trailing);
440         if (isset($trailing[0])) {
441             preg_match_all('/[(\[<]/', $url, $opened);
442             preg_match_all('/[)\]>]/', $url, $closed);
443             $unopened = count($closed[0]) - count($opened[0]);
444
445             // Make sure not to take off more closing parens than there are at the end
446             $unopened = ($unopened > mb_strlen($trailing[0])) ? mb_strlen($trailing[0]):$unopened;
447
448             $url = ($unopened > 0) ? mb_substr($url, 0, $unopened * -1):$url;
449         }
450
451         // Remove trailing punctuation again (in case there were some inside parens)
452         $url = rtrim($url, '.?!,;:\'"`');
453
454         // Make sure we didn't capture part of the next sentence
455         preg_match('#((?:[^.\s/]+\.)+)(museum|travel|[a-z]{2,4})#i', $url, $url_parts);
456
457         // Were the parts capitalized any?
458         $last_part = (mb_strtolower($url_parts[2]) !== $url_parts[2]) ? true:false;
459         $prev_part = (mb_strtolower($url_parts[1]) !== $url_parts[1]) ? true:false;
460
461         // If the first part wasn't cap'd but the last part was, we captured too much
462         if ((!$prev_part && $last_part)) {
463             $url = mb_substr($url, 0 , mb_strpos($url, '.'.$url_parts['2'], 0));
464         }
465
466         // Capture the new TLD
467         preg_match('#((?:[^.\s/]+\.)+)(museum|travel|[a-z]{2,4})#i', $url, $url_parts);
468
469         $tlds = array('ac', 'ad', 'ae', 'aero', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq', 'ar', 'arpa', 'as', 'asia', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'biz', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cat', 'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'com', 'coop', 'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'edu', 'ee', 'eg', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gov', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'info', 'int', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm', 'jo', 'jobs', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mil', 'mk', 'ml', 'mm', 'mn', 'mo', 'mobi', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'museum', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'name', 'nc', 'ne', 'net', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'org', 'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'pro', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'st', 'su', 'sv', 'sy', 'sz', 'tc', 'td', 'tel', 'tf', 'tg', 'th', 'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'travel', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', 'yt', 'yu', 'za', 'zm', 'zw');
470
471         if (!in_array($url_parts[2], $tlds)) continue;
472
473         // Make sure we didn't capture a hash tag
474         if (strpos($url, '#') === 0) continue;
475
476         // Put the url back the way we found it.
477         $url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url);
478
479         // Call user specified func
480         if (empty($notice_id)) {
481             $modified_url = call_user_func($callback, $url);
482         } else {
483             $modified_url = call_user_func($callback, array($url, $notice_id));
484         }
485
486         // Replace it!
487         $start = mb_strpos($text, $url, $offset);
488         $text = mb_substr($text, 0, $start).$modified_url.mb_substr($text, $start + mb_strlen($url), mb_strlen($text));
489         $offset = $start + mb_strlen($modified_url);
490     }
491
492     return $text;
493 }
494
495 function common_linkify($url) {
496     // It comes in special'd, so we unspecial it before passing to the stringifying
497     // functions
498     $url = htmlspecialchars_decode($url);
499     $display = File_redirection::_canonUrl($url);
500     $longurl_data = File_redirection::where($url);
501     if (is_array($longurl_data)) {
502         $longurl = $longurl_data['url'];
503     } elseif (is_string($longurl_data)) {
504         $longurl = $longurl_data;
505     } else {
506         die('impossible to linkify');
507     }
508
509     $attrs = array('href' => $longurl, 'rel' => 'external');
510
511     $is_attachment = false;
512     $attachment_id = null;
513     $has_thumb = false;
514
515     // Check to see whether there's a filename associated with this URL.
516     // If there is, it's an upload and qualifies as an attachment
517
518     $localfile = File::staticGet('url', $longurl);
519
520     if (!empty($localfile)) {
521         if (isset($localfile->filename)) {
522             $is_attachment = true;
523             $attachment_id = $localfile->id;
524         }
525     }
526
527 // if this URL is an attachment, then we set class='attachment' and id='attahcment-ID'
528 // where ID is the id of the attachment for the given URL.
529 //
530 // we need a better test telling what can be shown as an attachment
531 // we're currently picking up oembeds only.
532 // I think the best option is another file_view table in the db
533 // and associated dbobject.
534
535     $query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'";
536     $file = new File;
537     $file->query($query);
538     $file->fetch();
539
540     if (!empty($file->file_id)) {
541         $is_attachment = true;
542         $attachment_id = $file->file_id;
543
544         $query = "select file_thumbnail.file_id as file_id from file join file_thumbnail on file.id = file_thumbnail.file_id where file.url='$longurl'";
545         $file2 = new File;
546         $file2->query($query);
547         $file2->fetch();
548
549         if (!empty($file2)) {
550             $has_thumb = true;
551         }
552     }
553
554     // Add clippy
555     if ($is_attachment) {
556         $attrs['class'] = 'attachment';
557         if ($has_thumb) {
558             $attrs['class'] = 'attachment thumbnail';
559         }
560         $attrs['id'] = "attachment-{$attachment_id}";
561     }
562
563     return XMLStringer::estring('a', $attrs, $display);
564 }
565
566 function common_shorten_links($text)
567 {
568     if (mb_strlen($text) <= 140) return $text;
569     return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
570 }
571
572 function common_xml_safe_str($str)
573 {
574     // Neutralize control codes and surrogates
575         return preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str);
576 }
577
578 function common_tag_link($tag)
579 {
580     $canonical = common_canonical_tag($tag);
581     $url = common_local_url('tag', array('tag' => $canonical));
582     $xs = new XMLStringer();
583     $xs->elementStart('span', 'tag');
584     $xs->element('a', array('href' => $url,
585                             'rel' => 'tag'),
586                  $tag);
587     $xs->elementEnd('span');
588     return $xs->getString();
589 }
590
591 function common_canonical_tag($tag)
592 {
593     return strtolower(str_replace(array('-', '_', '.'), '', $tag));
594 }
595
596 function common_valid_profile_tag($str)
597 {
598     return preg_match('/^[A-Za-z0-9_\-\.]{1,64}$/', $str);
599 }
600
601 function common_at_link($sender_id, $nickname)
602 {
603     $sender = Profile::staticGet($sender_id);
604     $recipient = common_relative_profile($sender, common_canonical_nickname($nickname));
605     if ($recipient) {
606         $user = User::staticGet('id', $recipient->id);
607         if ($user) {
608             $url = common_local_url('userbyid', array('id' => $user->id));
609         } else {
610             $url = $recipient->profileurl;
611         }
612         $xs = new XMLStringer(false);
613         $attrs = array('href' => $url,
614                        'class' => 'url');
615         if (!empty($recipient->fullname)) {
616             $attrs['title'] = $recipient->fullname . ' (' . $recipient->nickname . ')';
617         }
618         $xs->elementStart('span', 'vcard');
619         $xs->elementStart('a', $attrs);
620         $xs->element('span', 'fn nickname', $nickname);
621         $xs->elementEnd('a');
622         $xs->elementEnd('span');
623         return $xs->getString();
624     } else {
625         return $nickname;
626     }
627 }
628
629 function common_group_link($sender_id, $nickname)
630 {
631     $sender = Profile::staticGet($sender_id);
632     $group = User_group::getForNickname($nickname);
633     if ($group && $sender->isMember($group)) {
634         $attrs = array('href' => $group->permalink(),
635                        'class' => 'url');
636         if (!empty($group->fullname)) {
637             $attrs['title'] = $group->fullname . ' (' . $group->nickname . ')';
638         }
639         $xs = new XMLStringer();
640         $xs->elementStart('span', 'vcard');
641         $xs->elementStart('a', $attrs);
642         $xs->element('span', 'fn nickname', $nickname);
643         $xs->elementEnd('a');
644         $xs->elementEnd('span');
645         return $xs->getString();
646     } else {
647         return $nickname;
648     }
649 }
650
651 function common_at_hash_link($sender_id, $tag)
652 {
653     $user = User::staticGet($sender_id);
654     if (!$user) {
655         return $tag;
656     }
657     $tagged = Profile_tag::getTagged($user->id, common_canonical_tag($tag));
658     if ($tagged) {
659         $url = common_local_url('subscriptions',
660                                 array('nickname' => $user->nickname,
661                                       'tag' => $tag));
662         $xs = new XMLStringer();
663         $xs->elementStart('span', 'tag');
664         $xs->element('a', array('href' => $url,
665                                 'rel' => $tag),
666                      $tag);
667         $xs->elementEnd('span');
668         return $xs->getString();
669     } else {
670         return $tag;
671     }
672 }
673
674 function common_relative_profile($sender, $nickname, $dt=null)
675 {
676     // Try to find profiles this profile is subscribed to that have this nickname
677     $recipient = new Profile();
678     // XXX: use a join instead of a subquery
679     $recipient->whereAdd('EXISTS (SELECT subscribed from subscription where subscriber = '.$sender->id.' and subscribed = id)', 'AND');
680     $recipient->whereAdd("nickname = '" . trim($nickname) . "'", 'AND');
681     if ($recipient->find(true)) {
682         // XXX: should probably differentiate between profiles with
683         // the same name by date of most recent update
684         return $recipient;
685     }
686     // Try to find profiles that listen to this profile and that have this nickname
687     $recipient = new Profile();
688     // XXX: use a join instead of a subquery
689     $recipient->whereAdd('EXISTS (SELECT subscriber from subscription where subscribed = '.$sender->id.' and subscriber = id)', 'AND');
690     $recipient->whereAdd("nickname = '" . trim($nickname) . "'", 'AND');
691     if ($recipient->find(true)) {
692         // XXX: should probably differentiate between profiles with
693         // the same name by date of most recent update
694         return $recipient;
695     }
696     // If this is a local user, try to find a local user with that nickname.
697     $sender = User::staticGet($sender->id);
698     if ($sender) {
699         $recipient_user = User::staticGet('nickname', $nickname);
700         if ($recipient_user) {
701             return $recipient_user->getProfile();
702         }
703     }
704     // Otherwise, no links. @messages from local users to remote users,
705     // or from remote users to other remote users, are just
706     // outside our ability to make intelligent guesses about
707     return null;
708 }
709
710 function common_local_url($action, $args=null, $params=null, $fragment=null)
711 {
712     static $sensitive = array('login', 'register', 'passwordsettings',
713                               'twittersettings', 'finishopenidlogin',
714                               'finishaddopenid', 'api');
715
716     $r = Router::get();
717     $path = $r->build($action, $args, $params, $fragment);
718
719     $ssl = in_array($action, $sensitive);
720
721     if (common_config('site','fancy')) {
722         $url = common_path(mb_substr($path, 1), $ssl);
723     } else {
724         if (mb_strpos($path, '/index.php') === 0) {
725             $url = common_path(mb_substr($path, 1), $ssl);
726         } else {
727             $url = common_path('index.php'.$path, $ssl);
728         }
729     }
730     return $url;
731 }
732
733 function common_path($relative, $ssl=false)
734 {
735     $pathpart = (common_config('site', 'path')) ? common_config('site', 'path')."/" : '';
736
737     if (($ssl && (common_config('site', 'ssl') === 'sometimes'))
738         || common_config('site', 'ssl') === 'always') {
739         $proto = 'https';
740         if (is_string(common_config('site', 'sslserver')) &&
741             mb_strlen(common_config('site', 'sslserver')) > 0) {
742             $serverpart = common_config('site', 'sslserver');
743         } else {
744             $serverpart = common_config('site', 'server');
745         }
746     } else {
747         $proto = 'http';
748         $serverpart = common_config('site', 'server');
749     }
750
751     return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
752 }
753
754 function common_date_string($dt)
755 {
756     // XXX: do some sexy date formatting
757     // return date(DATE_RFC822, $dt);
758     $t = strtotime($dt);
759     $now = time();
760     $diff = $now - $t;
761
762     if ($now < $t) { // that shouldn't happen!
763         return common_exact_date($dt);
764     } else if ($diff < 60) {
765         return _('a few seconds ago');
766     } else if ($diff < 92) {
767         return _('about a minute ago');
768     } else if ($diff < 3300) {
769         return sprintf(_('about %d minutes ago'), round($diff/60));
770     } else if ($diff < 5400) {
771         return _('about an hour ago');
772     } else if ($diff < 22 * 3600) {
773         return sprintf(_('about %d hours ago'), round($diff/3600));
774     } else if ($diff < 37 * 3600) {
775         return _('about a day ago');
776     } else if ($diff < 24 * 24 * 3600) {
777         return sprintf(_('about %d days ago'), round($diff/(24*3600)));
778     } else if ($diff < 46 * 24 * 3600) {
779         return _('about a month ago');
780     } else if ($diff < 330 * 24 * 3600) {
781         return sprintf(_('about %d months ago'), round($diff/(30*24*3600)));
782     } else if ($diff < 480 * 24 * 3600) {
783         return _('about a year ago');
784     } else {
785         return common_exact_date($dt);
786     }
787 }
788
789 function common_exact_date($dt)
790 {
791     static $_utc;
792     static $_siteTz;
793
794     if (!$_utc) {
795         $_utc = new DateTimeZone('UTC');
796         $_siteTz = new DateTimeZone(common_timezone());
797     }
798
799     $dateStr = date('d F Y H:i:s', strtotime($dt));
800     $d = new DateTime($dateStr, $_utc);
801     $d->setTimezone($_siteTz);
802     return $d->format(DATE_RFC850);
803 }
804
805 function common_date_w3dtf($dt)
806 {
807     $dateStr = date('d F Y H:i:s', strtotime($dt));
808     $d = new DateTime($dateStr, new DateTimeZone('UTC'));
809     $d->setTimezone(new DateTimeZone(common_timezone()));
810     return $d->format(DATE_W3C);
811 }
812
813 function common_date_rfc2822($dt)
814 {
815     $dateStr = date('d F Y H:i:s', strtotime($dt));
816     $d = new DateTime($dateStr, new DateTimeZone('UTC'));
817     $d->setTimezone(new DateTimeZone(common_timezone()));
818     return $d->format('r');
819 }
820
821 function common_date_iso8601($dt)
822 {
823     $dateStr = date('d F Y H:i:s', strtotime($dt));
824     $d = new DateTime($dateStr, new DateTimeZone('UTC'));
825     $d->setTimezone(new DateTimeZone(common_timezone()));
826     return $d->format('c');
827 }
828
829 function common_sql_now()
830 {
831     return strftime('%Y-%m-%d %H:%M:%S', time());
832 }
833
834 function common_redirect($url, $code=307)
835 {
836     static $status = array(301 => "Moved Permanently",
837                            302 => "Found",
838                            303 => "See Other",
839                            307 => "Temporary Redirect");
840
841     header('HTTP/1.1 '.$code.' '.$status[$code]);
842     header("Location: $url");
843
844     $xo = new XMLOutputter();
845     $xo->startXML('a',
846                   '-//W3C//DTD XHTML 1.0 Strict//EN',
847                   'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
848     $xo->element('a', array('href' => $url), $url);
849     $xo->endXML();
850     exit;
851 }
852
853 function common_broadcast_notice($notice, $remote=false)
854 {
855     if (common_config('queue', 'enabled')) {
856         // Do it later!
857         return common_enqueue_notice($notice);
858     } else {
859         return common_real_broadcast($notice, $remote);
860     }
861 }
862
863 // Stick the notice on the queue
864
865 function common_enqueue_notice($notice)
866 {
867     $transports = array('omb', 'sms', 'public', 'twitter', 'facebook', 'ping');
868
869     if (common_config('xmpp', 'enabled'))
870     {
871         $transports[] = 'jabber';
872     }
873
874     if (common_config('queue','subsystem') == 'stomp') {
875         common_enqueue_notice_stomp($notice, $transports);
876     }
877     else {
878         common_enqueue_notice_db($notice, $transports);
879     }
880     return $result;
881 }
882
883 function common_enqueue_notice_stomp($notice, $transports)
884 {
885     // use an external message queue system via STOMP
886     require_once("Stomp.php");
887
888     $server = common_config('queue','stomp_server');
889     $username = common_config('queue', 'stomp_username');
890     $password = common_config('queue', 'stomp_password');
891
892     $con = new Stomp($server);
893
894     if (!$con->connect($username, $password)) {
895         common_log(LOG_ERR, 'Failed to connect to queue server');
896         return false;
897     }
898
899     $queue_basename = common_config('queue','queue_basename');
900
901     foreach ($transports as $transport) {
902         $result = $con->send('/queue/'.$queue_basename.'-'.$transport, // QUEUE
903                              $notice->id,               // BODY of the message
904                              array ('created' => $notice->created));
905         if (!$result) {
906             common_log(LOG_ERR, 'Error sending to '.$transport.' queue');
907             return false;
908         }
909         common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' . $notice->id . ' for ' . $transport);
910     }
911
912     //send tags as headers, so they can be used as JMS selectors
913     common_log(LOG_DEBUG, 'searching for tags ' . $notice->id);
914     $tags = array();
915     $tag = new Notice_tag();
916     $tag->notice_id = $notice->id;
917     if ($tag->find()) {
918         while ($tag->fetch()) {
919             common_log(LOG_DEBUG, 'tag found = ' . $tag->tag);
920             array_push($tags,$tag->tag);
921         }
922     }
923     $tag->free();
924
925     $con->send('/topic/laconica.'.$notice->profile_id,
926                $notice->content,
927                array(
928                      'profile_id' => $notice->profile_id,
929                      'created' => $notice->created,
930                      'tags' => implode($tags,' - ')
931                      )
932                );
933     common_log(LOG_DEBUG, 'sent to personal topic ' . $notice->id);
934     $con->send('/topic/laconica.allusers',
935                $notice->content,
936                array(
937                      'profile_id' => $notice->profile_id,
938                      'created' => $notice->created,
939                      'tags' => implode($tags,' - ')
940                      )
941                );
942     common_log(LOG_DEBUG, 'sent to catch-all topic ' . $notice->id);
943     $result = true;
944 }
945
946 function common_enqueue_notice_db($notice, $transports)
947 {
948     // in any other case, 'internal'
949     foreach ($transports as $transport) {
950         common_enqueue_notice_transport($notice, $transport);
951     }
952 }
953
954 function common_enqueue_notice_transport($notice, $transport)
955 {
956     $qi = new Queue_item();
957     $qi->notice_id = $notice->id;
958     $qi->transport = $transport;
959     $qi->created = $notice->created;
960     $result = $qi->insert();
961     if (!$result) {
962         $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
963         common_log(LOG_ERR, 'DB error inserting queue item: ' . $last_error->message);
964         throw new ServerException('DB error inserting queue item: ' . $last_error->message);
965     }
966     common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport);
967     return true;
968 }
969
970 function common_real_broadcast($notice, $remote=false)
971 {
972     $success = true;
973     if (!$remote) {
974         // Make sure we have the OMB stuff
975         require_once(INSTALLDIR.'/lib/omb.php');
976         $success = omb_broadcast_remote_subscribers($notice);
977         if (!$success) {
978             common_log(LOG_ERR, 'Error in OMB broadcast for notice ' . $notice->id);
979         }
980     }
981     if ($success) {
982         require_once(INSTALLDIR.'/lib/jabber.php');
983         $success = jabber_broadcast_notice($notice);
984         if (!$success) {
985             common_log(LOG_ERR, 'Error in jabber broadcast for notice ' . $notice->id);
986         }
987     }
988     if ($success) {
989         require_once(INSTALLDIR.'/lib/mail.php');
990         $success = mail_broadcast_notice_sms($notice);
991         if (!$success) {
992             common_log(LOG_ERR, 'Error in sms broadcast for notice ' . $notice->id);
993         }
994     }
995     if ($success) {
996         $success = jabber_public_notice($notice);
997         if (!$success) {
998             common_log(LOG_ERR, 'Error in public broadcast for notice ' . $notice->id);
999         }
1000     }
1001     if ($success) {
1002         $success = broadcast_twitter($notice);
1003         if (!$success) {
1004             common_log(LOG_ERR, 'Error in Twitter broadcast for notice ' . $notice->id);
1005         }
1006     }
1007
1008     // XXX: Do a real-time FB broadcast here?
1009
1010     // XXX: broadcast notices to other IM
1011     return $success;
1012 }
1013
1014 function common_broadcast_profile($profile)
1015 {
1016     // XXX: optionally use a queue system like http://code.google.com/p/microapps/wiki/NQDQ
1017     require_once(INSTALLDIR.'/lib/omb.php');
1018     omb_broadcast_profile($profile);
1019     // XXX: Other broadcasts...?
1020     return true;
1021 }
1022
1023 function common_profile_url($nickname)
1024 {
1025     return common_local_url('showstream', array('nickname' => $nickname));
1026 }
1027
1028 // Should make up a reasonable root URL
1029
1030 function common_root_url($ssl=false)
1031 {
1032     return common_path('', $ssl);
1033 }
1034
1035 // returns $bytes bytes of random data as a hexadecimal string
1036 // "good" here is a goal and not a guarantee
1037
1038 function common_good_rand($bytes)
1039 {
1040     // XXX: use random.org...?
1041     if (@file_exists('/dev/urandom')) {
1042         return common_urandom($bytes);
1043     } else { // FIXME: this is probably not good enough
1044         return common_mtrand($bytes);
1045     }
1046 }
1047
1048 function common_urandom($bytes)
1049 {
1050     $h = fopen('/dev/urandom', 'rb');
1051     // should not block
1052     $src = fread($h, $bytes);
1053     fclose($h);
1054     $enc = '';
1055     for ($i = 0; $i < $bytes; $i++) {
1056         $enc .= sprintf("%02x", (ord($src[$i])));
1057     }
1058     return $enc;
1059 }
1060
1061 function common_mtrand($bytes)
1062 {
1063     $enc = '';
1064     for ($i = 0; $i < $bytes; $i++) {
1065         $enc .= sprintf("%02x", mt_rand(0, 255));
1066     }
1067     return $enc;
1068 }
1069
1070 function common_set_returnto($url)
1071 {
1072     common_ensure_session();
1073     $_SESSION['returnto'] = $url;
1074 }
1075
1076 function common_get_returnto()
1077 {
1078     common_ensure_session();
1079     return $_SESSION['returnto'];
1080 }
1081
1082 function common_timestamp()
1083 {
1084     return date('YmdHis');
1085 }
1086
1087 function common_ensure_syslog()
1088 {
1089     static $initialized = false;
1090     if (!$initialized) {
1091         openlog(common_config('syslog', 'appname'), 0, LOG_USER);
1092         $initialized = true;
1093     }
1094 }
1095
1096 function common_log($priority, $msg, $filename=null)
1097 {
1098     $logfile = common_config('site', 'logfile');
1099     if ($logfile) {
1100         $log = fopen($logfile, "a");
1101         if ($log) {
1102             static $syslog_priorities = array('LOG_EMERG', 'LOG_ALERT', 'LOG_CRIT', 'LOG_ERR',
1103                                               'LOG_WARNING', 'LOG_NOTICE', 'LOG_INFO', 'LOG_DEBUG');
1104             $output = date('Y-m-d H:i:s') . ' ' . $syslog_priorities[$priority] . ': ' . $msg . "\n";
1105             fwrite($log, $output);
1106             fclose($log);
1107         }
1108     } else {
1109         common_ensure_syslog();
1110         syslog($priority, $msg);
1111     }
1112 }
1113
1114 function common_debug($msg, $filename=null)
1115 {
1116     if ($filename) {
1117         common_log(LOG_DEBUG, basename($filename).' - '.$msg);
1118     } else {
1119         common_log(LOG_DEBUG, $msg);
1120     }
1121 }
1122
1123 function common_log_db_error(&$object, $verb, $filename=null)
1124 {
1125     $objstr = common_log_objstring($object);
1126     $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
1127     common_log(LOG_ERR, $last_error->message . '(' . $verb . ' on ' . $objstr . ')', $filename);
1128 }
1129
1130 function common_log_objstring(&$object)
1131 {
1132     if (is_null($object)) {
1133         return "null";
1134     }
1135     $arr = $object->toArray();
1136     $fields = array();
1137     foreach ($arr as $k => $v) {
1138         $fields[] = "$k='$v'";
1139     }
1140     $objstring = $object->tableName() . '[' . implode(',', $fields) . ']';
1141     return $objstring;
1142 }
1143
1144 function common_valid_http_url($url)
1145 {
1146     return Validate::uri($url, array('allowed_schemes' => array('http', 'https')));
1147 }
1148
1149 function common_valid_tag($tag)
1150 {
1151     if (preg_match('/^tag:(.*?),(\d{4}(-\d{2}(-\d{2})?)?):(.*)$/', $tag, $matches)) {
1152         return (Validate::email($matches[1]) ||
1153                 preg_match('/^([\w-\.]+)$/', $matches[1]));
1154     }
1155     return false;
1156 }
1157
1158 /* Following functions are copied from MediaWiki GlobalFunctions.php
1159  * and written by Evan Prodromou. */
1160
1161 function common_accept_to_prefs($accept, $def = '*/*')
1162 {
1163     // No arg means accept anything (per HTTP spec)
1164     if(!$accept) {
1165         return array($def => 1);
1166     }
1167
1168     $prefs = array();
1169
1170     $parts = explode(',', $accept);
1171
1172     foreach($parts as $part) {
1173         // FIXME: doesn't deal with params like 'text/html; level=1'
1174         @list($value, $qpart) = explode(';', trim($part));
1175         $match = array();
1176         if(!isset($qpart)) {
1177             $prefs[$value] = 1;
1178         } elseif(preg_match('/q\s*=\s*(\d*\.\d+)/', $qpart, $match)) {
1179             $prefs[$value] = $match[1];
1180         }
1181     }
1182
1183     return $prefs;
1184 }
1185
1186 function common_mime_type_match($type, $avail)
1187 {
1188     if(array_key_exists($type, $avail)) {
1189         return $type;
1190     } else {
1191         $parts = explode('/', $type);
1192         if(array_key_exists($parts[0] . '/*', $avail)) {
1193             return $parts[0] . '/*';
1194         } elseif(array_key_exists('*/*', $avail)) {
1195             return '*/*';
1196         } else {
1197             return null;
1198         }
1199     }
1200 }
1201
1202 function common_negotiate_type($cprefs, $sprefs)
1203 {
1204     $combine = array();
1205
1206     foreach(array_keys($sprefs) as $type) {
1207         $parts = explode('/', $type);
1208         if($parts[1] != '*') {
1209             $ckey = common_mime_type_match($type, $cprefs);
1210             if($ckey) {
1211                 $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
1212             }
1213         }
1214     }
1215
1216     foreach(array_keys($cprefs) as $type) {
1217         $parts = explode('/', $type);
1218         if($parts[1] != '*' && !array_key_exists($type, $sprefs)) {
1219             $skey = common_mime_type_match($type, $sprefs);
1220             if($skey) {
1221                 $combine[$type] = $sprefs[$skey] * $cprefs[$type];
1222             }
1223         }
1224     }
1225
1226     $bestq = 0;
1227     $besttype = 'text/html';
1228
1229     foreach(array_keys($combine) as $type) {
1230         if($combine[$type] > $bestq) {
1231             $besttype = $type;
1232             $bestq = $combine[$type];
1233         }
1234     }
1235
1236     if ('text/html' === $besttype) {
1237         return "text/html; charset=utf-8";
1238     }
1239     return $besttype;
1240 }
1241
1242 function common_config($main, $sub)
1243 {
1244     global $config;
1245     return isset($config[$main][$sub]) ? $config[$main][$sub] : false;
1246 }
1247
1248 function common_copy_args($from)
1249 {
1250     $to = array();
1251     $strip = get_magic_quotes_gpc();
1252     foreach ($from as $k => $v) {
1253         $to[$k] = ($strip) ? stripslashes($v) : $v;
1254     }
1255     return $to;
1256 }
1257
1258 // Neutralise the evil effects of magic_quotes_gpc in the current request.
1259 // This is used before handing a request off to OAuthRequest::from_request.
1260 function common_remove_magic_from_request()
1261 {
1262     if(get_magic_quotes_gpc()) {
1263         $_POST=array_map('stripslashes',$_POST);
1264         $_GET=array_map('stripslashes',$_GET);
1265     }
1266 }
1267
1268 function common_user_uri(&$user)
1269 {
1270     return common_local_url('userbyid', array('id' => $user->id));
1271 }
1272
1273 function common_notice_uri(&$notice)
1274 {
1275     return common_local_url('shownotice',
1276                             array('notice' => $notice->id));
1277 }
1278
1279 // 36 alphanums - lookalikes (0, O, 1, I) = 32 chars = 5 bits
1280
1281 function common_confirmation_code($bits)
1282 {
1283     // 36 alphanums - lookalikes (0, O, 1, I) = 32 chars = 5 bits
1284     static $codechars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
1285     $chars = ceil($bits/5);
1286     $code = '';
1287     for ($i = 0; $i < $chars; $i++) {
1288         // XXX: convert to string and back
1289         $num = hexdec(common_good_rand(1));
1290         // XXX: randomness is too precious to throw away almost
1291         // 40% of the bits we get!
1292         $code .= $codechars[$num%32];
1293     }
1294     return $code;
1295 }
1296
1297 // convert markup to HTML
1298
1299 function common_markup_to_html($c)
1300 {
1301     $c = preg_replace('/%%action.(\w+)%%/e', "common_local_url('\\1')", $c);
1302     $c = preg_replace('/%%doc.(\w+)%%/e', "common_local_url('doc', array('title'=>'\\1'))", $c);
1303     $c = preg_replace('/%%(\w+).(\w+)%%/e', 'common_config(\'\\1\', \'\\2\')', $c);
1304     return Markdown($c);
1305 }
1306
1307 function common_profile_uri($profile)
1308 {
1309     if (!$profile) {
1310         return null;
1311     }
1312     $user = User::staticGet($profile->id);
1313     if ($user) {
1314         return $user->uri;
1315     }
1316
1317     $remote = Remote_profile::staticGet($profile->id);
1318     if ($remote) {
1319         return $remote->uri;
1320     }
1321     // XXX: this is a very bad profile!
1322     return null;
1323 }
1324
1325 function common_canonical_sms($sms)
1326 {
1327     // strip non-digits
1328     preg_replace('/\D/', '', $sms);
1329     return $sms;
1330 }
1331
1332 function common_error_handler($errno, $errstr, $errfile, $errline, $errcontext)
1333 {
1334     switch ($errno) {
1335      case E_USER_ERROR:
1336         common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline)");
1337         exit(1);
1338         break;
1339
1340      case E_USER_WARNING:
1341         common_log(LOG_WARNING, "[$errno] $errstr ($errfile:$errline)");
1342         break;
1343
1344      case E_USER_NOTICE:
1345         common_log(LOG_NOTICE, "[$errno] $errstr ($errfile:$errline)");
1346         break;
1347     }
1348
1349     // FIXME: show error page if we're on the Web
1350     /* Don't execute PHP internal error handler */
1351     return true;
1352 }
1353
1354 function common_session_token()
1355 {
1356     common_ensure_session();
1357     if (!array_key_exists('token', $_SESSION)) {
1358         $_SESSION['token'] = common_good_rand(64);
1359     }
1360     return $_SESSION['token'];
1361 }
1362
1363 function common_cache_key($extra)
1364 {
1365     $base_key = common_config('memcached', 'base');
1366
1367     if (empty($base_key)) {
1368         $base_key = common_keyize(common_config('site', 'name'));
1369     }
1370
1371     return 'laconica:' . $base_key . ':' . $extra;
1372 }
1373
1374 function common_keyize($str)
1375 {
1376     $str = strtolower($str);
1377     $str = preg_replace('/\s/', '_', $str);
1378     return $str;
1379 }
1380
1381 function common_memcache()
1382 {
1383     static $cache = null;
1384     if (!common_config('memcached', 'enabled')) {
1385         return null;
1386     } else {
1387         if (!$cache) {
1388             $cache = new Memcache();
1389             $servers = common_config('memcached', 'server');
1390             if (is_array($servers)) {
1391                 foreach($servers as $server) {
1392                     $cache->addServer($server);
1393                 }
1394             } else {
1395                 $cache->addServer($servers);
1396             }
1397         }
1398         return $cache;
1399     }
1400 }
1401
1402 function common_compatible_license($from, $to)
1403 {
1404     // XXX: better compatibility check needed here!
1405     return ($from == $to);
1406 }
1407
1408 /**
1409  * returns a quoted table name, if required according to config
1410  */
1411 function common_database_tablename($tablename)
1412 {
1413
1414   if(common_config('db','quote_identifiers')) {
1415       $tablename = '"'. $tablename .'"';
1416   }
1417   //table prefixes could be added here later
1418   return $tablename;
1419 }
1420
1421 function common_shorten_url($long_url)
1422 {
1423     $user = common_current_user();
1424     if (empty($user)) {
1425         // common current user does not find a user when called from the XMPP daemon
1426         // therefore we'll set one here fix, so that XMPP given URLs may be shortened
1427         $svc = 'ur1.ca';
1428     } else {
1429         $svc = $user->urlshorteningservice;
1430     }
1431
1432     $curlh = curl_init();
1433     curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
1434     curl_setopt($curlh, CURLOPT_USERAGENT, 'Laconica');
1435     curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
1436
1437     switch($svc) {
1438      case 'ur1.ca':
1439         require_once INSTALLDIR.'/lib/Shorturl_api.php';
1440         $short_url_service = new LilUrl;
1441         $short_url = $short_url_service->shorten($long_url);
1442         break;
1443
1444      case '2tu.us':
1445         $short_url_service = new TightUrl;
1446         require_once INSTALLDIR.'/lib/Shorturl_api.php';
1447         $short_url = $short_url_service->shorten($long_url);
1448         break;
1449
1450      case 'ptiturl.com':
1451         require_once INSTALLDIR.'/lib/Shorturl_api.php';
1452         $short_url_service = new PtitUrl;
1453         $short_url = $short_url_service->shorten($long_url);
1454         break;
1455
1456      case 'bit.ly':
1457         curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($long_url));
1458         $short_url = current(json_decode(curl_exec($curlh))->results)->hashUrl;
1459         break;
1460
1461      case 'is.gd':
1462         curl_setopt($curlh, CURLOPT_URL, 'http://is.gd/api.php?longurl='.urlencode($long_url));
1463         $short_url = curl_exec($curlh);
1464         break;
1465      case 'snipr.com':
1466         curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($long_url));
1467         $short_url = curl_exec($curlh);
1468         break;
1469      case 'metamark.net':
1470         curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($long_url));
1471         $short_url = curl_exec($curlh);
1472         break;
1473      case 'tinyurl.com':
1474         curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($long_url));
1475         $short_url = curl_exec($curlh);
1476         break;
1477      default:
1478         $short_url = false;
1479     }
1480
1481     curl_close($curlh);
1482
1483     return $short_url;
1484 }