We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

util.php 44.2 KB
Newer Older
1
<?php
Evan Prodromou's avatar
Evan Prodromou committed
2
/*
3
 * StatusNet - the distributed open-source microblogging tool
4
 * Copyright (C) 2008, 2009, StatusNet, Inc.
Evan Prodromou's avatar
Evan Prodromou committed
5
 *
6 7 8 9
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
Evan Prodromou's avatar
Evan Prodromou committed
10
 *
11 12 13 14
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
Evan Prodromou's avatar
Evan Prodromou committed
15
 *
16 17 18 19
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

20
/* XXX: break up into separate modules (HTTP, user, files) */
21

22
// Show a server error
23

24 25
function common_server_error($msg, $code=500)
{
26 27
    $err = new ServerErrorAction($msg, $code);
    $err->showPage();
28 29
}

30
// Show a user error
31 32
function common_user_error($msg, $code=400)
{
33 34
    $err = new ClientErrorAction($msg, $code);
    $err->showPage();
35 36
}

37 38
function common_init_locale($language=null)
{
39 40 41 42 43 44
    if(!$language) {
        $language = common_language();
    }
    putenv('LANGUAGE='.$language);
    putenv('LANG='.$language);
    return setlocale(LC_ALL, $language . ".utf8",
45 46 47 48
                     $language . ".UTF8",
                     $language . ".utf-8",
                     $language . ".UTF-8",
                     $language);
49 50
}

51 52
function common_init_language()
{
53
    mb_internal_encoding('UTF-8');
54 55 56 57 58 59

    // gettext seems very picky... We first need to setlocale()
    // to a locale which _does_ exist on the system, and _then_
    // we can set in another locale that may not be set up
    // (say, ga_ES for Galego/Galician) it seems to take it.
    common_init_locale("en_US");
60

61 62
    // Note that this setlocale() call may "fail" but this is harmless;
    // gettext will still select the right language.
63 64
    $language = common_language();
    $locale_set = common_init_locale($language);
65
    
66
    setlocale(LC_CTYPE, 'C');
Siebrand Mazeland's avatar
Siebrand Mazeland committed
67
    // So we do not have to make people install the gettext locales
68 69
    $path = common_config('site','locale_path');
    bindtextdomain("statusnet", $path);
70 71
    bind_textdomain_codeset("statusnet", "UTF-8");
    textdomain("statusnet");
72 73
}

74 75
function common_timezone()
{
76 77 78 79 80 81
    if (common_logged_in()) {
        $user = common_current_user();
        if ($user->timezone) {
            return $user->timezone;
        }
    }
82

83
    return common_config('site', 'timezone');
84 85
}

86 87
function common_language()
{
88

89 90
    // If there is a user logged in and they've set a language preference
    // then return that one...
91
    if (_have_config() && common_logged_in()) {
92 93
        $user = common_current_user();
        $user_language = $user->language;
94 95 96 97 98 99 100 101 102 103

        if ($user->language) {
            // Validate -- we don't want to end up with a bogus code
            // left over from some old junk.
            foreach (common_config('site', 'languages') as $code => $info) {
                if ($info['lang'] == $user_language) {
                    return $user_language;
                }
            }
        }
104
    }
105

106 107 108 109 110 111 112 113
    // Otherwise, find the best match for the languages requested by the
    // user's browser...
    $httplang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : null;
    if (!empty($httplang)) {
        $language = client_prefered_language($httplang);
        if ($language)
          return $language;
    }
114

115 116
    // Finally, if none of the above worked, use the site's default...
    return common_config('site', 'language');
117
}
118
// salted, hashed passwords are stored in the DB
119

120 121
function common_munge_password($password, $id)
{
122
    return md5($password . $id);
123 124
}

125
// check if a username exists and has matching password
126

