]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - lib/util.php
Merge remote-tracking branch 'mainline/1.0.x' into people_tags_rebase
[quix0rs-gnu-social.git] / lib / util.php
index ce5da1cd8134fa050087511392d0c9f2aed06a24..d48d4c18c59b485c6ded326c1d8493be6bda1fa8 100644 (file)
@@ -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;
         }
     }
 
@@ -300,7 +316,11 @@ function common_set_user($user)
 
     if ($user) {
         if (Event::handle('StartSetUser', array(&$user))) {
-            if($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();
                 $_SESSION['userid'] = $user->id;
                 $_cur = $user;
@@ -517,14 +537,29 @@ function common_user_cache_hash($user=false)
     }
 }
 
-// get canonical version of nickname for comparison
+/**
+ * get canonical version of nickname for comparison
+ *
+ * @param string $nickname
+ * @return string
+ *
+ * @throws NicknameException on invalid input
+ * @deprecated call Nickname::normalize() directly.
+ */
 function common_canonical_nickname($nickname)
 {
-    // XXX: UTF-8 canonicalization (like combining chars)
-    return strtolower($nickname);
+    return Nickname::normalize($nickname);
 }
 
-// get canonical version of email for comparison
+/**
+ * get canonical version of email for comparison
+ *
+ * @fixme actually normalize
+ * @fixme reject invalid input
+ *
+ * @param string $email
+ * @return string
+ */
 function common_canonical_email($email)
 {
     // XXX: canonicalize UTF-8
@@ -532,15 +567,33 @@ function common_canonical_email($email)
     return $email;
 }
 
+/**
+ * Partial notice markup rendering step: build links to !group references.
+ *
+ * @param string $text partially rendered HTML
+ * @param Notice $notice in whose context we're working
+ * @return string partially rendered HTML
+ */
 function common_render_content($text, $notice)
 {
     $r = common_render_text($text);
     $id = $notice->profile_id;
     $r = common_linkify_mentions($r, $notice);
-    $r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
+    $r = preg_replace('/(^|[\s\.\,\:\;]+)!(' . Nickname::DISPLAY_FMT . ')/e',
+                      "'\\1!'.common_group_link($id, '\\2')", $r);
     return $r;
 }
 
+/**
+ * Finds @-mentions within the partially-rendered text section and
+ * turns them into live links.
+ *
+ * Should generally not be called except from common_render_content().
+ *
+ * @param string $text partially-rendered HTML
+ * @param Notice $notice in-progress or complete Notice object for context
+ * @return string partially-rendered HTML
+ */
 function common_linkify_mentions($text, $notice)
 {
     $mentions = common_find_mentions($text, $notice);
@@ -597,6 +650,21 @@ function common_linkify_mention($mention)
     return $output;
 }
 
+/**
+ * Find @-mentions in the given text, using the given notice object as context.
+ * References will be resolved with common_relative_profile() against the user
+ * who posted the notice.
+ *
+ * Note the return data format is internal, to be used for building links and
+ * such. Should not be used directly; rather, call common_linkify_mentions().
+ *
+ * @param string $text
+ * @param Notice $notice notice in whose context we're building links
+ *
+ * @return array
+ *
+ * @access private
+ */
 function common_find_mentions($text, $notice)
 {
     $mentions = array();
@@ -631,20 +699,15 @@ function common_find_mentions($text, $notice)
             }
         }
 
-        preg_match_all('/^T ([A-Z0-9]{1,64}) /',
-                       $text,
-                       $tmatches,
-                       PREG_OFFSET_CAPTURE);
-
-        preg_match_all('/(?:^|\s+)@(['.NICKNAME_FMT.']{1,64})/',
-                       $text,
-                       $atmatches,
-                       PREG_OFFSET_CAPTURE);
-
-        $matches = array_merge($tmatches[1], $atmatches[1]);
+        $matches = common_find_mentions_raw($text);
 
         foreach ($matches as $match) {
-            $nickname = common_canonical_nickname($match[0]);
+            try {
+                $nickname = Nickname::normalize($match[0]);
+            } catch (NicknameException $e) {
+                // Bogus match? Drop it.
+                continue;
+            }
 
             // Try to get a profile for this nickname.
             // Start with conversation context, then go to
@@ -691,17 +754,19 @@ function common_find_mentions($text, $notice)
         foreach ($hmatches[1] as $hmatch) {
 
             $tag = common_canonical_tag($hmatch[0]);
-
-            $tagged = Profile_tag::getTagged($sender->id, $tag);
-
-            $url = common_local_url('subscriptions',
-                                    array('nickname' => $sender->nickname,
-                                          'tag' => $tag));
-
-            $mentions[] = array('mentioned' => $tagged,
-                                'text' => $hmatch[0],
-                                'position' => $hmatch[1],
-                                'url' => $url);
+            $plist = Profile_list::getByTaggerAndTag($sender->id, $tag);
+            if (!empty($plist) && !$plist->private) {
+                $tagged = $sender->getTaggedSubscribers($tag);
+
+                $url = common_local_url('showprofiletag',
+                                        array('tagger' => $sender->nickname,
+                                              'tag' => $tag));
+
+                $mentions[] = array('mentioned' => $tagged,
+                                    'text' => $hmatch[0],
+                                    'position' => $hmatch[1],
+                                    'url' => $url);
+            }
         }
 
         Event::handle('EndFindMentions', array($sender, $text, &$mentions));
@@ -710,18 +775,50 @@ function common_find_mentions($text, $notice)
     return $mentions;
 }
 
