]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - lib/util.php
Merge branch '0.9.x' into 1.0.x
[quix0rs-gnu-social.git] / lib / util.php
index 9f62097d544a28acf4a53f50633bc2a7a867b212..1d4f5a5499228947cd459fdf425e6e3675024500 100644 (file)
 
 /* XXX: break up into separate modules (HTTP, user, files) */
 
-// Show a server error
-
+/**
+ * Show a server error.
+ */
 function common_server_error($msg, $code=500)
 {
     $err = new ServerErrorAction($msg, $code);
     $err->showPage();
 }
 
-// Show a user error
+/**
+ * Show a user error.
+ */
 function common_user_error($msg, $code=400)
 {
     $err = new ClientErrorAction($msg, $code);
@@ -37,7 +40,7 @@ function common_user_error($msg, $code=400)
 /**
  * This should only be used at setup; processes switching languages
  * to send text to other users should use common_switch_locale().
- * 
+ *
  * @param string $language Locale language code (optional; empty uses
  *                         current user's preference or site default)
  * @return mixed success
@@ -61,10 +64,10 @@ function common_init_locale($language=null)
 /**
  * Initialize locale and charset settings and gettext with our message catalog,
  * using the current user's language preference or the site default.
- * 
+ *
  * This should generally only be run at framework initialization; code switching
  * languages at runtime should call common_switch_language().
- * 
+ *
  * @access private
  */
 function common_init_language()
@@ -142,7 +145,6 @@ function common_switch_locale($language=null)
     textdomain("statusnet");
 }
 
-
 function common_timezone()
 {
     if (common_logged_in()) {
@@ -155,23 +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;
         }
     }
 
@@ -189,8 +206,10 @@ function common_language()
     // Finally, if none of the above worked, use the site's default...
     return common_config('site', 'language');
 }
-// salted, hashed passwords are stored in the DB
 