127 128
function common_check_user($nickname, $password)
{
129 130 131 132 133 134
    $authenticatedUser = false;

    if (Event::handle('StartCheckPassword', array($nickname, $password, &$authenticatedUser))) {
        $user = User::staticGet('nickname', $nickname);
        if (!empty($user)) {
            if (!empty($password)) { // never allow login with blank password
Craig Andrews's avatar
Craig Andrews committed
135 136 137
                if (0 == strcmp(common_munge_password($password, $user->id),
                                $user->password)) {
                    //internal checking passed
138
                    $authenticatedUser =& $user;
Craig Andrews's avatar
Craig Andrews committed
139
                }
Craig Andrews's avatar
Craig Andrews committed
140 141
            }
        }
142
        Event::handle('EndCheckPassword', array($nickname, $password, $authenticatedUser));
143
    }
144 145

    return $authenticatedUser;
146 147 148
}

// is the current user logged in?
149 150
function common_logged_in()
{
151
    return (!is_null(common_current_user()));
152 153
}

154 155
function common_have_session()
{
156
    return (0 != strcmp(session_id(), ''));
157 158
}

159 160
function common_ensure_session()
{
Evan Prodromou's avatar
Evan Prodromou committed
161
    $c = null;
162
    if (array_key_exists(session_name(), $_COOKIE)) {
Evan Prodromou's avatar
Evan Prodromou committed
163 164
        $c = $_COOKIE[session_name()];
    }
165
    if (!common_have_session()) {
166 167 168
        if (common_config('sessions', 'handle')) {
            Session::setSaveHandler();
        }
169
        @session_start();
Evan Prodromou's avatar
Evan Prodromou committed
170 171 172 173 174 175 176
        if (!isset($_SESSION['started'])) {
            $_SESSION['started'] = time();
            if (!empty($c)) {
                common_log(LOG_WARNING, 'Session cookie "' . $_COOKIE[session_name()] . '" ' .
                           ' is set but started value is null');
            }
        }
177
    }
178 179
}

180 181 182 183
// Three kinds of arguments:
// 1) a user object
// 2) a nickname
// 3) null to clear
184

185
// Initialize to false; set to null if none found
186 187 188

$_cur = false;

189 190
function common_set_user($user)
{
191 192 193

    global $_cur;

194 195 196 197 198 199 200 201 202 203 204 205
    if (is_null($user) && common_have_session()) {
        $_cur = null;
        unset($_SESSION['userid']);
        return true;
    } else if (is_string($user)) {
        $nickname = $user;
        $user = User::staticGet('nickname', $nickname);
    } else if (!($user instanceof User)) {
        return false;
    }

    if ($user) {
Craig Andrews's avatar
Craig Andrews committed
206 207 208 209 210 211 212 213 214
        if (Event::handle('StartSetUser', array(&$user))) {
            if($user){
                common_ensure_session();
                $_SESSION['userid'] = $user->id;
                $_cur = $user;
                Event::handle('EndSetUser', array($user));
                return $_cur;
            }
        }
215 216
    }
    return false;
217 218
}

219 220
function common_set_cookie($key, $value, $expiration=0)
{
221 222
    $path = common_config('site', 'path');
    $server = common_config('site', 'server');
223

224 225 226 227 228 229 230 231 232 233
    if ($path && ($path != '/')) {
        $cookiepath = '/' . $path . '/';
    } else {
        $cookiepath = '/';
    }
    return setcookie($key,
                     $value,
                     $expiration,
                     $cookiepath,
                     $server);
234 235 236
}

define('REMEMBERME', 'rememberme');
237
define('REMEMBERME_EXPIRY', 30 * 24 * 60 * 60); // 30 days
238