+/**
+ * Does the actual regex pulls to find @-mentions in text.
+ * Should generally not be called directly; for use in common_find_mentions.
+ *
+ * @param string $text
+ * @return array of PCRE match arrays
+ */
+function common_find_mentions_raw($text)
+{
+    $tmatches = array();
+    preg_match_all('/^T (' . Nickname::DISPLAY_FMT . ') /',
+                   $text,
+                   $tmatches,
+                   PREG_OFFSET_CAPTURE);
+
+    $atmatches = array();
+    preg_match_all('/(?:^|\s+)@(' . Nickname::DISPLAY_FMT . ')\b/',
+                   $text,
+                   $atmatches,
+                   PREG_OFFSET_CAPTURE);
+
+    $matches = array_merge($tmatches[1], $atmatches[1]);
+    return $matches;
+}
+
 function common_render_text($text)
 {
     $r = htmlspecialchars($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})/e', "'\\1#'.common_tag_link('\\2')", $r);
+    $r = preg_replace('/(^|\&quot\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/ue', "'\\1#'.common_tag_link('\\2')", $r);
     // XXX: machine tags
     return $r;
 }
 
-function common_replace_urls_callback($text, $callback, $notice_id = null) {
+/**
+ * Find links in the given text and pass them to the given callback function.
+ *
+ * @param string $text
+ * @param function($text, $arg) $callback: return replacement text
+ * @param mixed $arg: optional argument will be passed on to the callback
+ */
+function common_replace_urls_callback($text, $callback, $arg = null) {
     // Start off with a regex
     $regex = '#'.
     '(?:^|[\s\<\>\(\)\[\]\{\}\\\'\\\";]+)(?![\@\!\#])'.
@@ -762,10 +859,21 @@ function common_replace_urls_callback($text, $callback, $notice_id = null) {
     '#ixu';
     //preg_match_all($regex,$text,$matches);
     //print_r($matches);
-    return preg_replace_callback($regex, curry('callback_helper',$callback,$notice_id) ,$text);
+    return preg_replace_callback($regex, curry('callback_helper',$callback,$arg) ,$text);
 }
 
-function callback_helper($matches, $callback, $notice_id) {
+/**
+ * Intermediate callback for common_replace_links(), helps resolve some
+ * ambiguous link forms before passing on to the final callback.
+ *
+ * @param array $matches
+ * @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) {
     $url=$matches[1];
     $left = strpos($matches[0],$url);
     $right = $left+strlen($url);
@@ -808,11 +916,7 @@ function callback_helper($matches, $callback, $notice_id) {
         }
     }while($original_url!=$url);
 
-    if(empty($notice_id)){
-        $result = call_user_func_array($callback, array($url));
-    }else{
-        $result = call_user_func_array($callback, array(array($url,$notice_id)) );
-    }
+    $result = call_user_func_array($callback, array($url, $arg));
     return substr($matches[0],0,$left) . $result . substr($matches[0],$right);
 }
 
@@ -840,11 +944,11 @@ function common_linkify($url) {
     // functions
     $url = htmlspecialchars_decode($url);
 
-   if(strpos($url, '@') !== false && strpos($url, ':') === false) {
-       //url is an email address without the mailto: protocol
-       $canon = "mailto:$url";
-       $longurl = "mailto:$url";
-   }else{
+    if (strpos($url, '@') !== false && strpos($url, ':') === false && Validate::email($url)) {
+        //url is an email address without the mailto: protocol
+        $canon = "mailto:$url";
+        $longurl = "mailto:$url";
+    } else {
 
         $canon = File_redirection::_canonUrl($url);
 
@@ -912,11 +1016,33 @@ function common_linkify($url) {
     return XMLStringer::estring('a', $attrs, $url);
 }
 
-function common_shorten_links($text, $always = false)
+/**
+ * Find and shorten links in a given chunk of text if it's longer than the
+ * configured notice content limit (or unconditionally).
+ *
+ * Side effects: may save file and file_redirection records for referenced URLs.
+ *
+ * Pass the $user option or call $user->shortenLinks($text) to ensure the proper
+ * user's options are used; otherwise the current web session user's setitngs
+ * will be used or ur1.ca if there is no active web login.
+ *
+ * @param string $text
+ * @param boolean $always (optional)
+ * @param User $user (optional)
+ *
+ * @return string
+ */
+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 = common_current_user();
+
+    $maxLength = User_urlshortener_prefs::maxNoticeLength($user);
+
+    if ($always || 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);
+    }
 }
 
 /**
@@ -977,9 +1103,9 @@ function common_tag_link($tag)
     $canonical = common_canonical_tag($tag);
     if (common_config('singleuser', 'enabled')) {
         // regular TagAction isn't set up in 1user mode
-        $user = User::singleUser();
+        $nickname = User::singleUserNickname();
         $url = common_local_url('showstream',
-                                array('nickname' => $user->nickname,
+                                array('nickname' => $nickname,
                                       'tag' => $canonical));
     } else {
         $url = common_local_url('tag', array('tag' => $canonical));
@@ -1004,6 +1130,13 @@ 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);
@@ -1026,13 +1159,37 @@ function common_group_link($sender_id, $nickname)
     }
 }
 
+/**
+ * Resolve an ambiguous profile nickname reference, checking in following order:
+ * - profiles that $sender subscribes to
+ * - profiles that subscribe to $sender
+ * - local user profiles
+ *
+ * WARNING: does not validate or normalize $nickname -- MUST BE PRE-VALIDATED
+ * OR THERE MAY BE A RISK OF SQL INJECTION ATTACKS. THIS FUNCTION DOES NOT
+ * ESCAPE SQL.
+ *
+ * @fixme validate input
+ * @fixme escape SQL
+ * @fixme fix or remove mystery third parameter
+ * @fixme is $sender a User or Profile?
+ *
+ * @param <type> $sender the user or profile in whose context we're looking
+ * @param string $nickname validated nickname of
+ * @param <type> $dt unused mystery parameter; in Notice reply-to handling a timestamp is passed.
+ *
+ * @return Profile or null
+ */
 function common_relative_profile($sender, $nickname, $dt=null)
 {
+    // Will throw exception on invalid input.
+    $nickname = Nickname::normalize($nickname);
+
     // 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');
-    $recipient->whereAdd("nickname = '" . trim($nickname) . "'", 'AND');
+    $recipient->whereAdd('EXISTS (SELECT subscribed from subscription where subscriber = '.intval($sender->id).' and subscribed = id)', 'AND');
+    $recipient->whereAdd("nickname = '" . $recipient->escape($nickname) . "'", 'AND');
     if ($recipient->find(true)) {
         // XXX: should probably differentiate between profiles with
         // the same name by date of most recent update
@@ -1041,8 +1198,8 @@ function common_relative_profile($sender, $nickname, $dt=null)
     // 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');
-    $recipient->whereAdd("nickname = '" . trim($nickname) . "'", 'AND');
+    $recipient->whereAdd('EXISTS (SELECT subscriber from subscription where subscribed = '.intval($sender->id).' and subscriber = id)', 'AND');
+    $recipient->whereAdd("nickname = '" . $recipient->escape($nickname) . "'", 'AND');
     if ($recipient->find(true)) {
         // XXX: should probably differentiate between profiles with
         // the same name by date of most recent update
@@ -1181,28 +1338,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');
@@ -1300,14 +1457,8 @@ 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',
@@ -1321,18 +1472,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))) {
@@ -1871,21 +2013,6 @@ function common_session_token()
     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)) {
@@ -1926,30 +2053,46 @@ 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
- * @fixme provide a way to specify to use a given user's shortening preferences
  */
-function common_shorten_url($long_url)
+function common_shorten_url($long_url, User $user=null, $force = false)
 {
     $long_url = trim($long_url);
+
     $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
+    $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 (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));
+                return $shortenedUrl;
+            }
+        } else {
+            return $long_url;
+        }
+    } else {
         //URL was shortened, so return the result
         return trim($shortenedUrl);
     }