+/**
+ * Salted, hashed passwords are stored in the DB.
+ */
 function common_munge_password($password, $id)
 {
     if (is_object($id) || is_object($password)) {
@@ -201,8 +220,9 @@ function common_munge_password($password, $id)
     return md5($password . $id);
 }
 
-// check if a username exists and has matching password
-
+/**
+ * Check if a username exists and has matching password.
+ */
 function common_check_user($nickname, $password)
 {
     // empty nickname always unacceptable
@@ -229,7 +249,9 @@ function common_check_user($nickname, $password)
     return $authenticatedUser;
 }
 
-// is the current user logged in?
+/**
+ * Is the current user logged in?
+ */
 function common_logged_in()
 {
     return (!is_null(common_current_user()));
@@ -275,12 +297,10 @@ function common_ensure_session()
 // 3) null to clear
 
 // Initialize to false; set to null if none found
-
 $_cur = false;
 
 function common_set_user($user)
 {
-
     global $_cur;
 
     if (is_null($user) && common_have_session()) {
@@ -322,7 +342,8 @@ function common_set_cookie($key, $value, $expiration=0)
                      $value,
                      $expiration,
                      $cookiepath,
-                     $server);
+                     $server,
+                     common_config('site', 'ssl')=='always');
 }
 
 define('REMEMBERME', 'rememberme');
@@ -366,7 +387,6 @@ function common_rememberme($user=null)
 
 function common_remembered_user()
 {
-
     $user = null;
 
     $packed = isset($_COOKIE[REMEMBERME]) ? $_COOKIE[REMEMBERME] : null;
@@ -428,14 +448,17 @@ function common_remembered_user()
     return $user;
 }
 
-// must be called with a valid user!
-
+/**
+ * must be called with a valid user!
+ */
 function common_forgetme()
 {
     common_set_cookie(REMEMBERME, '', 0);
 }
 
-// who is the current user?
+/**
+ * Who is the current user?
+ */
 function common_current_user()
 {
     global $_cur;
@@ -471,10 +494,11 @@ function common_current_user()
     return $_cur;
 }
 
-// Logins that are 'remembered' aren't 'real' -- they're subject to
-// cookie-stealing. So, we don't let them do certain things. New reg,
-// OpenID, and password logins _are_ real.
-
+/**
+ * Logins that are 'remembered' aren't 'real' -- they're subject to
+ * cookie-stealing. So, we don't let them do certain things. New reg,
+ * OpenID, and password logins _are_ real.
+ */
 function common_real_login($real=true)
 {
     common_ensure_session();
@@ -486,6 +510,29 @@ function common_is_real_login()
     return common_logged_in() && $_SESSION['real_login'];
 }
 
+/**
+ * Get a hash portion for HTTP caching Etags and such including
+ * info on the current user's session. If login/logout state changes,
+ * or we've changed accounts, or we've renamed the current user,
+ * we'll get a new hash value.
+ *
+ * This should not be considered secure information.
+ *
+ * @param User $user (optional; uses common_current_user() if left out)
+ * @return string
+ */
+function common_user_cache_hash($user=false)
+{
+    if ($user === false) {
+        $user = common_current_user();
+    }
+    if ($user) {
+        return crc32($user->id . ':' . $user->nickname);
+    } else {
+        return '0';
+    }
+}
+
 // get canonical version of nickname for comparison
 function common_canonical_nickname($nickname)
 {
@@ -577,9 +624,7 @@ function common_find_mentions($text, $notice)
     }
 
     if (Event::handle('StartFindMentions', array($sender, $text, &$mentions))) {
-
         // Get the context of the original notice, if any
-
         $originalAuthor   = null;
         $originalNotice   = null;
         $originalMentions = array();
@@ -615,7 +660,6 @@ function common_find_mentions($text, $notice)
         $matches = array_merge($tmatches[1], $atmatches[1]);
 
         foreach ($matches as $match) {
-
             $nickname = common_canonical_nickname($match[0]);
 
             // Try to get a profile for this nickname.
@@ -623,19 +667,15 @@ function common_find_mentions($text, $notice)
             // sender context.
 
             if (!empty($originalAuthor) && $originalAuthor->nickname == $nickname) {
-
                 $mentioned = $originalAuthor;
-
             } else if (!empty($originalMentions) &&
                        array_key_exists($nickname, $originalMentions)) {
-
                 $mentioned = $originalMentions[$nickname];
             } else {
                 $mentioned = common_relative_profile($sender, $nickname);
             }
 
             if (!empty($mentioned)) {
-
                 $user = User::staticGet('id', $mentioned->id);
 
                 if ($user) {
@@ -830,10 +870,14 @@ function common_linkify($url) {
         } elseif (is_string($longurl_data)) {
             $longurl = $longurl_data;
         } else {
-            throw new ServerException("Can't linkify url '$url'");
+            // Unable to reach the server to verify contents, etc
+            // Just pass the link on through for now.
+            common_log(LOG_ERR, "Can't linkify url '$url'");
+            $longurl = $url;
         }
     }
-    $attrs = array('href' => $canon, 'title' => $longurl, 'rel' => 'external');
+
+    $attrs = array('href' => $canon, 'title' => $longurl);
 
     $is_attachment = false;
     $attachment_id = null;
@@ -869,16 +913,65 @@ function common_linkify($url) {
         $attrs['id'] = "attachment-{$attachment_id}";
     }
 
+    // Whether to nofollow
+
+    $nf = common_config('nofollow', 'external');
+
+    if ($nf == 'never') {
+        $attrs['rel'] = 'external';
+    } else {
+        $attrs['rel'] = 'nofollow external';
+    }
+
     return XMLStringer::estring('a', $attrs, $url);
 }
 
 function common_shorten_links($text, $always = false)
 {
-    $maxLength = Notice::maxContent();
-    if (!$always && ($maxLength == 0 || mb_strlen($text) <= $maxLength)) return $text;
-    return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
+    common_debug("common_shorten_links() called");
+
+    $user = common_current_user();
+
+    $maxLength = User_urlshortener_prefs::maxNoticeLength($user);
+
+    common_debug("maxLength = $maxLength");
+
+    if ($always || mb_strlen($text) > $maxLength) {
+        common_debug("Forcing shortening");
+        return common_replace_urls_callback($text, array('File_redirection', 'forceShort'));
+    } else {
+        common_debug("Not forcing shortening");
+        return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
+    }
 }
 
+/**
+ * Very basic stripping of invalid UTF-8 input text.
+ *
+ * @param string $str
+ * @return mixed string or null if invalid input
+ *
+ * @todo ideally we should drop bad chars, and maybe do some of the checks
+ *       from common_xml_safe_str. But we can't strip newlines, etc.
+ * @todo Unicode normalization might also be useful, but not needed now.
+ */
+function common_validate_utf8($str)
+{
+    // preg_replace will return NULL on invalid UTF-8 input.
+    //
+    // Note: empty regex //u also caused NULL return on some
+    // production machines, but none of our test machines.
+    //
+    // This should be replaced with a more reliable check.
+    return preg_replace('/\x00/u', '', $str);
+}
+
+/**
+ * Make sure an arbitrary string is safe for output in XML as a single line.
+ *
+ * @param string $str
+ * @return string
+ */
 function common_xml_safe_str($str)
 {
     // Replace common eol and extra whitespace input chars
@@ -910,8 +1003,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();
         $url = common_local_url('showstream',
-                                array('nickname' => common_config('singleuser', 'nickname'),
+                                array('nickname' => $user->nickname,
                                       'tag' => $canonical));
     } else {
         $url = common_local_url('tag', array('tag' => $canonical));
@@ -944,7 +1038,7 @@ function common_group_link($sender_id, $nickname)
         $attrs = array('href' => $group->permalink(),
                        'class' => 'url');
         if (!empty($group->fullname)) {
-            $attrs['title'] = $group->fullname . ' (' . $group->nickname . ')';
+            $attrs['title'] = $group->getFancyName();
         }
         $xs = new XMLStringer();
         $xs->elementStart('span', 'vcard');
@@ -1015,8 +1109,17 @@ function common_local_url($action, $args=null, $params=null, $fragment=null, $ad
 
 function common_is_sensitive($action)
 {
-    static $sensitive = array('login', 'register', 'passwordsettings',
-                              'twittersettings', 'api');
+    static $sensitive = array(
+        'login',
+        'register',
+        'passwordsettings',
+        'api',
+        'ApiOauthRequestToken',
+        'ApiOauthAccessToken',
+        'ApiOauthAuthorize',
+        'ApiOauthPin',
+        'showapplication'
+    );
     $ssl = null;
 
     if (Event::handle('SensitiveAction', array($action, &$ssl))) {
@@ -1102,30 +1205,30 @@ function common_date_string($dt)
         // TRANS: Used in notices to indicate when the notice was made compared to now.
         return _('about a minute ago');
     } else if ($diff < 3300) {
-        // XXX: should support plural.
+        $minutes = round($diff/60);
         // TRANS: Used in notices to indicate when the notice was made compared to now.
-        return sprintf(_('about %d minutes ago'), round($diff/60));
+        return sprintf( ngettext('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) {
-        // XXX: should support plural.
+        $hours = round($diff/3600);
         // TRANS: Used in notices to indicate when the notice was made compared to now.
-        return sprintf(_('about %d hours ago'), round($diff/3600));
+        return sprintf( ngettext('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) {
-        // XXX: should support plural.
+        $days = round($diff/(24*3600));
         // TRANS: Used in notices to indicate when the notice was made compared to now.
-        return sprintf(_('about %d days ago'), round($diff/(24*3600)));
+        return sprintf( ngettext('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) {
-        // XXX: should support plural.
+        $months = round($diff/(30*24*3600));
         // TRANS: Used in notices to indicate when the notice was made compared to now.
-        return sprintf(_('about %d months ago'), round($diff/(30*24*3600)));
+        return sprintf( ngettext('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');
@@ -1223,11 +1326,6 @@ function common_redirect($url, $code=307)
     exit;
 }
 
-function common_broadcast_notice($notice, $remote=false)
-{
-    // DO NOTHING!
-}
-
 // Stick the notice on the queue
 
 function common_enqueue_notice($notice)
@@ -1243,18 +1341,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))) {
@@ -1291,8 +1380,9 @@ function common_profile_url($nickname)
                             null, null, false);
 }
 
-// Should make up a reasonable root URL
-
+/**
+ * Should make up a reasonable root URL
+ */
 function common_root_url($ssl=false)
 {
     $url = common_path('', $ssl, false);
@@ -1303,9 +1393,10 @@ function common_root_url($ssl=false)
     return $url;
 }
 
-// returns $bytes bytes of random data as a hexadecimal string
-// "good" here is a goal and not a guarantee
-
+/**
+ * returns $bytes bytes of random data as a hexadecimal string
+ * "good" here is a goal and not a guarantee
+ */
 function common_good_rand($bytes)
 {
     // XXX: use random.org...?
@@ -1341,13 +1432,13 @@ function common_mtrand($bytes)
 /**
  * Record the given URL as the return destination for a future
  * form submission, to be read by common_get_returnto().
- * 
+ *
  * @param string $url
- * 
+ *
  * @fixme as a session-global setting, this can allow multiple forms
  * to conflict and overwrite each others' returnto destinations if
  * the user has multiple tabs or windows open.
- * 
+ *
  * Should refactor to index with a token or otherwise only pass the
  * data along its intended path.
  */
@@ -1360,13 +1451,13 @@ function common_set_returnto($url)
 /**
  * Fetch a return-destination URL previously recorded by
  * common_set_returnto().
- * 
+ *
  * @return mixed URL string or null
- * 
+ *
  * @fixme as a session-global setting, this can allow multiple forms
  * to conflict and overwrite each others' returnto destinations if
  * the user has multiple tabs or windows open.
- * 
+ *
  * Should refactor to index with a token or otherwise only pass the
  * data along its intended path.
  */
@@ -1451,7 +1542,12 @@ function common_log_db_error(&$object, $verb, $filename=null)
 {
     $objstr = common_log_objstring($object);
     $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
-    common_log(LOG_ERR, $last_error->message . '(' . $verb . ' on ' . $objstr . ')', $filename);
+    if (is_object($last_error)) {
+        $msg = $last_error->message;
+    } else {
+        $msg = 'Unknown error (' . var_export($last_error, true) . ')';
+    }
+    common_log(LOG_ERR, $msg . '(' . $verb . ' on ' . $objstr . ')', $filename);
 }
 
 function common_log_objstring(&$object)
@@ -1493,7 +1589,7 @@ function common_valid_tag($tag)
  * Determine if given domain or address literal is valid
  * eg for use in JIDs and URLs. Does not check if the domain
  * exists!
- * 
+ *
  * @param string $domain
  * @return boolean valid or not
  */
@@ -1629,19 +1725,25 @@ function common_config($main, $sub)
             array_key_exists($sub, $config[$main])) ? $config[$main][$sub] : false;
 }
 
+/**
+ * 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.
+ *
+ * @param array $from
+ * @return array
+ */
 function common_copy_args($from)
 {
     $to = array();
     $strip = get_magic_quotes_gpc();
     foreach ($from as $k => $v) {
-        if($strip) {
-            if(is_array($v)) {
-                $to[$k] = common_copy_args($v);
-            } else {
-                $to[$k] = stripslashes($v);
-            }
+        if(is_array($v)) {
+            $to[$k] = common_copy_args($v);
         } else {
-            $to[$k] = $v;
+            if ($strip) {
+                $v = stripslashes($v);
+            }
+            $to[$k] = strval(common_validate_utf8($v));
         }
     }
     return $to;
@@ -1779,21 +1881,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)) {
@@ -1824,7 +1911,6 @@ function common_compatible_license($from, $to)
  */
 function common_database_tablename($tablename)
 {
-
   if(common_config('db','quote_identifiers')) {
       $tablename = '"'. $tablename .'"';
   }
@@ -1835,30 +1921,42 @@ 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 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, $force = false)
 {
+    common_debug("Shortening URL '$long_url' (force = $force)");
+
     $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;
+
+    $maxUrlLength = User_urlshortener_prefs::maxUrlLength($user);
+    common_debug("maxUrlLength = $maxUrlLength");
+
+    // $force forces shortening even if it's not strictly needed
+
+    if (mb_strlen($long_url) < $maxUrlLength && !$force) {
+        common_debug("Skipped shortening URL.");
+        return $long_url;
     }
 
-    if(Event::handle('StartShortenUrl', array($long_url,$shortenerName,&$shortenedUrl))){
+    $shortenerName = User_urlshortener_prefs::urlShorteningService($user);
+
+    common_debug("Shortener name = '$shortenerName'");
+
+    if (Event::handle('StartShortenUrl', array($long_url, $shortenerName, &$shortenedUrl))) {
         //URL wasn't shortened, so return the long url
         return $long_url;
-    }else{
+    } else {
         //URL was shortened, so return the result
         return trim($shortenedUrl);
     }