239 240
function common_rememberme($user=null)
{
241 242 243 244 245 246 247
    if (!$user) {
        $user = common_current_user();
        if (!$user) {
            common_debug('No current user to remember', __FILE__);
            return false;
        }
    }
248

249
    $rm = new Remember_me();
250

251 252
    $rm->code = common_good_rand(16);
    $rm->user_id = $user->id;
253

254
    // Wrap the insert in some good ol' fashioned transaction code
255 256 257

    $rm->query('BEGIN');

258
    $result = $rm->insert();
259

260 261 262 263
    if (!$result) {
        common_log_db_error($rm, 'INSERT', __FILE__);
        common_debug('Error adding rememberme record for ' . $user->nickname, __FILE__);
        return false;
264 265
    }

266 267
    $rm->query('COMMIT');

268
    common_debug('Inserted rememberme record (' . $rm->code . ', ' . $rm->user_id . '); result = ' . $result . '.', __FILE__);
269 270 271

    $cookieval = $rm->user_id . ':' . $rm->code;

272
    common_log(LOG_INFO, 'adding rememberme cookie "' . $cookieval . '" for ' . $user->nickname);
273

274
    common_set_cookie(REMEMBERME, $cookieval, time() + REMEMBERME_EXPIRY);
275

276
    return true;
277 278
}

279 280
function common_remembered_user()
{
281

282
    $user = null;
283

284
    $packed = isset($_COOKIE[REMEMBERME]) ? $_COOKIE[REMEMBERME] : null;
285

286 287
    if (!$packed) {
        return null;
288 289 290 291 292
    }

    list($id, $code) = explode(':', $packed);

    if (!$id || !$code) {
293
        common_log(LOG_WARNING, 'Malformed rememberme cookie: ' . $packed);
294
        common_forgetme();
295
        return null;
296 297 298 299 300
    }

    $rm = Remember_me::staticGet($code);

    if (!$rm) {
301
        common_log(LOG_WARNING, 'No such remember code: ' . $code);
302
        common_forgetme();
303
        return null;
304 305 306
    }

    if ($rm->user_id != $id) {
307
        common_log(LOG_WARNING, 'Rememberme code for wrong user: ' . $rm->user_id . ' != ' . $id);
308
        common_forgetme();
309
        return null;
310 311 312 313 314
    }

    $user = User::staticGet($rm->user_id);

    if (!$user) {
315
        common_log(LOG_WARNING, 'No such user for rememberme: ' . $rm->user_id);
316
        common_forgetme();
317
        return null;
318 319
    }

320
    // successful!
321 322 323 324
    $result = $rm->delete();

    if (!$result) {
        common_log_db_error($rm, 'DELETE', __FILE__);
325
        common_log(LOG_WARNING, 'Could not delete rememberme: ' . $code);
326
        common_forgetme();
327
        return null;
328 329 330 331
    }

    common_log(LOG_INFO, 'logging in ' . $user->nickname . ' using rememberme code ' . $rm->code);

332
    common_set_user($user);
333 334
    common_real_login(false);

335 336
    // We issue a new cookie, so they can log in
    // automatically again after this session
337 338 339

    common_rememberme($user);

340
    return $user;
341 342
}

343
// must be called with a valid user!
344

345 346
function common_forgetme()
{
347
    common_set_cookie(REMEMBERME, '', 0);
348 349
}

350
// who is the current user?
351 352
function common_current_user()
{
353 354
    global $_cur;

355 356 357 358
    if (!_have_config()) {
        return null;
    }

359 360 361 362 363 364
    if ($_cur === false) {

        if (isset($_REQUEST[session_name()]) || (isset($_SESSION['userid']) && $_SESSION['userid'])) {
            common_ensure_session();
            $id = isset($_SESSION['userid']) ? $_SESSION['userid'] : false;
            if ($id) {
365 366 367 368 369
                $user = User::staticGet($id);
                if ($user) {
                	$_cur = $user;
                	return $_cur;
                }
370 371 372
            }
        }

373
        // that didn't work; try to remember; will init $_cur to null on failure
374 375 376 377 378
        $_cur = common_remembered_user();

        if ($_cur) {
            common_debug("Got User " . $_cur->nickname);
            common_debug("Faking session on remembered user");
379
            // XXX: Is this necessary?
380 381 382 383
            $_SESSION['userid'] = $_cur->id;
        }
    }

384
    return $_cur;
385 386
}

