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

Commit 0853ed06 authored by Eric Helgeson's avatar Eric Helgeson

Merge commit 'origin/0.8.x' into 0.9.x

parents 9e611b40 ff6e976d
...@@ -23,4 +23,4 @@ config-*.php ...@@ -23,4 +23,4 @@ config-*.php
good-config.php good-config.php
lac08.log lac08.log
php.log php.log
config.php.*
...@@ -964,9 +964,6 @@ sslserver: use an alternate server name for SSL URLs, like ...@@ -964,9 +964,6 @@ sslserver: use an alternate server name for SSL URLs, like
shorturllength: Length of URL at which URLs in a message exceeding 140 shorturllength: Length of URL at which URLs in a message exceeding 140
characters will be sent to the user's chosen characters will be sent to the user's chosen
shortening service. shortening service.
design: a default design (colors and background) for the site.
Sub-items are: backgroundcolor, contentcolor, sidebarcolor,
textcolor, linkcolor, backgroundimage, disposition.
dupelimit: minimum time allowed for one person to say the same thing dupelimit: minimum time allowed for one person to say the same thing
twice. Default 60s. Anything lower is considered a user twice. Default 60s. Anything lower is considered a user
or UI error. or UI error.
...@@ -1432,6 +1429,20 @@ notify third-party servers of updates. ...@@ -1432,6 +1429,20 @@ notify third-party servers of updates.
notify: an array of URLs for ping endpoints. Default is the empty notify: an array of URLs for ping endpoints. Default is the empty
array (no notification). array (no notification).
design
------
Default design (colors and background) for the site. Actual appearance
depends on the theme. Null values mean to use the theme defaults.
backgroundcolor: Hex color of the site background.
contentcolor: Hex color of the content area background.
sidebarcolor: Hex color of the sidebar background.
textcolor: Hex color of all non-link text.
linkcolor: Hex color of all links.
backgroundimage: Image to use for the background.
disposition: Flags for whether or not to tile the background image.
Plugins Plugins
======= =======
......
...@@ -130,6 +130,7 @@ class ApiAction extends Action ...@@ -130,6 +130,7 @@ class ApiAction extends Action
'laconica/wadl', 'laconica/wadl',
'tags/timeline', 'tags/timeline',
'oembed/oembed', 'oembed/oembed',
'groups/show',
'groups/timeline'); 'groups/timeline');
static $bareauth = array('statuses/user_timeline', static $bareauth = array('statuses/user_timeline',
......
...@@ -167,6 +167,8 @@ class ConversationTree extends NoticeList ...@@ -167,6 +167,8 @@ class ConversationTree extends NoticeList
function _buildTree() function _buildTree()
{ {
$cnt = 0;
$this->tree = array(); $this->tree = array();
$this->table = array(); $this->table = array();
......
...@@ -229,7 +229,7 @@ class PublicAction extends Action ...@@ -229,7 +229,7 @@ class PublicAction extends Action
// $top->show(); // $top->show();
$pop = new PopularNoticeSection($this); $pop = new PopularNoticeSection($this);
$pop->show(); $pop->show();
$gbp = new GroupsByPostsSection($this); $gbp = new GroupsByMembersSection($this);
$gbp->show(); $gbp->show();
$feat = new FeaturedUsersSection($this); $feat = new FeaturedUsersSection($this);
$feat->show(); $feat->show();
......
...@@ -207,32 +207,10 @@ class TwitapifavoritesAction extends TwitterapiAction ...@@ -207,32 +207,10 @@ class TwitapifavoritesAction extends TwitterapiAction
$other = User::staticGet('id', $notice->profile_id); $other = User::staticGet('id', $notice->profile_id);
if ($other && $other->id != $user->id) { if ($other && $other->id != $user->id) {
if ($other->email && $other->emailnotifyfav) { if ($other->email && $other->emailnotifyfav) {
$this->notify_mail($other, $user, $notice); mail_notify_fave($other, $user, $notice);
} }
# XXX: notify by IM # XXX: notify by IM
# XXX: notify by SMS # XXX: notify by SMS
} }
} }
}
function notify_mail($other, $user, $notice)
{
$profile = $user->getProfile();
$bestname = $profile->getBestName();
$subject = sprintf(_('%s added your notice as a favorite'), $bestname);
$body = sprintf(_("%1\$s just added your notice from %2\$s as one of their favorites.\n\n" .
"In case you forgot, you can see the text of your notice here:\n\n" .
"%3\$s\n\n" .
"You can see the list of %1\$s's favorites here:\n\n" .
"%4\$s\n\n" .
"Faithfully yours,\n" .
"%5\$s\n"),
$bestname,
common_exact_date($notice->created),
common_local_url('shownotice', array('notice' => $notice->id)),
common_local_url('showfavorites', array('nickname' => $user->nickname)),
common_config('site', 'name'));
mail_to_user($other, $subject, $body);
}
}
\ No newline at end of file
...@@ -51,6 +51,32 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; ...@@ -51,6 +51,32 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
class TwitapigroupsAction extends TwitterapiAction class TwitapigroupsAction extends TwitterapiAction
{ {
function show($args, $apidata)
{
parent::handle($args);
common_debug("in groups api action");
$this->auth_user = $apidata['user'];
$group = $this->get_group($apidata['api_arg'], $apidata);
if (empty($group)) {
$this->clientError('Not Found', 404, $apidata['content-type']);
return;
}
switch($apidata['content-type']) {
case 'xml':
$this->show_single_xml_group($group);
break;
case 'json':
$this->show_single_json_group($group);
break;
default:
$this->clientError(_('API method not found!'), $code = 404);
}
}
function timeline($args, $apidata) function timeline($args, $apidata)
{ {
parent::handle($args); parent::handle($args);
...@@ -88,8 +114,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; ...@@ -88,8 +114,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
$this->show_xml_timeline($notice); $this->show_xml_timeline($notice);
break; break;
case 'rss': case 'rss':
$this->show_rss_timeline($notice, $title, $link, $this->show_rss_timeline($notice, $title, $link, $subtitle);
$subtitle, $suplink);
break; break;
case 'atom': case 'atom':
if (isset($apidata['api_arg'])) { if (isset($apidata['api_arg'])) {
...@@ -101,7 +126,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; ...@@ -101,7 +126,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
'api/laconica/groups/timeline.atom'; 'api/laconica/groups/timeline.atom';
} }
$this->show_atom_timeline($notice, $title, $id, $link, $this->show_atom_timeline($notice, $title, $id, $link,
$subtitle, $suplink, $selfuri); $subtitle, null, $selfuri);
break; break;
case 'json': case 'json':
$this->show_json_timeline($notice); $this->show_json_timeline($notice);
......
...@@ -88,8 +88,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; ...@@ -88,8 +88,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
$this->show_xml_timeline($notice); $this->show_xml_timeline($notice);
break; break;
case 'rss': case 'rss':
$this->show_rss_timeline($notice, $title, $link, $this->show_rss_timeline($notice, $title, $link, $subtitle);
$subtitle, $suplink);
break; break;
case 'atom': case 'atom':
if (isset($apidata['api_arg'])) { if (isset($apidata['api_arg'])) {
...@@ -101,7 +100,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; ...@@ -101,7 +100,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
'api/laconica/tags/timeline.atom'; 'api/laconica/tags/timeline.atom';
} }
$this->show_atom_timeline($notice, $title, $id, $link, $this->show_atom_timeline($notice, $title, $id, $link,
$subtitle, $suplink, $selfuri); $subtitle, null, $selfuri);
break; break;
case 'json': case 'json':
$this->show_json_timeline($notice); $this->show_json_timeline($notice);
......
...@@ -55,26 +55,38 @@ class Design extends Memcached_DataObject ...@@ -55,26 +55,38 @@ class Design extends Memcached_DataObject
function showCSS($out) function showCSS($out)
{ {
try { $css = '';
$bgcolor = new WebColor($this->backgroundcolor); $bgcolor = Design::toWebColor($this->backgroundcolor);
$ccolor = new WebColor($this->contentcolor);
$sbcolor = new WebColor($this->sidebarcolor);
$tcolor = new WebColor($this->textcolor);
$lcolor = new WebColor($this->linkcolor);
} catch (WebColorException $e) { if (!empty($bgcolor)) {
// This shouldn't happen $css .= 'body { background-color: #' . $bgcolor->hexValue() . ' }' . "\n";
common_log(LOG_ERR, "Unable to create color for design $id.", }
__FILE__);
$ccolor = Design::toWebColor($this->contentcolor);
if (!empty($ccolor)) {
$css .= '#content, #site_nav_local_views .current a { background-color: #';
$css .= $ccolor->hexValue() . '} '."\n";
}
$sbcolor = Design::toWebColor($this->sidebarcolor);
if (!empty($sbcolor)) {
$css .= '#aside_primary { background-color: #'. $sbcolor->hexValue() . ' }' . "\n";
}
$tcolor = Design::toWebColor($this->textcolor);
if (!empty($tcolor)) {
$css .= 'html body { color: #'. $tcolor->hexValue() . ' }'. "\n";
} }
$css = 'body { background-color: #' . $bgcolor->hexValue() . ' }' . "\n"; $lcolor = Design::toWebColor($this->linkcolor);
$css .= '#content, #site_nav_local_views .current a { background-color: #';
$css .= $ccolor->hexValue() . '} '."\n"; if (!empty($lcolor)) {
$css .= '#aside_primary { background-color: #'. $sbcolor->hexValue() . ' }' . "\n"; $css .= 'a { color: #' . $lcolor->hexValue() . ' }' . "\n";
$css .= 'html body { color: #'. $tcolor->hexValue() . ' }'. "\n"; }
$css .= 'a { color: #' . $lcolor->hexValue() . ' }' . "\n";
if (!empty($this->backgroundimage) && if (!empty($this->backgroundimage) &&
$this->disposition & BACKGROUND_ON) { $this->disposition & BACKGROUND_ON) {
...@@ -88,8 +100,25 @@ class Design extends Memcached_DataObject ...@@ -88,8 +100,25 @@ class Design extends Memcached_DataObject
'); ' . $repeat . ' background-attachment:fixed; }' . "\n"; '); ' . $repeat . ' background-attachment:fixed; }' . "\n";
} }
$out->element('style', array('type' => 'text/css'), $css); if (0 != mb_strlen($css)) {
$out->element('style', array('type' => 'text/css'), $css);
}
}
static function toWebColor($color)
{
if (is_null($color)) {
return null;
}
try {
return new WebColor($color);
} catch (WebColorException $e) {
// This shouldn't happen
common_log(LOG_ERR, "Unable to create color for design $id.",
__FILE__);
return null;
}
} }
static function filename($id, $extension, $extra=null) static function filename($id, $extension, $extra=null)
...@@ -152,4 +181,33 @@ class Design extends Memcached_DataObject ...@@ -152,4 +181,33 @@ class Design extends Memcached_DataObject
} }
} }
/**
* Return a design object based on the configured site design.
*
* @return Design a singleton design object for the site.
*/
static function siteDesign()
{
static $siteDesign = null;
if (empty($siteDesign)) {
$siteDesign = new Design();
$attrs = array('backgroundcolor',
'contentcolor',
'sidebarcolor',
'textcolor',
'linkcolor',
'backgroundimage',
'disposition');
foreach ($attrs as $attr) {
$siteDesign->$attr = common_config('design', $attr);
}
}
return $siteDesign;
}
} }
...@@ -93,7 +93,6 @@ class File extends Memcached_DataObject ...@@ -93,7 +93,6 @@ class File extends Memcached_DataObject
if (empty($file)) { if (empty($file)) {
$file_redir = File_redirection::staticGet('url', $given_url); $file_redir = File_redirection::staticGet('url', $given_url);
if (empty($file_redir)) { if (empty($file_redir)) {
common_debug("processNew() '$given_url' not a known redirect.\n");
$redir_data = File_redirection::where($given_url); $redir_data = File_redirection::where($given_url);
$redir_url = $redir_data['url']; $redir_url = $redir_data['url'];
if ($redir_url === $given_url) { if ($redir_url === $given_url) {
...@@ -114,7 +113,9 @@ class File extends Memcached_DataObject ...@@ -114,7 +113,9 @@ class File extends Memcached_DataObject
if (empty($x)) { if (empty($x)) {
$x = File::staticGet($file_id); $x = File::staticGet($file_id);
if (empty($x)) die('Impossible!'); if (empty($x)) {
throw new ServerException("Robin thinks something is impossible.");
}
} }
File_to_post::processNew($file_id, $notice_id); File_to_post::processNew($file_id, $notice_id);
......
...@@ -116,15 +116,14 @@ class Notice extends Memcached_DataObject ...@@ -116,15 +116,14 @@ class Notice extends Memcached_DataObject
if (!$count) { if (!$count) {
return true; return true;
} }
//turn each into their canonical tag //turn each into their canonical tag
//this is needed to remove dupes before saving e.g. #hash.tag = #hashtag //this is needed to remove dupes before saving e.g. #hash.tag = #hashtag
$hashtags = array(); $hashtags = array();
for($i=0; $i<count($match[1]); $i++) { for($i=0; $i<count($match[1]); $i++) {
$hashtags[] = common_canonical_tag($match[1][$i]); $hashtags[] = common_canonical_tag($match[1][$i]);
} }
/* Add them to the database */ /* Add them to the database */
foreach(array_unique($hashtags) as $hashtag) { foreach(array_unique($hashtags) as $hashtag) {
/* elide characters we don't want in the tag */ /* elide characters we don't want in the tag */
...@@ -197,29 +196,30 @@ class Notice extends Memcached_DataObject ...@@ -197,29 +196,30 @@ class Notice extends Memcached_DataObject
$notice->is_local = $is_local; $notice->is_local = $is_local;
} }
$notice->query('BEGIN');
$notice->reply_to = $reply_to;
if (!empty($created)) { if (!empty($created)) {
$notice->created = $created; $notice->created = $created;
} else { } else {
$notice->created = common_sql_now(); $notice->created = common_sql_now();
} }
$notice->content = $final; $notice->content = $final;
$notice->rendered = common_render_content($final, $notice); $notice->rendered = common_render_content($final, $notice);
$notice->source = $source; $notice->source = $source;
$notice->uri = $uri; $notice->uri = $uri;
if (!empty($reply_to)) { $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final);
$reply_notice = Notice::staticGet('id', $reply_to);
if (!empty($reply_notice)) { if (!empty($notice->reply_to)) {
$notice->reply_to = $reply_to; $reply = Notice::staticGet('id', $notice->reply_to);
$notice->conversation = $reply_notice->conversation; $notice->conversation = $reply->conversation;
}
} }
if (Event::handle('StartNoticeSave', array(&$notice))) { if (Event::handle('StartNoticeSave', array(&$notice))) {
// XXX: some of these functions write to the DB
$notice->query('BEGIN');
$id = $notice->insert(); $id = $notice->insert();
if (!$id) { if (!$id) {
...@@ -227,18 +227,33 @@ class Notice extends Memcached_DataObject ...@@ -227,18 +227,33 @@ class Notice extends Memcached_DataObject
return _('Problem saving notice.'); return _('Problem saving notice.');
} }
# Update the URI after the notice is in the database // Update ID-dependent columns: URI, conversation
if (!$uri) {
$orig = clone($notice); $orig = clone($notice);
$changed = false;
if (empty($uri)) {
$notice->uri = common_notice_uri($notice); $notice->uri = common_notice_uri($notice);
$changed = true;
}
// If it's not part of a conversation, it's
// the beginning of a new conversation.
if (empty($notice->conversation)) {
$notice->conversation = $notice->id;
$changed = true;
}
if ($changed) {
if (!$notice->update($orig)) { if (!$notice->update($orig)) {
common_log_db_error($notice, 'UPDATE', __FILE__); common_log_db_error($notice, 'UPDATE', __FILE__);
return _('Problem saving notice.'); return _('Problem saving notice.');
} }
} }
# XXX: do we need to change this for remote users? // XXX: do we need to change this for remote users?
$notice->saveReplies(); $notice->saveReplies();
$notice->saveTags(); $notice->saveTags();
...@@ -246,8 +261,13 @@ class Notice extends Memcached_DataObject ...@@ -246,8 +261,13 @@ class Notice extends Memcached_DataObject
$notice->addToInboxes(); $notice->addToInboxes();
$notice->saveUrls(); $notice->saveUrls();
// FIXME: why do we have to re-render the content?
// Remove this if it's not necessary.
$orig2 = clone($notice); $orig2 = clone($notice);
$notice->rendered = common_render_content($final, $notice);
$notice->rendered = common_render_content($final, $notice);
if (!$notice->update($orig2)) { if (!$notice->update($orig2)) {
common_log_db_error($notice, 'UPDATE', __FILE__); common_log_db_error($notice, 'UPDATE', __FILE__);
return _('Problem saving notice.'); return _('Problem saving notice.');
...@@ -304,9 +324,9 @@ class Notice extends Memcached_DataObject ...@@ -304,9 +324,9 @@ class Notice extends Memcached_DataObject
$notice->profile_id = $profile_id; $notice->profile_id = $profile_id;
$notice->content = $content; $notice->content = $content;
if (common_config('db','type') == 'pgsql') if (common_config('db','type') == 'pgsql')
$notice->whereAdd('extract(epoch from now() - created) < ' . common_config('site', 'dupelimit')); $notice->whereAdd('extract(epoch from now() - created) < ' . common_config('site', 'dupelimit'));
else else
$notice->whereAdd('now() - created < ' . common_config('site', 'dupelimit')); $notice->whereAdd('now() - created < ' . common_config('site', 'dupelimit'));
$cnt = $notice->count(); $cnt = $notice->count();
return ($cnt == 0); return ($cnt == 0);
...@@ -920,14 +940,14 @@ class Notice extends Memcached_DataObject ...@@ -920,14 +940,14 @@ class Notice extends Memcached_DataObject
{ {
$user = new User(); $user = new User();
if(common_config('db','quote_identifiers')) if(common_config('db','quote_identifiers'))
$user_table = '"user"'; $user_table = '"user"';
else $user_table = 'user'; else $user_table = 'user';
$qry = $qry =
'SELECT id ' . 'SELECT id ' .
'FROM '. $user_table .' JOIN subscription '. 'FROM '. $user_table .' JOIN subscription '.
'ON '. $user_table .'.id = subscription.subscriber ' . 'ON '. $user_table .'.id = subscription.subscriber ' .
'WHERE subscription.subscribed = %d '; 'WHERE subscription.subscribed = %d ';
$user->query(sprintf($qry, $this->profile_id)); $user->query(sprintf($qry, $this->profile_id));
...@@ -1045,16 +1065,6 @@ class Notice extends Memcached_DataObject ...@@ -1045,16 +1065,6 @@ class Notice extends Memcached_DataObject
if (!$recipient) { if (!$recipient) {
continue; continue;
} }
if ($i == 0 && ($recipient->id != $sender->id) && !$this->reply_to) { // Don't save reply to self
$reply_for = $recipient;
$recipient_notice = $reply_for->getCurrentNotice();
if ($recipient_notice) {
$orig = clone($this);
$this->reply_to = $recipient_notice->id;
$this->conversation = $recipient_notice->conversation;
$this->update($orig);
}
}
// Don't save replies from blocked profile to local user // Don't save replies from blocked profile to local user
$recipient_user = User::staticGet('id', $recipient->id); $recipient_user = User::staticGet('id', $recipient->id);
if ($recipient_user && $recipient_user->hasBlocked($sender)) { if ($recipient_user && $recipient_user->hasBlocked($sender)) {
...@@ -1101,14 +1111,6 @@ class Notice extends Memcached_DataObject ...@@ -1101,14 +1111,6 @@ class Notice extends Memcached_DataObject
} }
} }
// If it's not a reply, make it the root of a new conversation
if (empty($this->conversation)) {
$orig = clone($this);
$this->conversation = $this->id;