@@ -1996,7 +2139,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)) {
@@ -2004,15 +2147,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
@@ -2020,12 +2163,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);
@@ -2052,3 +2195,40 @@ function common_nicknamize($str)
     $str = preg_replace('/\W/', '', $str);
     return strtolower($str);
 }
+
+function common_perf_counter($key, $val=null)
+{
+    global $_perfCounters;
+    if (isset($_perfCounters)) {
+        if (common_config('site', 'logperf')) {
+            if (array_key_exists($key, $_perfCounters)) {
+                $_perfCounters[$key][] = $val;
+            } else {
+                $_perfCounters[$key] = array($val);
+            }
+            if (common_config('site', 'logperf_detail')) {
+                common_log(LOG_DEBUG, "PERF COUNTER HIT: $key $val");
+            }
+        }
+    }
+}
+
+function common_log_perf_counters()
+{
+    if (common_config('site', 'logperf')) {
+        global $_startTime, $_perfCounters;
+
+        if (isset($_startTime)) {
+            $endTime = microtime(true);
+            $diff = round(($endTime - $_startTime) * 1000);
+            common_log(LOG_DEBUG, "PERF runtime: ${diff}ms");
+        }
+        $counters = $_perfCounters;
+        ksort($counters);
+        foreach ($counters as $key => $values) {
+            $count = count($values);
+            $unique = count(array_unique($values));
+            common_log(LOG_DEBUG, "PERF COUNTER: $key $count ($unique unique)");
+        }
+    }
+}