387
// Logins that are 'remembered' aren't 'real' -- they're subject to
388
// cookie-stealing. So, we don't let them do certain things. New reg,
389
// OpenID, and password logins _are_ real.
390

391 392
function common_real_login($real=true)
{
393 394
    common_ensure_session();
    $_SESSION['real_login'] = $real;
395 396
}

397 398
function common_is_real_login()
{
399
    return common_logged_in() && $_SESSION['real_login'];
400 401
}

402
// get canonical version of nickname for comparison
403 404
function common_canonical_nickname($nickname)
{
405 406
    // XXX: UTF-8 canonicalization (like combining chars)
    return strtolower($nickname);
407 408
}

409
// get canonical version of email for comparison
410 411
function common_canonical_email($email)
{
412 413 414
    // XXX: canonicalize UTF-8
    // XXX: lcase the domain part
    return $email;
415 416
}

417 418
function common_render_content($text, $notice)
{
419 420
    $r = common_render_text($text);
    $id = $notice->profile_id;
421
    $r = preg_replace('/(^|\s+)@(['.NICKNAME_FMT.']{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r);
422
    $r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r);
423 424
    $r = preg_replace('/(^|[\s\.\,\:\;]+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r);
    $r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
425
    return $r;
426 427
}

428 429
function common_render_text($text)
{
430
    $r = htmlspecialchars($text);
431

432
    $r = preg_replace('/[\x{0}-\x{8}\x{b}-\x{c}\x{e}-\x{19}]/', '', $r);
433
    $r = common_replace_urls_callback($r, 'common_linkify');
Craig Andrews's avatar
Craig Andrews committed
434
    $r = preg_replace('/(^|\&quot\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/e', "'\\1#'.common_tag_link('\\2')", $r);
435 436
    // XXX: machine tags
    return $r;
Evan Prodromou's avatar
Evan Prodromou committed
437 438
}

439
function common_replace_urls_callback($text, $callback, $notice_id = null) {
440
    // Start off with a regex
441
    $regex = '#'.
442
    '(?:^|[\s\<\>\(\)\[\]\{\}\\\'\\\";]+)(?![\@\!\#])'.
443
    '('.
444
        '(?:'.
445 446
            '(?:'. //Known protocols
                '(?:'.
447
                    '(?:(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|irc)://)'.
448
                    '|'.
449 450
                    '(?:(?:mailto|aim|tel|xmpp):)'.
                ')'.
451
                '(?:[\pN\pL\-\_\+\%\~]+(?::[\pN\pL\-\_\+\%\~]+)?\@)?'. //user:pass@
452 453 454 455 456 457
                '(?:'.
                    '(?:'.
                        '\[[\pN\pL\-\_\:\.]+(?<![\.\:])\]'. //[dns]
                    ')|(?:'.
                        '[\pN\pL\-\_\:\.]+(?<![\.\:])'. //dns
                    ')'.
458
                ')'.
459 460
            ')'.
            '|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'. //IPv4
461
            '|(?:'. //IPv6
462
                '\[?(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:(?:[0-9A-Fa-f]{1,4})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?::|(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(?::[0-9A-Fa-f]{1,4})))|(?:(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){4}(?::[0-9A-Fa-f]{1,4}){0,1}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){3}(?::[0-9A-Fa-f]{1,4}){0,2}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){2}(?::[0-9A-Fa-f]{1,4}){0,3}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:)(?::[0-9A-Fa-f]{1,4}){0,4}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?::(?::[0-9A-Fa-f]{1,4}){0,5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))\]?(?<!:)'.
463
            ')|(?:'. //DNS
464
                '(?:[\pN\pL\-\_\+\%\~]+(?:\:[\pN\pL\-\_\+\%\~]+)?\@)?'. //user:pass@
465 466
                '[\pN\pL\-\_]+(?:\.[\pN\pL\-\_]+)*\.'.
                //tld list from http://data.iana.org/TLD/tlds-alpha-by-domain.txt, also added local, loc, and onion
467
                '(?: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|XN--0ZWM56D|测试|XN--11B5BS3A9AJ6G|परीक्षा|XN--80AKHBYKNJ4F|испытание|XN--9T4B11YI5A|테스트|XN--DEBA0AD|טעסט|XN--G6W251D|測試|XN--HGBK6AJ7F53BBA|آزمایشی|XN--HLCJ6AYA9ESC7A|பரிட்சை|XN--JXALPDLP|δοκιμή|XN--KGBECHTV|إختبار|XN--ZCKZAH|テスト|YE|YT|YU|ZA|ZM|ZW|local|loc|onion)'.
468
            ')(?![\pN\pL\-\_])'.
469
        ')'.
470
        '(?:'.
471
            '(?:\:\d+)?'. //:port
472 473 474
            '(?:/[\pN\pL$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'@]*)?'. // /path
            '(?:\?[\pN\pL\$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'@\/]*)?'. // ?query string
            '(?:\#[\pN\pL$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\@/\?\#]*)?'. // #fragment
