textdomain("statusnet");
}
-
function common_timezone()
{
if (common_logged_in()) {
if ($user) {
if (Event::handle('StartSetUser', array(&$user))) {
- if($user){
+ if (!empty($user)) {
+ if (!$user->hasRight(Right::WEBLOGIN)) {
+ throw new AuthorizationException(_('Not allowed to log in.'));
+ }
common_ensure_session();
$_SESSION['userid'] = $user->id;
$_cur = $user;
$value,
$expiration,
$cookiepath,
- $server);
+ $server,
+ common_config('site', 'ssl')=='always');
}
define('REMEMBERME', 'rememberme');
}
}
-// 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
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);
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();
}
}
- 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
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);
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\<\>\(\)\[\]\{\}\\\'\\\";]+)(?![\@\!\#])'.
'#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);
}
}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);
}
$canon = File_redirection::_canonUrl($url);
- $longurl_data = File_redirection::where($canon);
+ $longurl_data = File_redirection::where($canon, common_config('attachments', 'process_links'));
if (is_array($longurl_data)) {
$longurl = $longurl_data['url'];
} elseif (is_string($longurl_data)) {
$longurl = $url;
}
}
- $attrs = array('href' => $canon, 'title' => $longurl, 'rel' => 'external');
+
+ $attrs = array('href' => $canon, 'title' => $longurl);
$is_attachment = false;
$attachment_id = null;
$f = File::staticGet('url', $longurl);
if (empty($f)) {
- // XXX: this writes to the database. :<
- $f = File::processNew($longurl);
+ if (common_config('attachments', 'process_links')) {
+ // XXX: this writes to the database. :<
+ $f = File::processNew($longurl);
+ }
}
if (!empty($f)) {
- if ($f->getEnclosure() || File_oembed::staticGet('file_id',$f->id)) {
+ if ($f->getEnclosure()) {
$is_attachment = true;
$attachment_id = $f->id;
$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)
+/**
+ * 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'));
+ return common_replace_urls_callback($text, array('File_redirection', 'makeShort'), $user);
}
/**
function common_validate_utf8($str)
{
// preg_replace will return NULL on invalid UTF-8 input.
- return preg_replace('//u', '', $str);
+ //
+ // 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);
}
/**
$canonical = common_canonical_tag($tag);
if (common_config('singleuser', 'enabled')) {
// regular TagAction isn't set up in 1user mode
+ $nickname = User::singleUserNickname();
$url = common_local_url('showstream',
- array('nickname' => common_config('singleuser', 'nickname'),
+ array('nickname' => $nickname,
'tag' => $canonical));
} else {
$url = common_local_url('tag', array('tag' => $canonical));
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);
$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');
}
}
+/**
+ * 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
// 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
function common_is_sensitive($action)
{
- static $sensitive = array('login', 'register', 'passwordsettings', 'api');
+ static $sensitive = array(
+ 'login',
+ 'register',
+ 'passwordsettings',
+ 'api',
+ 'ApiOauthRequestToken',
+ 'ApiOauthAccessToken',
+ 'ApiOauthAuthorize',
+ 'ApiOauthPin',
+ 'showapplication'
+ );
$ssl = null;
if (Event::handle('SensitiveAction', array($action, &$ssl))) {
function common_log($priority, $msg, $filename=null)
{
if(Event::handle('StartLog', array(&$priority, &$msg, &$filename))){
+ $msg = (empty($filename)) ? $msg : basename($filename) . ' - ' . $msg;
$msg = '[' . common_request_id() . '] ' . $msg;
$logfile = common_config('site', 'logfile');
if ($logfile) {
* Length is not considered.
*
* @param string $long_url
+ * @param User $user to specify a particular user's options
* @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)
{
$long_url = trim($long_url);
- $user = common_current_user();
+ 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
$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)");
+ }
+ }
+}