]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - lib/util.php
slightly less confusing inline if
[quix0rs-gnu-social.git] / lib / util.php
index d8eee3d1341d098541e7f6d81d8927095d4c14ee..7b75a467843f1c56452c3d1218c76cd166000712 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, StatusNet, Inc.
+ * Copyright (C) 2008-2011, StatusNet, Inc.
  *
  * 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
@@ -157,22 +157,38 @@ function common_timezone()
     return common_config('site', 'timezone');
 }
 
+function common_valid_language($lang)
+{
+    if ($lang) {
+        // 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'] == $lang) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 function common_language()
 {
+    // Allow ?uselang=xx override, very useful for debugging
+    // and helping translators check usage and context.
+    if (isset($_GET['uselang'])) {
+        $uselang = strval($_GET['uselang']);
+        if (common_valid_language($uselang)) {
+            return $uselang;
+        }
+    }
+
     // If there is a user logged in and they've set a language preference
     // then return that one...
     if (_have_config() && common_logged_in()) {
         $user = common_current_user();
-        $user_language = $user->language;
-
-        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;
-                }
-            }
+
+        if (common_valid_language($user->language)) {
+            return $user->language;
         }
     }
 
@@ -194,14 +210,18 @@ function common_language()
 /**
  * Salted, hashed passwords are stored in the DB.
  */
-function common_munge_password($password, $id)
+function common_munge_password($password, $id, Profile $profile=null)
 {
-    if (is_object($id) || is_object($password)) {
-        $e = new Exception();
-        common_log(LOG_ERR, __METHOD__ . ' object in param to common_munge_password ' .
-                   str_replace("\n", " ", $e->getTraceAsString()));
+    $hashed = null;
+
+    if (Event::handle('StartHashPassword', array(&$hashed, $password, $profile))) {
+        Event::handle('EndHashPassword', array(&$hashed, $password, $profile));
     }
-    return md5($password . $id);
+    if (empty($hashed)) {
+        throw new PasswordHashException();
+    }
+
+    return $hashed;
 }
 
 /**
@@ -217,14 +237,18 @@ function common_check_user($nickname, $password)
     $authenticatedUser = false;
 
     if (Event::handle('StartCheckPassword', array($nickname, $password, &$authenticatedUser))) {
-        $user = User::staticGet('nickname', common_canonical_nickname($nickname));
-        if (!empty($user)) {
-            if (!empty($password)) { // never allow login with blank password
-                if (0 == strcmp(common_munge_password($password, $user->id),
-                                $user->password)) {
-                    //internal checking passed
-                    $authenticatedUser = $user;
-                }
+
+        if (common_is_email($nickname)) {
+            $user = User::getKV('email', common_canonical_email($nickname));
+        } else {
+            $user = User::getKV('nickname', Nickname::normalize($nickname));
+        }
+
+        if ($user instanceof User && !empty($password)) {
+            if (0 == strcmp(common_munge_password($password, $user->id),
+                            $user->password)) {
+                //internal checking passed
+                $authenticatedUser = $user;
             }
         }
         Event::handle('EndCheckPassword', array($nickname, $password, $authenticatedUser));
@@ -293,8 +317,8 @@ function common_set_user($user)
         return true;
     } else if (is_string($user)) {
         $nickname = $user;
-        $user = User::staticGet('nickname', $nickname);
-    } else if (!($user instanceof User)) {
+        $user = User::getKV('nickname', $nickname);
+    } else if (!$user instanceof User) {
         return false;
     }
 
@@ -302,6 +326,7 @@ function common_set_user($user)
         if (Event::handle('StartSetUser', array(&$user))) {
             if (!empty($user)) {
                 if (!$user->hasRight(Right::WEBLOGIN)) {
+                    // TRANS: Authorisation exception thrown when a user a not allowed to login.
                     throw new AuthorizationException(_('Not allowed to log in.'));
                 }
                 common_ensure_session();
@@ -347,7 +372,7 @@ function common_rememberme($user=null)
 
     $rm = new Remember_me();
 
-    $rm->code = common_good_rand(16);
+    $rm->code = common_random_hexstr(16);
     $rm->user_id = $user->id;
 
     // Wrap the insert in some good ol' fashioned transaction code
@@ -390,7 +415,7 @@ function common_remembered_user()
         return null;
     }
 
-    $rm = Remember_me::staticGet($code);
+    $rm = Remember_me::getKV('code', $code);
 
     if (!$rm) {
         common_log(LOG_WARNING, 'No such remember code: ' . $code);
@@ -404,9 +429,9 @@ function common_remembered_user()
         return null;
     }
 
-    $user = User::staticGet($rm->user_id);
+    $user = User::getKV('id', $rm->user_id);
 
-    if (!$user) {
+    if (!$user instanceof User) {
         common_log(LOG_WARNING, 'No such user for rememberme: ' . $rm->user_id);
         common_forgetme();
         return null;
@@ -461,8 +486,8 @@ function common_current_user()
             common_ensure_session();
             $id = isset($_SESSION['userid']) ? $_SESSION['userid'] : false;
             if ($id) {
-                $user = User::staticGet($id);
-                if ($user) {
+                $user = User::getKV('id', $id);
+                if ($user instanceof User) {
                        $_cur = $user;
                        return $_cur;
                 }
@@ -557,13 +582,10 @@ function common_canonical_email($email)
  * @param Notice $notice in whose context we're working
  * @return string partially rendered HTML
  */
-function common_render_content($text, $notice)
+function common_render_content($text, Notice $notice)
 {
     $r = common_render_text($text);
-    $id = $notice->profile_id;
     $r = common_linkify_mentions($r, $notice);
-    $r = preg_replace('/(^|[\s\.\,\:\;]+)!(' . Nickname::DISPLAY_FMT . ')/e',
-                      "'\\1!'.common_group_link($id, '\\2')", $r);
     return $r;
 }
 
@@ -621,7 +643,7 @@ function common_linkify_mention($mention)
 
         $xs->elementStart('span', 'vcard');
         $xs->elementStart('a', $attrs);
-        $xs->element('span', 'fn nickname', $mention['text']);
+        $xs->element('span', 'fn nickname '.$mention['type'], $mention['text']);
         $xs->elementEnd('a');
         $xs->elementEnd('span');
 
@@ -650,35 +672,41 @@ function common_linkify_mention($mention)
  */
 function common_find_mentions($text, $notice)
 {
-    $mentions = array();
-
-    $sender = Profile::staticGet('id', $notice->profile_id);
-
-    if (empty($sender)) {
-        return $mentions;
+    try {
+        $sender = Profile::getKV('id', $notice->profile_id);
+    } catch (NoProfileException $e) {
+        return array();
     }
 
+    $mentions = array();
+
     if (Event::handle('StartFindMentions', array($sender, $text, &$mentions))) {
         // Get the context of the original notice, if any
-        $originalAuthor   = null;
-        $originalNotice   = null;
-        $originalMentions = array();
+        $origAuthor   = null;
+        $origNotice   = null;
+        $origMentions = array();
 
         // Is it a reply?
 
-        if (!empty($notice) && !empty($notice->reply_to)) {
-            $originalNotice = Notice::staticGet('id', $notice->reply_to);
-            if (!empty($originalNotice)) {
-                $originalAuthor = Profile::staticGet('id', $originalNotice->profile_id);
+        if ($notice instanceof Notice) {
+            try {
+                $origNotice = $notice->getParent();
+                $origAuthor = $origNotice->getProfile();
 
-                $ids = $originalNotice->getReplies();
+                $ids = $origNotice->getReplies();
 
                 foreach ($ids as $id) {
-                    $repliedTo = Profile::staticGet('id', $id);
-                    if (!empty($repliedTo)) {
-                        $originalMentions[$repliedTo->nickname] = $repliedTo;
+                    $repliedTo = Profile::getKV('id', $id);
+                    if ($repliedTo instanceof Profile) {
+                        $origMentions[$repliedTo->nickname] = $repliedTo;
                     }
                 }
+            } catch (NoProfileException $e) {
+                common_log(LOG_WARNING, sprintf('Notice %d author profile id %d does not exist', $origNotice->id, $origNotice->profile_id));
+            } catch (ServerException $e) {
+                // Probably just no parent. Should get a specific NoParentException
+            } catch (Exception $e) {
+                common_log(LOG_WARNING, __METHOD__ . ' got exception ' . get_class($e) . ' : ' . $e->getMessage());
             }
         }
 
@@ -696,25 +724,26 @@ function common_find_mentions($text, $notice)
             // Start with conversation context, then go to
             // sender context.
 
-            if (!empty($originalAuthor) && $originalAuthor->nickname == $nickname) {
-                $mentioned = $originalAuthor;
-            } else if (!empty($originalMentions) &&
-                       array_key_exists($nickname, $originalMentions)) {
-                $mentioned = $originalMentions[$nickname];
+            if ($origAuthor instanceof Profile && $origAuthor->nickname == $nickname) {
+                $mentioned = $origAuthor;
+            } else if (!empty($origMentions) &&
+                       array_key_exists($nickname, $origMentions)) {
+                $mentioned = $origMentions[$nickname];
             } else {
                 $mentioned = common_relative_profile($sender, $nickname);
             }
 
-            if (!empty($mentioned)) {
-                $user = User::staticGet('id', $mentioned->id);
+            if ($mentioned instanceof Profile) {
+                $user = User::getKV('id', $mentioned->id);
 
-                if ($user) {
+                if ($user instanceof User) {
                     $url = common_local_url('userbyid', array('id' => $user->id));
                 } else {
                     $url = $mentioned->profileurl;
                 }
 
                 $mention = array('mentioned' => array($mentioned),
+                                 'type' => 'mention',
                                  'text' => $match[0],
                                  'position' => $match[1],
                                  'url' => $url);
@@ -730,26 +759,46 @@ function common_find_mentions($text, $notice)
         // @#tag => mention of all subscriptions tagged 'tag'
 
         preg_match_all('/(?:^|[\s\.\,\:\;]+)@#([\pL\pN_\-\.]{1,64})/',
-                       $text,
-                       $hmatches,
-                       PREG_OFFSET_CAPTURE);
-
+                       $text, $hmatches, PREG_OFFSET_CAPTURE);
         foreach ($hmatches[1] as $hmatch) {
-
             $tag = common_canonical_tag($hmatch[0]);
+            $plist = Profile_list::getByTaggerAndTag($sender->id, $tag);
+            if (!$plist instanceof Profile_list || $plist->private) {
+                continue;
+            }
+            $tagged = $sender->getTaggedSubscribers($tag);
 
-            $tagged = Profile_tag::getTagged($sender->id, $tag);
-
-            $url = common_local_url('subscriptions',
-                                    array('nickname' => $sender->nickname,
+            $url = common_local_url('showprofiletag',
+                                    array('tagger' => $sender->nickname,
                                           'tag' => $tag));
 
             $mentions[] = array('mentioned' => $tagged,
+                                'type'      => 'list',
                                 'text' => $hmatch[0],
                                 'position' => $hmatch[1],
                                 'url' => $url);
         }
 
+        preg_match_all('/(?:^|[\s\.\,\:\;]+)!(' . Nickname::DISPLAY_FMT . ')/',
+                       $text, $hmatches, PREG_OFFSET_CAPTURE);
+        foreach ($hmatches[1] as $hmatch) {
+            $nickname = Nickname::normalize($hmatch[0]);
+            $group = User_group::getForNickname($nickname, $sender);
+
+            if (!$group instanceof User_group || !$sender->isMember($group)) {
+                continue;
+            }
+
+            $profile = $group->getProfile();
+
+            $mentions[] = array('mentioned' => array($profile),
+                                'type'      => 'group',
+                                'text'      => $hmatch[0],
+                                'position'  => $hmatch[1],
+                                'url'       => $group->permalink(),
+                                'title'     => $group->getFancyName());
+        }
+
         Event::handle('EndFindMentions', array($sender, $text, &$mentions));
     }
 
@@ -787,7 +836,8 @@ function common_render_text($text)
 
     $r = preg_replace('/[\x{0}-\x{8}\x{b}-\x{c}\x{e}-\x{19}]/', '', $r);
     $r = common_replace_urls_callback($r, 'common_linkify');
-    $r = preg_replace('/(^|\&quot\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/ue', "'\\1#'.common_tag_link('\\2')", $r);
+    $r = preg_replace_callback('/(^|\&quot\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/u',
+                function ($m) { return "{$m[1]}#".common_tag_link($m[2]); }, $r);
     // XXX: machine tags
     return $r;
 }
@@ -851,7 +901,7 @@ function common_replace_urls_callback($text, $callback, $arg = null) {
  * @param callable $callback
  * @param mixed $arg optional argument to pass on as second param to callback
  * @return string
- * 
+ *
  * @access private
  */
 function callback_helper($matches, $callback, $arg=null) {
@@ -954,7 +1004,7 @@ function common_linkify($url) {
 
     // Check to see whether this is a known "attachment" URL.
 
-    $f = File::staticGet('url', $longurl);
+    $f = File::getKV('url', $longurl);
 
     if (empty($f)) {
         if (common_config('attachments', 'process_links')) {
@@ -968,7 +1018,7 @@ function common_linkify($url) {
             $is_attachment = true;
             $attachment_id = $f->id;
 
-            $thumb = File_thumbnail::staticGet('file_id', $f->id);
+            $thumb = File_thumbnail::getKV('file_id', $f->id);
             if (!empty($thumb)) {
                 $has_thumb = true;
             }
@@ -1015,9 +1065,17 @@ function common_linkify($url) {
  */
 function common_shorten_links($text, $always = false, User $user=null)
 {
-    $maxLength = Notice::maxContent();
-    if (!$always && ($maxLength == 0 || mb_strlen($text) <= $maxLength)) return $text;
-    return common_replace_urls_callback($text, array('File_redirection', 'makeShort'), $user);
+    if ($user === null) {
+        $user = common_current_user();
+    }
+
+    $maxLength = User_urlshortener_prefs::maxNoticeLength($user);
+
+    if ($always || ($maxLength != -1 && mb_strlen($text) > $maxLength)) {
+        return common_replace_urls_callback($text, array('File_redirection', 'forceShort'), $user);
+    } else {
+        return common_replace_urls_callback($text, array('File_redirection', 'makeShort'), $user);
+    }
 }
 
 /**
@@ -1097,7 +1155,7 @@ function common_tag_link($tag)
 function common_canonical_tag($tag)
 {
   // only alphanum
-  $tag = preg_replace('/[^\pL\pN]/', '', $tag);
+  $tag = preg_replace('/[^\pL\pN]/u', '', $tag);
   $tag = mb_convert_case($tag, MB_CASE_LOWER, "UTF-8");
   $tag = substr($tag, 0, 64);
   return $tag;
@@ -1108,35 +1166,6 @@ function common_valid_profile_tag($str)
     return preg_match('/^[A-Za-z0-9_\-\.]{1,64}$/', $str);
 }
 
-/**
- *
- * @param <type> $sender_id
- * @param <type> $nickname
- * @return <type>
- * @access private
- */
-function common_group_link($sender_id, $nickname)
-{
-    $sender = Profile::staticGet($sender_id);
-    $group = User_group::getForNickname($nickname, $sender);
-    if ($sender && $group && $sender->isMember($group)) {
-        $attrs = array('href' => $group->permalink(),
-                       'class' => 'url');
-        if (!empty($group->fullname)) {
-            $attrs['title'] = $group->getFancyName();
-        }
-        $xs = new XMLStringer();
-        $xs->elementStart('span', 'vcard');
-        $xs->elementStart('a', $attrs);
-        $xs->element('span', 'fn nickname', $nickname);
-        $xs->elementEnd('a');
-        $xs->elementEnd('span');
-        return $xs->getString();
-    } else {
-        return $nickname;
-    }
-}
-
 /**
  * Resolve an ambiguous profile nickname reference, checking in following order:
  * - profiles that $sender subscribes to
@@ -1184,10 +1213,10 @@ function common_relative_profile($sender, $nickname, $dt=null)
         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) {
+    $sender = User::getKV('id', $sender->id);
+    if ($sender instanceof User) {
+        $recipient_user = User::getKV('nickname', $nickname);
+        if ($recipient_user instanceof User) {
             return $recipient_user->getProfile();
         }
     }
@@ -1199,19 +1228,24 @@ function common_relative_profile($sender, $nickname, $dt=null)
 
 function common_local_url($action, $args=null, $params=null, $fragment=null, $addSession=true)
 {
-    $r = Router::get();
-    $path = $r->build($action, $args, $params, $fragment);
+    if (Event::handle('StartLocalURL', array(&$action, &$params, &$fragment, &$addSession, &$url))) {
+        $r = Router::get();
+        $path = $r->build($action, $args, $params, $fragment);
 
-    $ssl = common_is_sensitive($action);
+        $ssl = common_config('site', 'ssl') === 'always'
+                || StatusNet::isHTTPS()
+                || common_is_sensitive($action);
 
-    if (common_config('site','fancy')) {
-        $url = common_path(mb_substr($path, 1), $ssl, $addSession);
-    } else {
-        if (mb_strpos($path, '/index.php') === 0) {
-            $url = common_path(mb_substr($path, 1), $ssl, $addSession);
+        if (common_config('site','fancy')) {
+            $url = common_path($path, $ssl, $addSession);
         } else {
-            $url = common_path('index.php'.$path, $ssl, $addSession);
+            if (mb_strpos($path, '/index.php') === 0) {
+                $url = common_path($path, $ssl, $addSession);
+            } else {
+                $url = common_path('index.php/'.$path, $ssl, $addSession);
+            }
         }
+        Event::handle('EndLocalURL', array(&$action, &$params, &$fragment, &$addSession, &$url));
     }
     return $url;
 }
@@ -1223,10 +1257,10 @@ function common_is_sensitive($action)
         'register',
         'passwordsettings',
         'api',
-        'ApiOauthRequestToken',
-        'ApiOauthAccessToken',
-        'ApiOauthAuthorize',
-        'ApiOauthPin',
+        'ApiOAuthRequestToken',
+        'ApiOAuthAccessToken',
+        'ApiOAuthAuthorize',
+        'ApiOAuthPin',
         'showapplication'
     );
     $ssl = null;
@@ -1271,26 +1305,26 @@ function common_path($relative, $ssl=false, $addSession=true)
 
 function common_inject_session($url, $serverpart = null)
 {
-    if (common_have_session()) {
+    if (!common_have_session()) {
+        return $url;
+    }
 
-       if (empty($serverpart)) {
-           $serverpart = parse_url($url, PHP_URL_HOST);
-       }
+    if (empty($serverpart)) {
+        $serverpart = parse_url($url, PHP_URL_HOST);
+    }
 
-        $currentServer = $_SERVER['HTTP_HOST'];
+    $currentServer = (array_key_exists('HTTP_HOST', $_SERVER)) ? $_SERVER['HTTP_HOST'] : null;
 
-        // Are we pointing to another server (like an SSL server?)
+    // Are we pointing to another server (like an SSL server?)
 
-        if (!empty($currentServer) &&
-            0 != strcasecmp($currentServer, $serverpart)) {
-            // Pass the session ID as a GET parameter
-            $sesspart = session_name() . '=' . session_id();
-            $i = strpos($url, '?');
-            if ($i === false) { // no GET params, just append
-                $url .= '?' . $sesspart;
-            } else {
-                $url = substr($url, 0, $i + 1).$sesspart.'&'.substr($url, $i + 1);
-            }
+    if (!empty($currentServer) && 0 != strcasecmp($currentServer, $serverpart)) {
+        // Pass the session ID as a GET parameter
+        $sesspart = session_name() . '=' . session_id();
+        $i = strpos($url, '?');
+        if ($i === false) { // no GET params, just append
+            $url .= '?' . $sesspart;
+        } else {
+            $url = substr($url, 0, $i + 1).$sesspart.'&'.substr($url, $i + 1);
         }
     }
 
@@ -1316,28 +1350,28 @@ function common_date_string($dt)
     } else if ($diff < 3300) {
         $minutes = round($diff/60);
         // TRANS: Used in notices to indicate when the notice was made compared to now.
-        return sprintf( ngettext('about one minute ago', 'about %d minutes ago', $minutes), $minutes);
+        return sprintf( _m('about one minute ago', 'about %d minutes ago', $minutes), $minutes);
     } else if ($diff < 5400) {
         // TRANS: Used in notices to indicate when the notice was made compared to now.
         return _('about an hour ago');
     } else if ($diff < 22 * 3600) {
         $hours = round($diff/3600);
         // TRANS: Used in notices to indicate when the notice was made compared to now.
-        return sprintf( ngettext('about one hour ago', 'about %d hours ago', $hours), $hours);
+        return sprintf( _m('about one hour ago', 'about %d hours ago', $hours), $hours);
     } else if ($diff < 37 * 3600) {
         // TRANS: Used in notices to indicate when the notice was made compared to now.
         return _('about a day ago');
     } else if ($diff < 24 * 24 * 3600) {
         $days = round($diff/(24*3600));
         // TRANS: Used in notices to indicate when the notice was made compared to now.
-        return sprintf( ngettext('about one day ago', 'about %d days ago', $days), $days);
+        return sprintf( _m('about one day ago', 'about %d days ago', $days), $days);
     } else if ($diff < 46 * 24 * 3600) {
         // TRANS: Used in notices to indicate when the notice was made compared to now.
         return _('about a month ago');
     } else if ($diff < 330 * 24 * 3600) {
         $months = round($diff/(30*24*3600));
         // TRANS: Used in notices to indicate when the notice was made compared to now.
-        return sprintf( ngettext('about one month ago', 'about %d months ago',$months), $months);
+        return sprintf( _m('about one month ago', 'about %d months ago',$months), $months);
     } else if ($diff < 480 * 24 * 3600) {
         // TRANS: Used in notices to indicate when the notice was made compared to now.
         return _('about a year ago');
@@ -1425,6 +1459,7 @@ function common_redirect($url, $code=307)
 
     header('HTTP/1.1 '.$code.' '.$status[$code]);
     header("Location: $url");
+    header("Connection: close");
 
     $xo = new XMLOutputter();
     $xo->startXML('a',
@@ -1435,18 +1470,11 @@ function common_redirect($url, $code=307)
     exit;
 }
 
-function common_broadcast_notice($notice, $remote=false)
-{
-    // DO NOTHING!
-}
+// Stick the notice on the queue
 
-/**
- * Stick the notice on the queue.
- */
 function common_enqueue_notice($notice)
 {
-    static $localTransports = array('omb',
-                                    'ping');
+    static $localTransports = array('ping');
 
     $transports = array();
     if (common_config('sms', 'enabled')) {
@@ -1456,18 +1484,9 @@ function common_enqueue_notice($notice)
         $transports[] = 'plugin';
     }
 
-    $xmpp = common_config('xmpp', 'enabled');
-
-    if ($xmpp) {
-        $transports[] = 'jabber';
-    }
-
     // We can skip these for gatewayed notices.
     if ($notice->isLocal()) {
         $transports = array_merge($transports, $localTransports);
-        if ($xmpp) {
-            $transports[] = 'public';
-        }
     }
 
     if (Event::handle('StartEnqueueNotice', array($notice, &$transports))) {
@@ -1485,19 +1504,6 @@ function common_enqueue_notice($notice)
     return true;
 }
 
-/**
- * Broadcast profile updates to OMB and other remote subscribers.
- *
- * Since this may be slow with a lot of subscribers or bad remote sites,
- * this is run through the background queues if possible.
- */
-function common_broadcast_profile(Profile $profile)
-{
-    $qm = QueueManager::get();
-    $qm->enqueue($profile, "profile");
-    return true;
-}
-
 function common_profile_url($nickname)
 {
     return common_local_url('showstream', array('nickname' => $nickname),
@@ -1519,16 +1525,18 @@ function common_root_url($ssl=false)
 
 /**
  * returns $bytes bytes of random data as a hexadecimal string
- * "good" here is a goal and not a guarantee
  */
-function common_good_rand($bytes)
+function common_random_hexstr($bytes)
 {
-    // XXX: use random.org...?
-    if (@file_exists('/dev/urandom')) {
-        return common_urandom($bytes);
-    } else { // FIXME: this is probably not good enough
-        return common_mtrand($bytes);
+    $str = @file_exists('/dev/urandom')
+            ? common_urandom($bytes)
+            : common_mtrand($bytes);
+
+    $hexstr = '';
+    for ($i = 0; $i < $bytes; $i++) {
+        $hexstr .= sprintf("%02x", ord($str[$i]));
     }
+    return $hexstr;
 }
 
 function common_urandom($bytes)
@@ -1537,20 +1545,16 @@ function common_urandom($bytes)
     // should not block
     $src = fread($h, $bytes);
     fclose($h);
-    $enc = '';
-    for ($i = 0; $i < $bytes; $i++) {
-        $enc .= sprintf("%02x", (ord($src[$i])));
-    }
-    return $enc;
+    return $src;
 }
 
 function common_mtrand($bytes)
 {
-    $enc = '';
+    $str = '';
     for ($i = 0; $i < $bytes; $i++) {
-        $enc .= sprintf("%02x", mt_rand(0, 255));
+        $str .= chr(mt_rand(0, 255));
     }
-    return $enc;
+    return $str;
 }
 
 /**
@@ -1665,8 +1669,10 @@ function common_debug($msg, $filename=null)
 
 function common_log_db_error(&$object, $verb, $filename=null)
 {
+    global $_PEAR;
+
     $objstr = common_log_objstring($object);
-    $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+    $last_error = &$_PEAR->getStaticProperty('DB_DataObject','lastError');
     if (is_object($last_error)) {
         $msg = $last_error->message;
     } else {
@@ -1696,9 +1702,13 @@ function common_log_objstring(&$object)
     return $objstring;
 }
 
-function common_valid_http_url($url)
+function common_valid_http_url($url, $secure=false)
 {
-    return Validate::uri($url, array('allowed_schemes' => array('http', 'https')));
+    // If $secure is true, only allow https URLs to pass
+    // (if false, we use '?' in 'https?' to say the 's' is optional)
+    $regex = $secure ? '/^https$/' : '/^https?$/';
+    return filter_var($url, FILTER_VALIDATE_URL)
+            && preg_match($regex, parse_url($url, PHP_URL_SCHEME));
 }
 
 function common_valid_tag($tag)
@@ -1850,6 +1860,30 @@ function common_config($main, $sub)
             array_key_exists($sub, $config[$main])) ? $config[$main][$sub] : false;
 }
 
+function common_config_set($main, $sub, $value)
+{
+    global $config;
+    if (!array_key_exists($main, $config)) {
+        $config[$main] = array();
+    }
+    $config[$main][$sub] = $value;
+}
+
+function common_config_append($main, $sub, $value)
+{
+    global $config;
+    if (!array_key_exists($main, $config)) {
+        $config[$main] = array();
+    }
+    if (!array_key_exists($sub, $config[$main])) {
+        $config[$main][$sub] = array();
+    }
+    if (!is_array($config[$main][$sub])) {
+        $config[$main][$sub] = array($config[$main][$sub]);
+    }
+    array_push($config[$main][$sub], $value);
+}
+
 /**
  * Pull arguments from a GET/POST/REQUEST array with first-level input checks:
  * strips "magic quotes" slashes if necessary, and kills invalid UTF-8 strings.
@@ -1911,7 +1945,7 @@ function common_confirmation_code($bits)
     $code = '';
     for ($i = 0; $i < $chars; $i++) {
         // XXX: convert to string and back
-        $num = hexdec(common_good_rand(1));
+        $num = hexdec(common_random_hexstr(1));
         // XXX: randomness is too precious to throw away almost
         // 40% of the bits we get!
         $code .= $codechars[$num%32];
@@ -1921,30 +1955,76 @@ function common_confirmation_code($bits)
 
 // convert markup to HTML
 
-function common_markup_to_html($c)
+function common_markup_to_html($c, $args=null)
 {
-    $c = preg_replace('/%%action.(\w+)%%/e', "common_local_url('\\1')", $c);
-    $c = preg_replace('/%%doc.(\w+)%%/e', "common_local_url('doc', array('title'=>'\\1'))", $c);
-    $c = preg_replace('/%%(\w+).(\w+)%%/e', 'common_config(\'\\1\', \'\\2\')', $c);
+    if ($c === null) {
+        return '';
+    }
+
+    if (is_null($args)) {
+        $args = array();
+    }
+
+    // XXX: not very efficient
+
+    foreach ($args as $name => $value) {
+        $c = preg_replace('/%%arg.'.$name.'%%/', $value, $c);
+    }
+
+    $c = preg_replace_callback('/%%user.(\w+)%%/', function ($m) { return common_user_property($m[1]); }, $c);
+    $c = preg_replace_callback('/%%action.(\w+)%%/', function ($m) { return common_local_url($m[1]); }, $c);
+    $c = preg_replace_callback('/%%doc.(\w+)%%/', function ($m) { return common_local_url('doc', array('title'=>$m[1])); }, $c);
+    $c = preg_replace_callback('/%%(\w+).(\w+)%%/', function ($m) { return common_config($m[1], $m[2]); }, $c);
     return Markdown($c);
 }
 
-function common_profile_uri($profile)
+function common_user_property($property)
 {
-    if (!$profile) {
+    $profile = Profile::current();
+
+    if (empty($profile)) {
         return null;
     }
-    $user = User::staticGet($profile->id);
-    if ($user) {
-        return $user->uri;
+
+    switch ($property) {
+    case 'profileurl':
+    case 'nickname':
+    case 'fullname':
+    case 'location':
+    case 'bio':
+        return $profile->$property;
+        break;
+    case 'avatar':
+        try {
+            return $profile->getAvatar(AVATAR_STREAM_SIZE);
+        } catch (Exception $e) {
+            return null;
+        }
+        break;
+    case 'bestname':
+        return $profile->getBestName();
+        break;
+    default:
+        return null;
     }
+}
+
+function common_profile_uri($profile)
+{
+    $uri = null;
 
-    $remote = Remote_profile::staticGet($profile->id);
-    if ($remote) {
-        return $remote->uri;
+    if (!empty($profile)) {
+        if (Event::handle('StartCommonProfileURI', array($profile, &$uri))) {
+            $user = User::getKV('id', $profile->id);
+            if ($user instanceof User) {
+                $uri = $user->uri;
+            }
+            Event::handle('EndCommonProfileURI', array($profile, &$uri));
+        }
     }
+
     // XXX: this is a very bad profile!
-    return null;
+    return $uri;
 }
 
 function common_canonical_sms($sms)
@@ -2001,26 +2081,11 @@ function common_session_token()
 {
     common_ensure_session();
     if (!array_key_exists('token', $_SESSION)) {
-        $_SESSION['token'] = common_good_rand(64);
+        $_SESSION['token'] = common_random_hexstr(64);
     }
     return $_SESSION['token'];
 }
 
-function common_cache_key($extra)
-{
-    return Cache::key($extra);
-}
-
-function common_keyize($str)
-{
-    return Cache::keyize($str);
-}
-
-function common_memcache()
-{
-    return Cache::instance();
-}
-
 function common_license_terms($uri)
 {
     if(preg_match('/creativecommons.org\/licenses\/([^\/]+)/', $uri, $matches)) {
@@ -2061,33 +2126,50 @@ function common_database_tablename($tablename)
 /**
  * Shorten a URL with the current user's configured shortening service,
  * or ur1.ca if configured, or not at all if no shortening is set up.
- * Length is not considered.
  *
- * @param string $long_url
+ * @param string  $long_url original URL
  * @param User $user to specify a particular user's options
+ * @param boolean $force    Force shortening (used when notice is too long)
  * @return string may return the original URL if shortening failed
  *
  * @fixme provide a way to specify a particular shortener
  */
-function common_shorten_url($long_url, User $user=null)
+function common_shorten_url($long_url, User $user=null, $force = false)
 {
     $long_url = trim($long_url);
-    if (empty($user)) {
-        // Current web session
-        $user = common_current_user();
-    }
-    if (empty($user)) {
-        // common current user does not find a user when called from the XMPP daemon
-        // therefore we'll set one here fix, so that XMPP given URLs may be shortened
-        $shortenerName = 'ur1.ca';
-    } else {
-        $shortenerName = $user->urlshorteningservice;
-    }
 
-    if(Event::handle('StartShortenUrl', array($long_url,$shortenerName,&$shortenedUrl))){
-        //URL wasn't shortened, so return the long url
+    $user = common_current_user();
+
+    $maxUrlLength = User_urlshortener_prefs::maxUrlLength($user);
+
+    // $force forces shortening even if it's not strictly needed
+    // I doubt URL shortening is ever 'strictly' needed. - ESP
+
+    if (($maxUrlLength == -1 || mb_strlen($long_url) < $maxUrlLength) && !$force) {
         return $long_url;
-    }else{
+    }
+
+    $shortenerName = User_urlshortener_prefs::urlShorteningService($user);
+
+    if (Event::handle('StartShortenUrl',
+                      array($long_url, $shortenerName, &$shortenedUrl))) {
+        if ($shortenerName == 'internal') {
+            $f = File::processNew($long_url);
+            if (empty($f)) {
+                return $long_url;
+            } else {
+                $shortenedUrl = common_local_url('redirecturl',
+                                                 array('id' => $f->id));
+                if ((mb_strlen($shortenedUrl) < mb_strlen($long_url)) || $force) {
+                    return $shortenedUrl;
+                } else {
+                    return $long_url;
+                }
+            }
+        } else {
+            return $long_url;
+        }
+    } else {
         //URL was shortened, so return the result
         return trim($shortenedUrl);
     }
@@ -2134,7 +2216,7 @@ function common_url_to_nickname($url)
 
     $parts = parse_url($url);
 
-    # If any of these parts exist, this won't work
+    // If any of these parts exist, this won't work
 
     foreach ($bad as $badpart) {
         if (array_key_exists($badpart, $parts)) {
@@ -2142,15 +2224,15 @@ function common_url_to_nickname($url)
         }
     }
 
-    # We just have host and/or path
+    // We just have host and/or path
 
-    # If it's just a host...
+    // If it's just a host...
     if (array_key_exists('host', $parts) &&
         (!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
     {
         $hostparts = explode('.', $parts['host']);
 
-        # Try to catch common idiom of nickname.service.tld
+        // Try to catch common idiom of nickname.service.tld
 
         if ((count($hostparts) > 2) &&
             (strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
@@ -2158,12 +2240,12 @@ function common_url_to_nickname($url)
         {
             return common_nicknamize($hostparts[0]);
         } else {
-            # Do the whole hostname
+            // Do the whole hostname
             return common_nicknamize($parts['host']);
         }
     } else {
         if (array_key_exists('path', $parts)) {
-            # Strip starting, ending slashes
+            // Strip starting, ending slashes
             $path = preg_replace('@/$@', '', $parts['path']);
             $path = preg_replace('@^/@', '', $path);
             $path = basename($path);
@@ -2187,8 +2269,11 @@ function common_url_to_nickname($url)
 
 function common_nicknamize($str)
 {
-    $str = preg_replace('/\W/', '', $str);
-    return strtolower($str);
+    try {
+        return Nickname::normalize($str);
+    } catch (NicknameException $e) {
+        return null;
+    }
 }
 
 function common_perf_counter($key, $val=null)
@@ -2227,3 +2312,36 @@ function common_log_perf_counters()
         }
     }
 }
+
+function common_is_email($str)
+{
+    return (strpos($str, '@') !== false);
+}
+
+function common_init_stats()
+{
+    global $_mem, $_ts;
+
+    $_mem = memory_get_usage(true);
+    $_ts  = microtime(true);
+}
+
+function common_log_delta($comment=null)
+{
+    global $_mem, $_ts;
+
+    $mold = $_mem;
+    $told = $_ts;
+
+    $_mem = memory_get_usage(true);
+    $_ts  = microtime(true);
+
+    $mtotal = $_mem - $mold;
+    $ttotal = $_ts - $told;
+
+    if (empty($comment)) {
+        $comment = 'Delta';
+    }
+
+    common_debug(sprintf("%s: %d %d", $comment, $mtotal, round($ttotal * 1000000)));
+}