475
        ')(?<![\?\.\,\#\,])'.
476
    ')'.
477
    '#ixu';
478
    //preg_match_all($regex,$text,$matches);
479
    //print_r($matches);
Evan Prodromou's avatar
Evan Prodromou committed
480
    return preg_replace_callback($regex, curry('callback_helper',$callback,$notice_id) ,$text);
481
}
482

483
function callback_helper($matches, $callback, $notice_id) {
484
    $url=$matches[1];
485 486
    $left = strpos($matches[0],$url);
    $right = $left+strlen($url);
487

488 489 490 491 492 493 494 495 496 497 498 499
    $groupSymbolSets=array(
        array(
            'left'=>'(',
            'right'=>')'
        ),
        array(
            'left'=>'[',
            'right'=>']'
        ),
        array(
            'left'=>'{',
            'right'=>'}'
500 501 502 503
        ),
        array(
            'left'=>'<',
            'right'=>'>'
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
        )
    );
    $cannotEndWith=array('.','?',',','#');
    $original_url=$url;
    do{
        $original_url=$url;
        foreach($groupSymbolSets as $groupSymbolSet){
            if(substr($url,-1)==$groupSymbolSet['right']){
                $group_left_count = substr_count($url,$groupSymbolSet['left']);
                $group_right_count = substr_count($url,$groupSymbolSet['right']);
                if($group_left_count<$group_right_count){
                    $right-=1;
                    $url=substr($url,0,-1);
                }
            }
        }
        if(in_array(substr($url,-1),$cannotEndWith)){
            $right-=1;
            $url=substr($url,0,-1);
        }
    }while($original_url!=$url);
525

526
    if(empty($notice_id)){
527
        $result = call_user_func_array($callback, array($url));
528
    }else{
Craig Andrews's avatar
Craig Andrews committed
529
        $result = call_user_func_array($callback, array(array($url,$notice_id)) );
530
    }
531
    return substr($matches[0],0,$left) . $result . substr($matches[0],$right);
532
}
533

534 535 536 537 538 539
function curry($fn) {
    //TODO switch to a PHP 5.3 function closure based approach if PHP 5.3 is used
    $args = func_get_args();
    array_shift($args);
    $id = uniqid('_partial');
    $GLOBALS[$id] = array($fn, $args);
540 541 542 543 544 545 546
    return create_function('',
                           '$args = func_get_args(); '.
                           'return call_user_func_array('.
                           '$GLOBALS["'.$id.'"][0],'.
                           'array_merge('.
                           '$args,'.
                           '$GLOBALS["'.$id.'"][1]));');
547 548 549
}

function common_linkify($url) {
Evan Prodromou's avatar
Evan Prodromou committed
550 551 552
    // It comes in special'd, so we unspecial it before passing to the stringifying
    // functions
    $url = htmlspecialchars_decode($url);
553

554
   if(strpos($url, '@') !== false && strpos($url, ':') === false) {
555
       //url is an email address without the mailto: protocol
556 557 558
       $canon = "mailto:$url";
       $longurl = "mailto:$url";
   }else{
559

560
        $canon = File_redirection::_canonUrl($url);
561

562 563 564 565 566 567
        $longurl_data = File_redirection::where($canon);
        if (is_array($longurl_data)) {
            $longurl = $longurl_data['url'];
        } elseif (is_string($longurl_data)) {
            $longurl = $longurl_data;
        } else {
568
            throw new ServerException("Can't linkify url '$url'");
569
        }
570
    }
571
    $attrs = array('href' => $canon, 'title' => $longurl, 'rel' => 'external');
572

573 574 575 576
    $is_attachment = false;
    $attachment_id = null;
    $has_thumb = false;

577
    // Check to see whether this is a known "attachment" URL.
578

579
    $f = File::staticGet('url', $longurl);
580

581 582 583
    if (empty($f)) {
        // XXX: this writes to the database. :<
        $f = File::processNew($longurl);
584 585
    }

586
    if (!empty($f)) {
587
        if ($f->isEnclosure()) {
588
            $is_attachment = true;
589
            $attachment_id = $f->id;
590
        } else {
591
            $foe = File_oembed::staticGet('file_id', $f->id);
592
            if (!empty($foe)) {
593
                // if it has OEmbed info, it's an attachment, too
594
                $is_attachment = true;
595
                $attachment_id = $f->id;
596

597
                $thumb = File_thumbnail::staticGet('file_id', $f->id);
598 599 600 601
                if (!empty($thumb)) {
                    $has_thumb = true;
                }
            }
602 603 604 605 606 607 608
        }
    }

    // Add clippy
    if ($is_attachment) {
        $attrs['class'] = 'attachment';
        if ($has_thumb) {
609 610
            $attrs['class'] = 'attachment thumbnail';
        }
611
        $attrs['id'] = "attachment-{$attachment_id}";
612
    }
613

614
    return XMLStringer::estring('a', $attrs, $url);
615 616
}

617 618
function common_shorten_links($text)
{
619 620
    $maxLength = Notice::maxContent();
    if ($maxLength == 0 || mb_strlen($text) <= $maxLength) return $text;
621
    return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
622 623
}

624 625
function common_xml_safe_str($str)
{
626 627
    // Neutralize control codes and surrogates
	return preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str);
628 629
}

630 631
function common_tag_link($tag)
{
632 633
    $canonical = common_canonical_tag($tag);
    $url = common_local_url('tag', array('tag' => $canonical));
634 635 636 637 638
    $xs = new XMLStringer();
    $xs->elementStart('span', 'tag');
    $xs->element('a', array('href' => $url,
                            'rel' => 'tag'),
                 $tag);
639
    $xs->elementEnd('span');
640
    return $xs->getString();
641 642
}

643 644
function common_canonical_tag($tag)
{
645 646
  $tag = mb_convert_case($tag, MB_CASE_LOWER, "UTF-8");
  return str_replace(array('-', '_', '.'), '', $tag);
Mike Cochrane's avatar
Mike Cochrane committed
647 648
}

649 650
function common_valid_profile_tag($str)
{
651
    return preg_match('/^[A-Za-z0-9_\-\.]{1,64}$/', $str);
652 653
}

654 655
function common_at_link($sender_id, $nickname)
{
656 657 658
    $sender = Profile::staticGet($sender_id);
    $recipient = common_relative_profile($sender, common_canonical_nickname($nickname));
    if ($recipient) {
659 660 661 662 663 664
        $user = User::staticGet('id', $recipient->id);
        if ($user) {
            $url = common_local_url('userbyid', array('id' => $user->id));
        } else {
            $url = $recipient->profileurl;
        }
665
        $xs = new XMLStringer(false);
666 667 668 669 670
        $attrs = array('href' => $url,
                       'class' => 'url');
        if (!empty($recipient->fullname)) {
            $attrs['title'] = $recipient->fullname . ' (' . $recipient->nickname . ')';
        }
671
        $xs->elementStart('span', 'vcard');
672
        $xs->elementStart('a', $attrs);
673 674 675 676
        $xs->element('span', 'fn nickname', $nickname);
        $xs->elementEnd('a');
        $xs->elementEnd('span');
        return $xs->getString();
677 678 679
    } else {
        return $nickname;
    }
680 681
}

682 683 684
function common_group_link($sender_id, $nickname)
{
    $sender = Profile::staticGet($sender_id);
685
    $group = User_group::getForNickname($nickname);
686
    if ($group && $sender->isMember($group)) {
687 688 689 690 691
        $attrs = array('href' => $group->permalink(),
                       'class' => 'url');
        if (!empty($group->fullname)) {
            $attrs['title'] = $group->fullname . ' (' . $group->nickname . ')';
        }
692 693
        $xs = new XMLStringer();
        $xs->elementStart('span', 'vcard');
694
        $xs->elementStart('a', $attrs);
695 696 697 698
        $xs->element('span', 'fn nickname', $nickname);
        $xs->elementEnd('a');
        $xs->elementEnd('span');
        return $xs->getString();
699 700 701 702 703
    } else {
        return $nickname;
    }
}

704 705
function common_at_hash_link($sender_id, $tag)
{
706 707 708 709 710 711 712 713 714
    $user = User::staticGet($sender_id);
    if (!$user) {
        return $tag;
    }
    $tagged = Profile_tag::getTagged($user->id, common_canonical_tag($tag));
    if ($tagged) {
        $url = common_local_url('subscriptions',
                                array('nickname' => $user->nickname,
                                      'tag' => $tag));
715 716 717 718 719 720 721
        $xs = new XMLStringer();
        $xs->elementStart('span', 'tag');
        $xs->element('a', array('href' => $url,
                                'rel' => $tag),
                     $tag);
        $xs->elementEnd('span');
        return $xs->getString();
722 723 724 725 726
    } else {
        return $tag;
    }
}

727 728
function common_relative_profile($sender, $nickname, $dt=null)
{
729 730 731 732
    // Try to find profiles this profile is subscribed to that have this nickname
    $recipient = new Profile();
    // XXX: use a join instead of a subquery
    $recipient->whereAdd('EXISTS (SELECT subscribed from subscription where subscriber = '.$sender->id.' and subscribed = id)', 'AND');
733
    $recipient->whereAdd("nickname = '" . trim($nickname) . "'", 'AND');
Evan Prodromou's avatar
TRUE  
Evan Prodromou committed
734
    if ($recipient->find(true)) {
735 736 737 738 739 740 741 742
        // XXX: should probably differentiate between profiles with
        // the same name by date of most recent update
        return $recipient;
    }
    // Try to find profiles that listen to this profile and that have this nickname
    $recipient = new Profile();
    // XXX: use a join instead of a subquery
    $recipient->whereAdd('EXISTS (SELECT subscriber from subscription where subscribed = '.$sender->id.' and subscriber = id)', 'AND');
743
    $recipient->whereAdd("nickname = '" . trim($nickname) . "'", 'AND');
Evan Prodromou's avatar
TRUE  
Evan Prodromou committed
744
    if ($recipient->find(true)) {
745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
        // XXX: should probably differentiate between profiles with
        // the same name by date of most recent update
        return $recipient;
    }
    // If this is a local user, try to find a local user with that nickname.
    $sender = User::staticGet($sender->id);
    if ($sender) {
        $recipient_user = User::staticGet('nickname', $nickname);
        if ($recipient_user) {
            return $recipient_user->getProfile();
        }
    }
    // Otherwise, no links. @messages from local users to remote users,
    // or from remote users to other remote users, are just
    // outside our ability to make intelligent guesses about
    return null;
761 762
}

763
function common_local_url($action, $args=null, $params=null, $fragment=null)
764
{
Evan Prodromou's avatar
Evan Prodromou committed
765
    $r = Router::get();
766
    $path = $r->build($action, $args, $params, $fragment);
767

768
    $ssl = common_is_sensitive($action);
769

770
    if (common_config('site','fancy')) {
771
        $url = common_path(mb_substr($path, 1), $ssl);
772
    } else {
773
        if (mb_strpos($path, '/index.php') === 0) {
774
            $url = common_path(mb_substr($path, 1), $ssl);
775
        } else {
776
            $url = common_path('index.php'.$path, $ssl);
777
        }
778 779 780 781
    }
    return $url;
}

782 783 784
function common_is_sensitive($action)
{
    static $sensitive = array('login', 'register', 'passwordsettings',
785
                              'twittersettings', 'api');
786 787 788 789 790 791 792 793 794
    $ssl = null;

    if (Event::handle('SensitiveAction', array($action, &$ssl))) {
        $ssl = in_array($action, $sensitive);
    }

    return $ssl;
}

795
function common_path($relative, $ssl=false)
796
{
797
    $pathpart = (common_config('site', 'path')) ? common_config('site', 'path')."/" : '';
798 799 800 801 802 803 804

    if (($ssl && (common_config('site', 'ssl') === 'sometimes'))
        || common_config('site', 'ssl') === 'always') {
        $proto = 'https';
        if (is_string(common_config('site', 'sslserver')) &&
            mb_strlen(common_config('site', 'sslserver')) > 0) {
            $serverpart = common_config('site', 'sslserver');
805
        } else if (common_config('site', 'server')) {
806
            $serverpart = common_config('site', 'server');
807 808
        } else {
            common_log(LOG_ERR, 'Site Sever not configured, unable to determine site name.');
809 810 811
        }
    } else {
        $proto = 'http';
812 813 814 815 816
        if (common_config('site', 'server')) {
            $serverpart = common_config('site', 'server');
        } else {
            common_log(LOG_ERR, 'Site Sever not configured, unable to determine site name.');
        }
817 818 819
    }

    return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
820 821
}

822 823
function common_date_string($dt)
{
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
    // XXX: do some sexy date formatting
    // return date(DATE_RFC822, $dt);
    $t = strtotime($dt);
    $now = time();
    $diff = $now - $t;

    if ($now < $t) { // that shouldn't happen!
        return common_exact_date($dt);
    } else if ($diff < 60) {
        return _('a few seconds ago');
    } else if ($diff < 92) {
        return _('about a minute ago');
    } else if ($diff < 3300) {
        return sprintf(_('about %d minutes ago'), round($diff/60));
    } else if ($diff < 5400) {
        return _('about an hour ago');
    } else if ($diff < 22 * 3600) {
        return sprintf(_('about %d hours ago'), round($diff/3600));
    } else if ($diff < 37 * 3600) {
        return _('about a day ago');
    } else if ($diff < 24 * 24 * 3600) {
        return sprintf(_('about %d days ago'), round($diff/(24*3600)));
    } else if ($diff < 46 * 24 * 3600) {
        return _('about a month ago');
    } else if ($diff < 330 * 24 * 3600) {
        return sprintf(_('about %d months ago'), round($diff/(30*24*3600)));
    } else if ($diff < 480 * 24 * 3600) {
        return _('about a year ago');
    } else {
        return common_exact_date($dt);
    }
Evan Prodromou's avatar
Evan Prodromou committed
855 856
}

857 858
function common_exact_date($dt)
{
Mike Cochrane's avatar
Mike Cochrane committed
859 860 861 862 863