diff options
author | Evan Prodromou <git@evanprodromou.name> | 2009-01-23 08:58:31 +0100 |
---|---|---|
committer | Evan Prodromou <git@evanprodromou.name> | 2009-01-23 08:58:31 +0100 |
commit | a7c85bebd5be9ea019a8c80d74730d7eb28d4651 (patch) | |
tree | c3fdf9575a342624bc71aad1d439ae73594f558c /classes | |
parent | 4873277b58941ae6ec16543f437f4267ccab5ac0 (diff) | |
parent | 7aa496cd8a939960eeaf79f3397f6fe94097e047 (diff) |
Merge branch 'master' of /var/www/mublog
Conflicts:
actions/api.php
actions/deletenotice.php
actions/recoverpassword.php
actions/remotesubscribe.php
actions/tag.php
actions/tagrss.php
actions/twitapiaccount.php
actions/twitapiusers.php
classes/Notice.php
classes/User.php
lib/common.php
lib/language.php
lib/subs.php
lib/twitterapi.php
lib/util.php
scripts/inbox_users.php
scripts/update_translations.php
Merged development trunk into laconica head. woohoo!
Diffstat (limited to 'classes')
37 files changed, 2894 insertions, 2046 deletions
diff --git a/classes/Avatar.php b/classes/Avatar.php index 901c47c51..9ae920647 100644 --- a/classes/Avatar.php +++ b/classes/Avatar.php @@ -21,75 +21,121 @@ class Avatar extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Avatar',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Avatar',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - # We clean up the file, too + # We clean up the file, too - function delete() { - $filename = $this->filename; - if (parent::delete()) { - @unlink(common_avatar_path($filename)); - } - } + function delete() + { + $filename = $this->filename; + if (parent::delete()) { + @unlink(common_avatar_path($filename)); + } + } - # Create and save scaled version of this avatar - # XXX: maybe break into different methods + # Create and save scaled version of this avatar + # XXX: maybe break into different methods - function scale($size) { + function scale($size) + { - $image_s = imagecreatetruecolor($size, $size); - $image_a = $this->to_image(); - $square = min($this->width, $this->height); + $image_s = imagecreatetruecolor($size, $size); + $image_a = $this->to_image(); + $square = min($this->width, $this->height); imagecolortransparent($image_s, imagecolorallocate($image_s, 0, 0, 0)); imagealphablending($image_s, false); imagesavealpha($image_s, true); - imagecopyresampled($image_s, $image_a, 0, 0, 0, 0, - $size, $size, $square, $square); - - $ext = ($this->mediattype == 'image/jpeg') ? ".jpeg" : ".png"; - - $filename = common_avatar_filename($this->profile_id, $ext, $size, common_timestamp()); - - if ($this->mediatype == 'image/jpeg') { - imagejpeg($image_s, common_avatar_path($filename)); - } else { - imagepng($image_s, common_avatar_path($filename)); - } - - $scaled = DB_DataObject::factory('avatar'); - $scaled->profile_id = $this->profile_id; - $scaled->width = $size; - $scaled->height = $size; - $scaled->original = false; - $scaled->mediatype = ($this->mediattype == 'image/jpeg') ? 'image/jpeg' : 'image/png'; - $scaled->filename = $filename; - $scaled->url = common_avatar_url($filename); - $scaled->created = DB_DataObject_Cast::dateTime(); # current time - - if ($scaled->insert()) { - return $scaled; - } else { - return NULL; - } - } - - function to_image() { - $filepath = common_avatar_path($this->filename); - if ($this->mediatype == 'image/gif') { - return imagecreatefromgif($filepath); - } else if ($this->mediatype == 'image/jpeg') { - return imagecreatefromjpeg($filepath); - } else if ($this->mediatype == 'image/png') { - return imagecreatefrompng($filepath); - } else { - return NULL; - } - } - - function &pkeyGet($kv) { - return Memcached_DataObject::pkeyGet('Avatar', $kv); - } + imagecopyresampled($image_s, $image_a, 0, 0, 0, 0, + $size, $size, $square, $square); + + $ext = ($this->mediattype == 'image/jpeg') ? ".jpeg" : ".png"; + + $filename = common_avatar_filename($this->profile_id, $ext, $size, common_timestamp()); + + if ($this->mediatype == 'image/jpeg') { + imagejpeg($image_s, common_avatar_path($filename)); + } else { + imagepng($image_s, common_avatar_path($filename)); + } + + $scaled = DB_DataObject::factory('avatar'); + $scaled->profile_id = $this->profile_id; + $scaled->width = $size; + $scaled->height = $size; + $scaled->original = false; + $scaled->mediatype = ($this->mediattype == 'image/jpeg') ? 'image/jpeg' : 'image/png'; + $scaled->filename = $filename; + $scaled->url = common_avatar_url($filename); + $scaled->created = DB_DataObject_Cast::dateTime(); # current time + + if ($scaled->insert()) { + return $scaled; + } else { + return null; + } + } + + function scale_and_crop($size, $x, $y, $w, $h) + { + + $image_s = imagecreatetruecolor($size, $size); + $image_a = $this->to_image(); + + # Retain alpha channel info if possible for .pngs + $background = imagecolorallocate($image_s, 0, 0, 0); + ImageColorTransparent($image_s, $background); + imagealphablending($image_s, false); + + imagecopyresized($image_s, $image_a, 0, 0, $x, $y, $size, $size, $w, $h); + + $ext = ($this->mediattype == 'image/jpeg') ? ".jpeg" : ".png"; + + $filename = common_avatar_filename($this->profile_id, $ext, $size, common_timestamp()); + + if ($this->mediatype == 'image/jpeg') { + imagejpeg($image_s, common_avatar_path($filename)); + } else { + imagepng($image_s, common_avatar_path($filename)); + } + + $cropped = DB_DataObject::factory('avatar'); + $cropped->profile_id = $this->profile_id; + $cropped->width = $size; + $cropped->height = $size; + $cropped->original = false; + $cropped->mediatype = ($this->mediattype == 'image/jpeg') ? 'image/jpeg' : 'image/png'; + $cropped->filename = $filename; + $cropped->url = common_avatar_url($filename); + $cropped->created = DB_DataObject_Cast::dateTime(); # current time + + if ($cropped->insert()) { + return $cropped; + } else { + return NULL; + } + } + + function to_image() + { + $filepath = common_avatar_path($this->filename); + if ($this->mediatype == 'image/gif') { + return imagecreatefromgif($filepath); + } else if ($this->mediatype == 'image/jpeg') { + return imagecreatefromjpeg($filepath); + } else if ($this->mediatype == 'image/png') { + return imagecreatefrompng($filepath); + } else { + return NULL; + } + } + + function &pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Avatar', $kv); + } + } diff --git a/classes/Channel.php b/classes/Channel.php index bcc0c36b5..2e3e4e8d4 100644 --- a/classes/Channel.php +++ b/classes/Channel.php @@ -19,182 +19,213 @@ if (!defined('LACONICA')) { exit(1); } -class Channel { - - function on($user) { - return false; - } - - function off($user) { - return false; - } - - function output($user, $text) { - return false; - } - - function error($user, $text) { - return false; - } - - function source() { - return NULL; - } +class Channel +{ + + function on($user) + { + return false; + } + + function off($user) + { + return false; + } + + function output($user, $text) + { + return false; + } + + function error($user, $text) + { + return false; + } + + function source() + { + return null; + } } -class XMPPChannel extends Channel { - - var $conn = NULL; - - function source() { - return 'xmpp'; - } - - function __construct($conn) { - $this->conn = $conn; - } - - function on($user) { - return $this->set_notify($user, 1); - } - - function off($user) { - return $this->set_notify($user, 0); - } - - function output($user, $text) { - $text = '['.common_config('site', 'name') . '] ' . $text; - jabber_send_message($user->jabber, $text); - } - - function error($user, $text) { - $text = '['.common_config('site', 'name') . '] ' . $text; - jabber_send_message($user->jabber, $text); - } - - function set_notify(&$user, $notify) { - $orig = clone($user); - $user->jabbernotify = $notify; - $result = $user->update($orig); - if (!$result) { - $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); - common_log(LOG_ERR, - 'Could not set notify flag to ' . $notify . - ' for user ' . common_log_objstring($user) . - ': ' . $last_error->message); - return false; - } else { - common_log(LOG_INFO, - 'User ' . $user->nickname . ' set notify flag to ' . $notify); - return true; - } - } +class XMPPChannel extends Channel +{ + + var $conn = null; + + function source() + { + return 'xmpp'; + } + + function __construct($conn) + { + $this->conn = $conn; + } + + function on($user) + { + return $this->set_notify($user, 1); + } + + function off($user) + { + return $this->set_notify($user, 0); + } + + function output($user, $text) + { + $text = '['.common_config('site', 'name') . '] ' . $text; + jabber_send_message($user->jabber, $text); + } + + function error($user, $text) + { + $text = '['.common_config('site', 'name') . '] ' . $text; + jabber_send_message($user->jabber, $text); + } + + function set_notify(&$user, $notify) + { + $orig = clone($user); + $user->jabbernotify = $notify; + $result = $user->update($orig); + if (!$result) { + $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); + common_log(LOG_ERR, + 'Could not set notify flag to ' . $notify . + ' for user ' . common_log_objstring($user) . + ': ' . $last_error->message); + return false; + } else { + common_log(LOG_INFO, + 'User ' . $user->nickname . ' set notify flag to ' . $notify); + return true; + } + } } -class WebChannel extends Channel { - - function source() { - return 'web'; - } - - function on($user) { - return false; - } - - function off($user) { - return false; - } - - function output($user, $text) { - # XXX: buffer all output and send it at the end - # XXX: even better, redirect to appropriate page - # depending on what command was run - common_show_header(_('Command results')); - common_element('p', NULL, $text); - common_show_footer(); - } - - function error($user, $text) { - common_user_error($text); - } +class WebChannel extends Channel +{ + + function source() + { + return 'web'; + } + + function on($user) + { + return false; + } + + function off($user) + { + return false; + } + + function output($user, $text) + { + # XXX: buffer all output and send it at the end + # XXX: even better, redirect to appropriate page + # depending on what command was run + common_show_header(_('Command results')); + common_element('p', null, $text); + common_show_footer(); + } + + function error($user, $text) + { + common_user_error($text); + } } -class AjaxWebChannel extends WebChannel { - - function output($user, $text) { - common_start_html('text/xml;charset=utf-8', true); - common_element_start('head'); - common_element('title', null, _('Command results')); - common_element_end('head'); - common_element_start('body'); - common_element('p', array('id' => 'command_result'), $text); - common_element_end('body'); - common_element_end('html'); - } - - function error($user, $text) { - common_start_html('text/xml;charset=utf-8', true); - common_element_start('head'); - common_element('title', null, _('Ajax Error')); - common_element_end('head'); - common_element_start('body'); - common_element('p', array('id' => 'error'), $text); - common_element_end('body'); - common_element_end('html'); - } +class AjaxWebChannel extends WebChannel +{ + + function output($user, $text) + { + common_start_html('text/xml;charset=utf-8', true); + common_element_start('head'); + common_element('title', null, _('Command results')); + common_element_end('head'); + common_element_start('body'); + common_element('p', array('id' => 'command_result'), $text); + common_element_end('body'); + common_element_end('html'); + } + + function error($user, $text) + { + common_start_html('text/xml;charset=utf-8', true); + common_element_start('head'); + common_element('title', null, _('Ajax Error')); + common_element_end('head'); + common_element_start('body'); + common_element('p', array('id' => 'error'), $text); + common_element_end('body'); + common_element_end('html'); + } } -class MailChannel extends Channel { - - var $addr = NULL; - - function source() { - return 'mail'; - } - - function __construct($addr=NULL) { - $this->addr = $addr; - } - - function on($user) { - return $this->set_notify($user, 1); - } - - function off($user) { - return $this->set_notify($user, 0); - } - - function output($user, $text) { - - $headers['From'] = $user->incomingemail; - $headers['To'] = $this->addr; - - $headers['Subject'] = _('Command complete'); - - return mail_send(array($this->addr), $headers, $text); - } - - function error($user, $text) { - - $headers['From'] = $user->incomingemail; - $headers['To'] = $this->addr; - - $headers['Subject'] = _('Command failed'); - - return mail_send(array($this->addr), $headers, $text); - } - - function set_notify($user, $value) { - $orig = clone($user); - $user->smsnotify = $value; - $result = $user->update($orig); - if (!$result) { - common_log_db_error($user, 'UPDATE', __FILE__); - return false; - } - return true; - } +class MailChannel extends Channel +{ + + var $addr = null; + + function source() + { + return 'mail'; + } + + function __construct($addr=null) + { + $this->addr = $addr; + } + + function on($user) + { + return $this->set_notify($user, 1); + } + + function off($user) + { + return $this->set_notify($user, 0); + } + + function output($user, $text) + { + + $headers['From'] = $user->incomingemail; + $headers['To'] = $this->addr; + + $headers['Subject'] = _('Command complete'); + + return mail_send(array($this->addr), $headers, $text); + } + + function error($user, $text) + { + + $headers['From'] = $user->incomingemail; + $headers['To'] = $this->addr; + + $headers['Subject'] = _('Command failed'); + + return mail_send(array($this->addr), $headers, $text); + } + + function set_notify($user, $value) + { + $orig = clone($user); + $user->smsnotify = $value; + $result = $user->update($orig); + if (!$result) { + common_log_db_error($user, 'UPDATE', __FILE__); + return false; + } + return true; + } } diff --git a/classes/Command.php b/classes/Command.php index c2409d140..eacbdacb3 100644 --- a/classes/Command.php +++ b/classes/Command.php @@ -21,356 +21,399 @@ if (!defined('LACONICA')) { exit(1); } require_once(INSTALLDIR.'/classes/Channel.php'); -class Command { - - var $user = NULL; - - function __construct($user=NULL) { - $this->user = $user; - } - - function execute($channel) { - return false; - } +class Command +{ + + var $user = null; + + function __construct($user=null) + { + $this->user = $user; + } + + function execute($channel) + { + return false; + } } -class UnimplementedCommand extends Command { - function execute($channel) { - $channel->error($this->user, _("Sorry, this command is not yet implemented.")); - } +class UnimplementedCommand extends Command +{ + function execute($channel) + { + $channel->error($this->user, _("Sorry, this command is not yet implemented.")); + } } -class TrackingCommand extends UnimplementedCommand { +class TrackingCommand extends UnimplementedCommand +{ } -class TrackOffCommand extends UnimplementedCommand { +class TrackOffCommand extends UnimplementedCommand +{ } -class TrackCommand extends UnimplementedCommand { - var $word = NULL; - function __construct($user, $word) { - parent::__construct($user); - $this->word = $word; - } +class TrackCommand extends UnimplementedCommand +{ + var $word = null; + function __construct($user, $word) + { + parent::__construct($user); + $this->word = $word; + } } -class UntrackCommand extends UnimplementedCommand { - var $word = NULL; - function __construct($user, $word) { - parent::__construct($user); - $this->word = $word; - } +class UntrackCommand extends UnimplementedCommand +{ + var $word = null; + function __construct($user, $word) + { + parent::__construct($user); + $this->word = $word; + } } -class NudgeCommand extends UnimplementedCommand { - var $other = NULL; - function __construct($user, $other) { - parent::__construct($user); - $this->other = $other; - } +class NudgeCommand extends UnimplementedCommand +{ + var $other = null; + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } } -class InviteCommand extends UnimplementedCommand { - var $other = NULL; - function __construct($user, $other) { - parent::__construct($user); - $this->other = $other; - } +class InviteCommand extends UnimplementedCommand +{ + var $other = null; + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } } -class StatsCommand extends Command { - function execute($channel) { +class StatsCommand extends Command +{ + function execute($channel) + { - $subs = new Subscription(); - $subs->subscriber = $this->user->id; - $subs_count = (int) $subs->count() - 1; + $subs = new Subscription(); + $subs->subscriber = $this->user->id; + $subs_count = (int) $subs->count() - 1; - $subbed = new Subscription(); - $subbed->subscribed = $this->user->id; - $subbed_count = (int) $subbed->count() - 1; + $subbed = new Subscription(); + $subbed->subscribed = $this->user->id; + $subbed_count = (int) $subbed->count() - 1; - $notices = new Notice(); - $notices->profile_id = $this->user->id; - $notice_count = (int) $notices->count(); - - $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n". - "Subscribers: %2\$s\n". - "Notices: %3\$s"), - $subs_count, - $subbed_count, - $notice_count)); - } + $notices = new Notice(); + $notices->profile_id = $this->user->id; + $notice_count = (int) $notices->count(); + + $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n". + "Subscribers: %2\$s\n". + "Notices: %3\$s"), + $subs_count, + $subbed_count, + $notice_count)); + } } -class FavCommand extends Command { - - var $other = NULL; - - function __construct($user, $other) { - parent::__construct($user); - $this->other = $other; - } - - function execute($channel) { - - $recipient = - common_relative_profile($this->user, common_canonical_nickname($this->other)); - - if (!$recipient) { - $channel->error($this->user, _('No such user.')); - return; - } - $notice = $recipient->getCurrentNotice(); - if (!$notice) { - $channel->error($this->user, _('User has no last notice')); - return; - } - - $fave = Fave::addNew($this->user, $notice); +class FavCommand extends Command +{ + + var $other = null; + + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } + + function execute($channel) + { + + $recipient = + common_relative_profile($this->user, common_canonical_nickname($this->other)); + + if (!$recipient) { + $channel->error($this->user, _('No such user.')); + return; + } + $notice = $recipient->getCurrentNotice(); + if (!$notice) { + $channel->error($this->user, _('User has no last notice')); + return; + } + + $fave = Fave::addNew($this->user, $notice); - if (!$fave) { - $channel->error($this->user, _('Could not create favorite.')); - return; - } + if (!$fave) { + $channel->error($this->user, _('Could not create favorite.')); + return; + } - $other = User::staticGet('id', $recipient->id); - - if ($other && $other->id != $user->id) { - if ($other->email && $other->emailnotifyfav) { - mail_notify_fave($other, $this->user, $notice); - } - } - - $this->user->blowFavesCache(); - - $channel->output($this->user, _('Notice marked as fave.')); - } + $other = User::staticGet('id', $recipient->id); + + if ($other && $other->id != $user->id) { + if ($other->email && $other->emailnotifyfav) { + mail_notify_fave($other, $this->user, $notice); + } + } + + $this->user->blowFavesCache(); + + $channel->output($this->user, _('Notice marked as fave.')); + } } -class WhoisCommand extends Command { - var $other = NULL; - function __construct($user, $other) { - parent::__construct($user); - $this->other = $other; - } - - function execute($channel) { - $recipient = - common_relative_profile($this->user, common_canonical_nickname($this->other)); - - if (!$recipient) { - $channel->error($this->user, _('No such user.')); - return; - } - - $whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname, - $recipient->profileurl); - if ($recipient->fullname) { - $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname); - } - if ($recipient->location) { - $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location); - } - if ($recipient->homepage) { - $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage); - } - if ($recipient->bio) { - $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio); - } - $channel->output($this->user, $whois); - } +class WhoisCommand extends Command +{ + var $other = null; + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } + + function execute($channel) + { + $recipient = + common_relative_profile($this->user, common_canonical_nickname($this->other)); + + if (!$recipient) { + $channel->error($this->user, _('No such user.')); + return; + } + + $whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname, + $recipient->profileurl); + if ($recipient->fullname) { + $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname); + } + if ($recipient->location) { + $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location); + } + if ($recipient->homepage) { + $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage); + } + if ($recipient->bio) { + $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio); + } + $channel->output($this->user, $whois); + } } -class MessageCommand extends Command { - var $other = NULL; - var $text = NULL; - function __construct($user, $other, $text) { - parent::__construct($user); - $this->other = $other; - $this->text = $text; - } - - function execute($channel) { - $other = User::staticGet('nickname', common_canonical_nickname($this->other)); - $len = mb_strlen($this->text); - if ($len == 0) { - $channel->error($this->user, _('No content!')); - return; - } else if ($len > 140) { - $content = common_shorten_links($content); - if (mb_strlen($content) > 140) { - $channel->error($this->user, sprintf(_('Message too long - maximum is 140 characters, you sent %d'), $len)); - return; - } - } - - if (!$other) { - $channel->error($this->user, _('No such user.')); - return; - } else if (!$this->user->mutuallySubscribed($other)) { - $channel->error($this->user, _('You can\'t send a message to this user.')); - return; - } else if ($this->user->id == $other->id) { - $channel->error($this->user, _('Don\'t send a message to yourself; just say it to yourself quietly instead.')); - return; - } - $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source()); - if ($message) { - $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other)); - } else { - $channel->error($this->user, _('Error sending direct message.')); - } - } +class MessageCommand extends Command +{ + var $other = null; + var $text = null; + function __construct($user, $other, $text) + { + parent::__construct($user); + $this->other = $other; + $this->text = $text; + } + + function execute($channel) + { + $other = User::staticGet('nickname', common_canonical_nickname($this->other)); + $len = mb_strlen($this->text); + if ($len == 0) { + $channel->error($this->user, _('No content!')); + return; + } else if ($len > 140) { + $content = common_shorten_links($content); + if (mb_strlen($content) > 140) { + $channel->error($this->user, sprintf(_('Message too long - maximum is 140 characters, you sent %d'), $len)); + return; + } + } + + if (!$other) { + $channel->error($this->user, _('No such user.')); + return; + } else if (!$this->user->mutuallySubscribed($other)) { + $channel->error($this->user, _('You can\'t send a message to this user.')); + return; + } else if ($this->user->id == $other->id) { + $channel->error($this->user, _('Don\'t send a message to yourself; just say it to yourself quietly instead.')); + return; + } + $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source()); + if ($message) { + $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other)); + } else { + $channel->error($this->user, _('Error sending direct message.')); + } + } } -class GetCommand extends Command { - - var $other = NULL; - - function __construct($user, $other) { - parent::__construct($user); - $this->other = $other; - } - - function execute($channel) { - $target_nickname = common_canonical_nickname($this->other); - - $target = - common_relative_profile($this->user, $target_nickname); +class GetCommand extends Command +{ + + var $other = null; + + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } + + function execute($channel) + { + $target_nickname = common_canonical_nickname($this->other); + + $target = + common_relative_profile($this->user, $target_nickname); - if (!$target) { - $channel->error($this->user, _('No such user.')); - return; - } - $notice = $target->getCurrentNotice(); - if (!$notice) { - $channel->error($this->user, _('User has no last notice')); - return; - } - $notice_content = $notice->content; - - $channel->output($this->user, $target_nickname . ": " . $notice_content); - } + if (!$target) { + $channel->error($this->user, _('No such user.')); + return; + } + $notice = $target->getCurrentNotice(); + if (!$notice) { + $channel->error($this->user, _('User has no last notice')); + return; + } + $notice_content = $notice->content; + + $channel->output($this->user, $target_nickname . ": " . $notice_content); + } } -class SubCommand extends Command { - - var $other = NULL; - - function __construct($user, $other) { - parent::__construct($user); - $this->other = $other; - } - - function execute($channel) { - - if (!$this->other) { - $channel->error($this->user, _('Specify the name of the user to subscribe to')); - return; - } - - $result = subs_subscribe_user($this->user, $this->other); - - if ($result == 'true') { - $channel->output($this->user, sprintf(_('Subscribed to %s'), $this->other)); - } else { - $channel->error($this->user, $result); - } - } +class SubCommand extends Command +{ + + var $other = null; + + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } + + function execute($channel) + { + + if (!$this->other) { + $channel->error($this->user, _('Specify the name of the user to subscribe to')); + return; + } + + $result = subs_subscribe_user($this->user, $this->other); + + if ($result == 'true') { + $channel->output($this->user, sprintf(_('Subscribed to %s'), $this->other)); + } else { + $channel->error($this->user, $result); + } + } } -class UnsubCommand extends Command { +class UnsubCommand extends Command +{ - var $other = NULL; - - function __construct($user, $other) { - parent::__construct($user); - $this->other = $other; - } + var $other = null; + + function __construct($user, $other) + { + parent::__construct($user); + $this->other = $other; + } - function execute($channel) { - if(!$this->other) { - $channel->error($this->user, _('Specify the name of the user to unsubscribe from')); - return; - } - - $result=subs_unsubscribe_user($this->user, $this->other); - - if ($result) { - $channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other)); - } else { - $channel->error($this->user, $result); - } - } + function execute($channel) + { + if(!$this->other) { + $channel->error($this->user, _('Specify the name of the user to unsubscribe from')); + return; + } + + $result=subs_unsubscribe_user($this->user, $this->other); + + if ($result) { + $channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other)); + } else { + $channel->error($this->user, $result); + } + } } -class OffCommand extends Command { - var $other = NULL; - function __construct($user, $other=NULL) { - parent::__construct($user); - $this->other = $other; - } - function execute($channel) { - if ($other) { - $channel->error($this->user, _("Command not yet implemented.")); - } else { - if ($channel->off($this->user)) { - $channel->output($this->user, _('Notification off.')); - } else { - $channel->error($this->user, _('Can\'t turn off notification.')); - } - } - } +class OffCommand extends Command +{ + var $other = null; + function __construct($user, $other=null) + { + parent::__construct($user); + $this->other = $other; + } + function execute($channel) + { + if ($other) { + $channel->error($this->user, _("Command not yet implemented.")); + } else { + if ($channel->off($this->user)) { + $channel->output($this->user, _('Notification off.')); + } else { + $channel->error($this->user, _('Can\'t turn off notification.')); + } + } + } } -class OnCommand extends Command { - var $other = NULL; - function __construct($user, $other=NULL) { - parent::__construct($user); - $this->other = $other; - } - - function execute($channel) { - if ($other) { - $channel->error($this->user, _("Command not yet implemented.")); - } else { - if ($channel->on($this->user)) { - $channel->output($this->user, _('Notification on.')); - } else { - $channel->error($this->user, _('Can\'t turn on notification.')); - } - } - } +class OnCommand extends Command +{ + var $other = null; + function __construct($user, $other=null) + { + parent::__construct($user); + $this->other = $other; + } + + function execute($channel) + { + if ($other) { + $channel->error($this->user, _("Command not yet implemented.")); + } else { + if ($channel->on($this->user)) { + $channel->output($this->user, _('Notification on.')); + } else { + $channel->error($this->user, _('Can\'t turn on notification.')); + } + } + } } -class HelpCommand extends Command { - function execute($channel) { - $channel->output($this->user, - _("Commands:\n". - "on - turn on notifications\n". - "off - turn off notifications\n". - "help - show this help\n". - "follow <nickname> - subscribe to user\n". - "leave <nickname> - unsubscribe from user\n". - "d <nickname> <text> - direct message to user\n". - "get <nickname> - get last notice from user\n". - "whois <nickname> - get profile info on user\n". - "fav <nickname> - add user's last notice as a 'fave'\n". - "stats - get your stats\n". - "stop - same as 'off'\n". - "quit - same as 'off'\n". - "sub <nickname> - same as 'follow'\n". - "unsub <nickname> - same as 'leave'\n". - "last <nickname> - same as 'get'\n". - "on <nickname> - not yet implemented.\n". - "off <nickname> - not yet implemented.\n". - "nudge <nickname> - not yet implemented.\n". - "invite <phone number> - not yet implemented.\n". - "track <word> - not yet implemented.\n". - "untrack <word> - not yet implemented.\n". - "track off - not yet implemented.\n". - "untrack all - not yet implemented.\n". - "tracks - not yet implemented.\n". - "tracking - not yet implemented.\n")); - } +class HelpCommand extends Command +{ + function execute($channel) + { + $channel->output($this->user, + _("Commands:\n". + "on - turn on notifications\n". + "off - turn off notifications\n". + "help - show this help\n". + "follow <nickname> - subscribe to user\n". + "leave <nickname> - unsubscribe from user\n". + "d <nickname> <text> - direct message to user\n". + "get <nickname> - get last notice from user\n". + "whois <nickname> - get profile info on user\n". + "fav <nickname> - add user's last notice as a 'fave'\n". + "stats - get your stats\n". + "stop - same as 'off'\n". + "quit - same as 'off'\n". + "sub <nickname> - same as 'follow'\n". + "unsub <nickname> - same as 'leave'\n". + "last <nickname> - same as 'get'\n". + "on <nickname> - not yet implemented.\n". + "off <nickname> - not yet implemented.\n". + "nudge <nickname> - not yet implemented.\n". + "invite <phone number> - not yet implemented.\n". + "track <word> - not yet implemented.\n". + "untrack <word> - not yet implemented.\n". + "track off - not yet implemented.\n". + "untrack all - not yet implemented.\n". + "tracks - not yet implemented.\n". + "tracking - not yet implemented.\n")); + } } diff --git a/classes/CommandInterpreter.php b/classes/CommandInterpreter.php index eae315cb6..0679f5462 100644 --- a/classes/CommandInterpreter.php +++ b/classes/CommandInterpreter.php @@ -21,176 +21,178 @@ if (!defined('LACONICA')) { exit(1); } require_once(INSTALLDIR.'/classes/Command.php'); -class CommandInterpreter { - - function handle_command($user, $text) { - # XXX: localise +class CommandInterpreter +{ - $text = preg_replace('/\s+/', ' ', trim($text)); - list($cmd, $arg) = explode(' ', $text, 2); + function handle_command($user, $text) + { + # XXX: localise - # We try to support all the same commands as Twitter, see - # http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands - # There are a few compatibility commands from earlier versions of - # Laconica - - switch(strtolower($cmd)) { - case 'help': - if ($arg) { - return NULL; - } - return new HelpCommand($user); - case 'on': - if ($arg) { - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return NULL; - } else { - return new OnCommand($user, $other); - } - } else { - return new OnCommand($user); - } - case 'off': - if ($arg) { - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return NULL; - } else { - return new OffCommand($user, $other); - } - } else { - return new OffCommand($user); - } - case 'stop': - case 'quit': - if ($arg) { - return NULL; - } else { - return new OffCommand($user); - } - case 'follow': - case 'sub': - if (!$arg) { - return NULL; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return NULL; - } else { - return new SubCommand($user, $other); - } - case 'leave': - case 'unsub': - if (!$arg) { - return NULL; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return NULL; - } else { - return new UnsubCommand($user, $other); - } - case 'get': - case 'last': - if (!$arg) { - return NULL; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return NULL; - } else { - return new GetCommand($user, $other); - } - case 'd': - case 'dm': - if (!$arg) { - return NULL; - } - list($other, $extra) = explode(' ', $arg, 2); - if (!$extra) { - return NULL; - } else { - return new MessageCommand($user, $other, $extra); - } - case 'whois': - if (!$arg) { - return NULL; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return NULL; - } else { - return new WhoisCommand($user, $other); - } - case 'fav': - if (!$arg) { - return NULL; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return NULL; - } else { - return new FavCommand($user, $other); - } - case 'nudge': - if (!$arg) { - return NULL; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return NULL; - } else { - return new NudgeCommand($user, $other); - } - case 'stats': - if ($arg) { - return NULL; - } - return new StatsCommand($user); - case 'invite': - if (!$arg) { - return NULL; - } - list($other, $extra) = explode(' ', $arg, 2); - if ($extra) { - return NULL; - } else { - return new InviteCommand($user, $other); - } - case 'track': - if (!$arg) { - return NULL; - } - list($word, $extra) = explode(' ', $arg, 2); - if ($extra) { - return NULL; - } else if ($word == 'off') { - return new TrackOffCommand($user); - } else { - return new TrackCommand($user, $word); - } - case 'untrack': - if (!$arg) { - return NULL; - } - list($word, $extra) = explode(' ', $arg, 2); - if ($extra) { - return NULL; - } else if ($word == 'all') { - return new TrackOffCommand($user); - } else { - return new UntrackCommand($user, $word); - } - case 'tracks': - case 'tracking': - if ($arg) { - return NULL; - } - return new TrackingCommand($user); - default: - return false; - } - } + $text = preg_replace('/\s+/', ' ', trim($text)); + list($cmd, $arg) = explode(' ', $text, 2); + + # We try to support all the same commands as Twitter, see + # http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands + # There are a few compatibility commands from earlier versions of + # Laconica + + switch(strtolower($cmd)) { + case 'help': + if ($arg) { + return null; + } + return new HelpCommand($user); + case 'on': + if ($arg) { + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new OnCommand($user, $other); + } + } else { + return new OnCommand($user); + } + case 'off': + if ($arg) { + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new OffCommand($user, $other); + } + } else { + return new OffCommand($user); + } + case 'stop': + case 'quit': + if ($arg) { + return null; + } else { + return new OffCommand($user); + } + case 'follow': + case 'sub': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new SubCommand($user, $other); + } + case 'leave': + case 'unsub': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new UnsubCommand($user, $other); + } + case 'get': + case 'last': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new GetCommand($user, $other); + } + case 'd': + case 'dm': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if (!$extra) { + return null; + } else { + return new MessageCommand($user, $other, $extra); + } + case 'whois': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new WhoisCommand($user, $other); + } + case 'fav': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new FavCommand($user, $other); + } + case 'nudge': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new NudgeCommand($user, $other); + } + case 'stats': + if ($arg) { + return null; + } + return new StatsCommand($user); + case 'invite': + if (!$arg) { + return null; + } + list($other, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else { + return new InviteCommand($user, $other); + } + case 'track': + if (!$arg) { + return null; + } + list($word, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else if ($word == 'off') { + return new TrackOffCommand($user); + } else { + return new TrackCommand($user, $word); + } + case 'untrack': + if (!$arg) { + return null; + } + list($word, $extra) = explode(' ', $arg, 2); + if ($extra) { + return null; + } else if ($word == 'all') { + return new TrackOffCommand($user); + } else { + return new UntrackCommand($user, $word); + } + case 'tracks': + case 'tracking': + if ($arg) { + return null; + } + return new TrackingCommand($user); + default: + return false; + } + } } diff --git a/classes/Confirm_address.php b/classes/Confirm_address.php index 10661ff5c..ed3875d22 100644 --- a/classes/Confirm_address.php +++ b/classes/Confirm_address.php @@ -20,10 +20,12 @@ class Confirm_address extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Confirm_address',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Confirm_address',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function sequenceKey() { return array(false, false); } + function sequenceKey() + { return array(false, false); } } diff --git a/classes/Consumer.php b/classes/Consumer.php index d18e6feeb..d5b7b7e33 100644 --- a/classes/Consumer.php +++ b/classes/Consumer.php @@ -16,7 +16,8 @@ class Consumer extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Consumer',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Consumer',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/Fave.php b/classes/Fave.php index 7cc3f585e..24df5938c 100644 --- a/classes/Fave.php +++ b/classes/Fave.php @@ -15,23 +15,25 @@ class Fave extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Fave',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Fave',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - static function addNew($user, $notice) { - $fave = new Fave(); - $fave->user_id = $user->id; - $fave->notice_id = $notice->id; - if (!$fave->insert()) { - common_log_db_error($fave, 'INSERT', __FILE__); - return false; - } - return $fave; - } - - function &pkeyGet($kv) { - return Memcached_DataObject::pkeyGet('Fave', $kv); - } + static function addNew($user, $notice) { + $fave = new Fave(); + $fave->user_id = $user->id; + $fave->notice_id = $notice->id; + if (!$fave->insert()) { + common_log_db_error($fave, 'INSERT', __FILE__); + return false; + } + return $fave; + } + + function &pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Fave', $kv); + } } diff --git a/classes/Foreign_link.php b/classes/Foreign_link.php index 7a625a209..afc0e2180 100644 --- a/classes/Foreign_link.php +++ b/classes/Foreign_link.php @@ -4,7 +4,7 @@ */ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Foreign_link extends Memcached_DataObject +class Foreign_link extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -13,7 +13,7 @@ class Foreign_link extends Memcached_DataObject public $user_id; // int(4) primary_key not_null public $foreign_id; // int(4) primary_key not_null public $service; // int(4) primary_key not_null - public $credentials; // varchar(255) + public $credentials; // varchar(255) public $noticesync; // tinyint(1) not_null default_1 public $friendsync; // tinyint(1) not_null default_2 public $profilesync; // tinyint(1) not_null default_1 @@ -21,56 +21,84 @@ class Foreign_link extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Foreign_link',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Foreign_link',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - // XXX: This only returns a 1->1 single obj mapping. Change? Or make - // a getForeignUsers() that returns more than one? --Zach - static function getByUserID($user_id, $service) { - $flink = new Foreign_link(); - $flink->service = $service; - $flink->user_id = $user_id; - $flink->limit(1); - - if ($flink->find(TRUE)) { - return $flink; - } - - return NULL; - } - - static function getByForeignID($foreign_id, $service) { - $flink = new Foreign_link(); - $flink->service = $service; - $flink->foreign_id = $foreign_id; - $flink->limit(1); - - if ($flink->find(TRUE)) { - return $flink; - } - - return NULL; - } - - # Convenience methods - function getForeignUser() { - $fuser = new Foreign_user(); - $fuser->service = $this->service; - $fuser->id = $this->foreign_id; - - $fuser->limit(1); - - if ($fuser->find(TRUE)) { - return $fuser; - } - - return NULL; - } - - function getUser() { - return User::staticGet($this->user_id); - } - + // XXX: This only returns a 1->1 single obj mapping. Change? Or make + // a getForeignUsers() that returns more than one? --Zach + static function getByUserID($user_id, $service) + { + $flink = new Foreign_link(); + $flink->service = $service; + $flink->user_id = $user_id; + $flink->limit(1); + + if ($flink->find(true)) { + return $flink; + } + + return null; + } + + static function getByForeignID($foreign_id, $service) + { + $flink = new Foreign_link(); + $flink->service = $service; + $flink->foreign_id = $foreign_id; + $flink->limit(1); + + if ($flink->find(true)) { + return $flink; + } + + return null; + } + + function set_flags($noticesync, $replysync, $friendsync) + { + if ($noticesync) { + $this->noticesync |= FOREIGN_NOTICE_SEND; + } else { + $this->noticesync &= ~FOREIGN_NOTICE_SEND; + } + + if ($replysync) { + $this->noticesync |= FOREIGN_NOTICE_SEND_REPLY; + } else { + $this->noticesync &= ~FOREIGN_NOTICE_SEND_REPLY; + } + + if ($friendsync) { + $this->friendsync |= FOREIGN_FRIEND_RECV; + } else { + $this->friendsync &= ~FOREIGN_FRIEND_RECV; + } + + $this->profilesync = 0; + } + + # Convenience methods + function getForeignUser() + { + $fuser = new Foreign_user(); + $fuser->service = $this->service; + $fuser->id = $this->foreign_id; + + $fuser->limit(1); + + if ($fuser->find(true)) { + return $fuser; + } + + return null; + } + + function getUser() + { + return User::staticGet($this->user_id); + } + } diff --git a/classes/Foreign_service.php b/classes/Foreign_service.php index 18ef83d69..ef614dbd6 100644 --- a/classes/Foreign_service.php +++ b/classes/Foreign_service.php @@ -17,7 +17,8 @@ class Foreign_service extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Foreign_service',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Foreign_service',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/Foreign_subscription.php b/classes/Foreign_subscription.php index 315064067..d50860621 100644 --- a/classes/Foreign_subscription.php +++ b/classes/Foreign_subscription.php @@ -16,7 +16,8 @@ class Foreign_subscription extends Memcached_DataObject public $created; // datetime() not_null /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Foreign_subscription',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Foreign_subscription',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/Foreign_user.php b/classes/Foreign_user.php index 027fae69d..61727abe5 100644 --- a/classes/Foreign_user.php +++ b/classes/Foreign_user.php @@ -18,53 +18,55 @@ class Foreign_user extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Foreign_user',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Foreign_user',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - - // XXX: This only returns a 1->1 single obj mapping. Change? Or make - // a getForeignUsers() that returns more than one? --Zach - static function getForeignUser($id, $service) { - $fuser = new Foreign_user(); - $fuser->whereAdd("service = $service"); - $fuser->whereAdd("id = $id"); - $fuser->limit(1); - - if ($fuser->find()) { - $fuser->fetch(); - return $fuser; - } - - return NULL; - } - - function updateKeys(&$orig) { - $parts = array(); - foreach (array('id', 'service', 'uri', 'nickname') as $k) { - if (strcmp($this->$k, $orig->$k) != 0) { - $parts[] = $k . ' = ' . $this->_quote($this->$k); - } - } - if (count($parts) == 0) { - # No changes - return true; - } - $toupdate = implode(', ', $parts); + + // XXX: This only returns a 1->1 single obj mapping. Change? Or make + // a getForeignUsers() that returns more than one? --Zach + static function getForeignUser($id, $service) { + $fuser = new Foreign_user(); + $fuser->whereAdd("service = $service"); + $fuser->whereAdd("id = $id"); + $fuser->limit(1); + + if ($fuser->find()) { + $fuser->fetch(); + return $fuser; + } + + return null; + } + + function updateKeys(&$orig) + { + $parts = array(); + foreach (array('id', 'service', 'uri', 'nickname') as $k) { + if (strcmp($this->$k, $orig->$k) != 0) { + $parts[] = $k . ' = ' . $this->_quote($this->$k); + } + } + if (count($parts) == 0) { + # No changes + return true; + } + $toupdate = implode(', ', $parts); - $table = $this->tableName(); - if(common_config('db','quote_identifiers')) { - $table = '"' . $table . '"'; - } - $qry = 'UPDATE ' . $table . ' SET ' . $toupdate . - ' WHERE id = ' . $this->id; - $orig->decache(); - $result = $this->query($qry); - if ($result) { - $this->encache(); - } - return $result; - } + $table = $this->tableName(); + if(common_config('db','quote_identifiers')) { + $table = '"' . $table . '"'; + } + $qry = 'UPDATE ' . $table . ' SET ' . $toupdate . + ' WHERE id = ' . $this->id; + $orig->decache(); + $result = $this->query($qry); + if ($result) { + $this->encache(); + } + return $result; + } - + } diff --git a/classes/Group_inbox.php b/classes/Group_inbox.php new file mode 100755 index 000000000..b80ba4272 --- /dev/null +++ b/classes/Group_inbox.php @@ -0,0 +1,21 @@ +<?php +/** + * Table Definition for group_inbox + */ + +class Group_inbox extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'group_inbox'; // table name + public $group_id; // int(4) primary_key not_null + public $notice_id; // int(4) primary_key not_null + public $created; // datetime() not_null + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Group_inbox',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE +} diff --git a/classes/Group_member.php b/classes/Group_member.php new file mode 100755 index 000000000..3c23a991f --- /dev/null +++ b/classes/Group_member.php @@ -0,0 +1,28 @@ +<?php +/** + * Table Definition for group_member + */ + +class Group_member extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'group_member'; // table name + public $group_id; // int(4) primary_key not_null + public $profile_id; // int(4) primary_key not_null + public $is_admin; // tinyint(1) + public $created; // datetime() not_null + public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Group_member',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function &pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Group_member', $kv); + } +} diff --git a/classes/Invitation.php b/classes/Invitation.php index 1477391b0..8a36fd8df 100644 --- a/classes/Invitation.php +++ b/classes/Invitation.php @@ -17,7 +17,8 @@ class Invitation extends Memcached_DataObject public $created; // datetime() not_null /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Invitation',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Invitation',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 7a33e158d..5f71f716b 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -21,156 +21,166 @@ if (!defined('LACONICA')) { exit(1); } require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Memcached_DataObject extends DB_DataObject +class Memcached_DataObject extends DB_DataObject { - function &staticGet($cls, $k, $v=NULL) { - if (is_null($v)) { - $v = $k; - # XXX: HACK! - $i = new $cls; - $keys = $i->keys(); - $k = $keys[0]; - unset($i); - } - $i = Memcached_DataObject::getcached($cls, $k, $v); - if ($i) { - return $i; - } else { - $i = DB_DataObject::staticGet($cls, $k, $v); - if ($i) { - $i->encache(); - } - return $i; - } - } - - function &pkeyGet($cls, $kv) { - $i = Memcached_DataObject::multicache($cls, $kv); - if ($i) { - return $i; - } else { - $i = new $cls(); - foreach ($kv as $k => $v) { - $i->$k = $v; - } - if ($i->find(true)) { - $i->encache(); - } else { - $i = NULL; - } + function &staticGet($cls, $k, $v=null) + { + if (is_null($v)) { + $v = $k; + # XXX: HACK! + $i = new $cls; + $keys = $i->keys(); + $k = $keys[0]; + unset($i); + } + $i = Memcached_DataObject::getcached($cls, $k, $v); + if ($i) { + return $i; + } else { + $i = DB_DataObject::staticGet($cls, $k, $v); + if ($i) { + $i->encache(); + } + return $i; + } + } + + function &pkeyGet($cls, $kv) + { + $i = Memcached_DataObject::multicache($cls, $kv); + if ($i) { return $i; - } - } - - function insert() { - $result = parent::insert(); - return $result; - } - - function update($orig=NULL) { - if (is_object($orig) && $orig instanceof Memcached_DataObject) { - $orig->decache(); # might be different keys - } - $result = parent::update($orig); - if ($result) { - $this->encache(); - } - return $result; - } - - function delete() { - $this->decache(); # while we still have the values! - return parent::delete(); - } - - static function memcache() { - return common_memcache(); - } - - static function cacheKey($cls, $k, $v) { - return common_cache_key(strtolower($cls).':'.$k.':'.$v); - } - - static function getcached($cls, $k, $v) { - $c = Memcached_DataObject::memcache(); - if (!$c) { - return false; - } else { - return $c->get(Memcached_DataObject::cacheKey($cls, $k, $v)); - } - } - - function keyTypes() { - global $_DB_DATAOBJECT; + } else { + $i = new $cls(); + foreach ($kv as $k => $v) { + $i->$k = $v; + } + if ($i->find(true)) { + $i->encache(); + } else { + $i = null; + } + return $i; + } + } + + function insert() + { + $result = parent::insert(); + return $result; + } + + function update($orig=null) + { + if (is_object($orig) && $orig instanceof Memcached_DataObject) { + $orig->decache(); # might be different keys + } + $result = parent::update($orig); + if ($result) { + $this->encache(); + } + return $result; + } + + function delete() + { + $this->decache(); # while we still have the values! + return parent::delete(); + } + + static function memcache() { + return common_memcache(); + } + + static function cacheKey($cls, $k, $v) { + return common_cache_key(strtolower($cls).':'.$k.':'.$v); + } + + static function getcached($cls, $k, $v) { + $c = Memcached_DataObject::memcache(); + if (!$c) { + return false; + } else { + return $c->get(Memcached_DataObject::cacheKey($cls, $k, $v)); + } + } + + function keyTypes() + { + global $_DB_DATAOBJECT; if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) { - $this->databaseStructure(); + $this->databaseStructure(); + + } + return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]; + } + + function encache() + { + $c = $this->memcache(); + if (!$c) { + return false; + } else { + $pkey = array(); + $pval = array(); + $types = $this->keyTypes(); + ksort($types); + foreach ($types as $key => $type) { + if ($type == 'K') { + $pkey[] = $key; + $pval[] = $this->$key; + } else { + $c->set($this->cacheKey($this->tableName(), $key, $this->$key), $this); + } + } + # XXX: should work for both compound and scalar pkeys + $pvals = implode(',', $pval); + $pkeys = implode(',', $pkey); + $c->set($this->cacheKey($this->tableName(), $pkeys, $pvals), $this); + } + } + + function decache() + { + $c = $this->memcache(); + if (!$c) { + return false; + } else { + $pkey = array(); + $pval = array(); + $types = $this->keyTypes(); + ksort($types); + foreach ($types as $key => $type) { + if ($type == 'K') { + $pkey[] = $key; + $pval[] = $this->$key; + } else { + $c->delete($this->cacheKey($this->tableName(), $key, $this->$key)); + } + } + # should work for both compound and scalar pkeys + # XXX: comma works for now but may not be safe separator for future keys + $pvals = implode(',', $pval); + $pkeys = implode(',', $pkey); + $c->delete($this->cacheKey($this->tableName(), $pkeys, $pvals)); + } + } + function multicache($cls, $kv) + { + ksort($kv); + $c = Memcached_DataObject::memcache(); + if (!$c) { + return false; + } else { + $pkeys = implode(',', array_keys($kv)); + $pvals = implode(',', array_values($kv)); + return $c->get(Memcached_DataObject::cacheKey($cls, $pkeys, $pvals)); } - return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]; - } - - function encache() { - $c = $this->memcache(); - if (!$c) { - return false; - } else { - $pkey = array(); - $pval = array(); - $types = $this->keyTypes(); - ksort($types); - foreach ($types as $key => $type) { - if ($type == 'K') { - $pkey[] = $key; - $pval[] = $this->$key; - } else { - $c->set($this->cacheKey($this->tableName(), $key, $this->$key), $this); - } - } - # XXX: should work for both compound and scalar pkeys - $pvals = implode(',', $pval); - $pkeys = implode(',', $pkey); - $c->set($this->cacheKey($this->tableName(), $pkeys, $pvals), $this); - } - } - - function decache() { - $c = $this->memcache(); - if (!$c) { - return false; - } else { - $pkey = array(); - $pval = array(); - $types = $this->keyTypes(); - ksort($types); - foreach ($types as $key => $type) { - if ($type == 'K') { - $pkey[] = $key; - $pval[] = $this->$key; - } else { - $c->delete($this->cacheKey($this->tableName(), $key, $this->$key)); - } - } - # should work for both compound and scalar pkeys - # XXX: comma works for now but may not be safe separator for future keys - $pvals = implode(',', $pval); - $pkeys = implode(',', $pkey); - $c->delete($this->cacheKey($this->tableName(), $pkeys, $pvals)); - } - } - - function multicache($cls, $kv) { - ksort($kv); - $c = Memcached_DataObject::memcache(); - if (!$c) { - return false; - } else { - $pkeys = implode(',', array_keys($kv)); - $pvals = implode(',', array_values($kv)); - return $c->get(Memcached_DataObject::cacheKey($cls, $pkeys, $pvals)); - } - } - - function getSearchEngine($table) { + } + + function getSearchEngine($table) + { require_once INSTALLDIR.'/lib/search_engines.php'; static $search_engine; if (!isset($search_engine)) { @@ -191,4 +201,30 @@ class Memcached_DataObject extends DB_DataObject } return $search_engine; } + + static function cachedQuery($cls, $qry, $expiry=3600) + { + $c = Memcached_DataObject::memcache(); + if (!$c) { + $inst = new $cls(); + $inst->query($qry); + return $inst; + } + $key_part = common_keyize($cls).':'.md5($qry); + $ckey = common_cache_key($key_part); + $stored = $c->get($ckey); + if ($stored) { + return new ArrayWrapper($stored); + } + + $inst = new $cls(); + $inst->query($qry); + $cached = array(); + while ($inst->fetch()) { + $cached[] = clone($inst); + } + $inst->free(); + $c->set($ckey, $cached, MEMCACHE_COMPRESSED, $expiry); + return new ArrayWrapper($cached); + } } diff --git a/classes/Message.php b/classes/Message.php index ef4bd0316..4806057b4 100644 --- a/classes/Message.php +++ b/classes/Message.php @@ -22,47 +22,50 @@ class Message extends Memcached_DataObject public $source; // varchar(32) /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Message',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Message',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - - function getFrom() { - return Profile::staticGet('id', $this->from_profile); - } - - function getTo() { - return Profile::staticGet('id', $this->to_profile); - } - - static function saveNew($from, $to, $content, $source) { - - $msg = new Message(); - - $msg->from_profile = $from; - $msg->to_profile = $to; - $msg->content = common_shorten_links($content); - $msg->rendered = common_render_text($content); - $msg->created = common_sql_now(); - $msg->source = $source; - - $result = $msg->insert(); - - if (!$result) { - common_log_db_error($msg, 'INSERT', __FILE__); - return _('Could not insert message.'); - } - - $orig = clone($msg); - $msg->uri = common_local_url('showmessage', array('message' => $msg->id)); - - $result = $msg->update($orig); - - if (!$result) { - common_log_db_error($msg, 'UPDATE', __FILE__); - return _('Could not update message with new URI.'); - } - - return $msg; - } + + function getFrom() + { + return Profile::staticGet('id', $this->from_profile); + } + + function getTo() + { + return Profile::staticGet('id', $this->to_profile); + } + + static function saveNew($from, $to, $content, $source) { + + $msg = new Message(); + + $msg->from_profile = $from; + $msg->to_profile = $to; + $msg->content = common_shorten_links($content); + $msg->rendered = common_render_text($content); + $msg->created = common_sql_now(); + $msg->source = $source; + + $result = $msg->insert(); + + if (!$result) { + common_log_db_error($msg, 'INSERT', __FILE__); + return _('Could not insert message.'); + } + + $orig = clone($msg); + $msg->uri = common_local_url('showmessage', array('message' => $msg->id)); + + $result = $msg->update($orig); + + if (!$result) { + common_log_db_error($msg, 'UPDATE', __FILE__); + return _('Could not update message with new URI.'); + } + + return $msg; + } } diff --git a/classes/Nonce.php b/classes/Nonce.php index 89d673c53..2c0edfa14 100644 --- a/classes/Nonce.php +++ b/classes/Nonce.php @@ -18,7 +18,8 @@ class Nonce extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Nonce',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Nonce',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/Notice.php b/classes/Notice.php index 281696632..4a06c9258 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -10,11 +10,11 @@ * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ if (!defined('LACONICA')) { exit(1); } @@ -31,69 +31,73 @@ define('NOTICE_CACHE_WINDOW', 61); class Notice extends Memcached_DataObject { - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'notice'; // table name - public $id; // int(4) primary_key not_null - public $profile_id; // int(4) not_null - public $uri; // varchar(255) unique_key - public $content; // varchar(140) - public $rendered; // text() - public $url; // varchar(255) - public $created; // datetime() not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - public $reply_to; // int(4) - public $is_local; // tinyint(1) - public $source; // varchar(32) - - /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Notice',$k,$v); } - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - function getProfile() { - return Profile::staticGet('id', $this->profile_id); - } - - function delete() { - $this->blowCaches(true); - $this->blowFavesCache(true); - $this->blowInboxes(); - return parent::delete(); - } - - function saveTags() { - /* extract all #hastags */ - $count = preg_match_all('/(?:^|\s)#([A-Za-z0-9_\-\.]{1,64})/', strtolower($this->content), $match); - if (!$count) { - return true; - } - - /* elide characters we don't want in the tag */ - $match[1] = str_replace(array('-', '_', '.'), '', $match[1]); - - /* Add them to the database */ - foreach(array_unique($match[1]) as $hashtag) { - $tag = DB_DataObject::factory('Notice_tag'); - $tag->notice_id = $this->id; - $tag->tag = $hashtag; - $tag->created = $this->created; - $id = $tag->insert(); - if (!$id) { - $last_error = PEAR::getStaticProperty('DB_DataObject','lastError'); - common_log(LOG_ERR, 'DB error inserting hashtag: ' . $last_error->message); - common_server_error(sprintf(_('DB error inserting hashtag: %s'), $last_error->message)); - return; - } - } - return true; - } - - static function saveNew($profile_id, $content, $source=NULL, $is_local=1, $reply_to=NULL, $uri=NULL) { - - $profile = Profile::staticGet($profile_id); + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'notice'; // table name + public $id; // int(4) primary_key not_null + public $profile_id; // int(4) not_null + public $uri; // varchar(255) unique_key + public $content; // varchar(140) + public $rendered; // text() + public $url; // varchar(255) + public $created; // datetime() not_null + public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + public $reply_to; // int(4) + public $is_local; // tinyint(1) + public $source; // varchar(32) + + /* Static get */ + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Notice',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function getProfile() + { + return Profile::staticGet('id', $this->profile_id); + } + + function delete() + { + $this->blowCaches(true); + $this->blowFavesCache(true); + $this->blowInboxes(); + return parent::delete(); + } + + function saveTags() + { + /* extract all #hastags */ + $count = preg_match_all('/(?:^|\s)#([A-Za-z0-9_\-\.]{1,64})/', strtolower($this->content), $match); + if (!$count) { + return true; + } + + /* elide characters we don't want in the tag */ + $match[1] = str_replace(array('-', '_', '.'), '', $match[1]); + + /* Add them to the database */ + foreach(array_unique($match[1]) as $hashtag) { + $tag = DB_DataObject::factory('Notice_tag'); + $tag->notice_id = $this->id; + $tag->tag = $hashtag; + $tag->created = $this->created; + $id = $tag->insert(); + if (!$id) { + $last_error = PEAR::getStaticProperty('DB_DataObject','lastError'); + common_log(LOG_ERR, 'DB error inserting hashtag: ' . $last_error->message); + common_server_error(sprintf(_('DB error inserting hashtag: %s'), $last_error->message)); + return; + } + } + return true; + } + + static function saveNew($profile_id, $content, $source=null, $is_local=1, $reply_to=null, $uri=null) { + + $profile = Profile::staticGet($profile_id); if (!$profile) { common_log(LOG_ERR, 'Problem saving notice. Unknown user.'); @@ -102,76 +106,74 @@ class Notice extends Memcached_DataObject if (common_config('throttle', 'enabled') && !Notice::checkEditThrottle($profile_id)) { common_log(LOG_WARNING, 'Excessive posting by profile #' . $profile_id . '; throttled.'); - return _('Too many notices too fast; take a breather and post again in a few minutes.'); + return _('Too many notices too fast; take a breather and post again in a few minutes.'); } - $banned = common_config('profile', 'banned'); + $banned = common_config('profile', 'banned'); - if ( in_array($profile_id, $banned) || in_array($profile->nickname, $banned)) { - common_log(LOG_WARNING, "Attempted post from banned user: $profile->nickname (user id = $profile_id)."); + if ( in_array($profile_id, $banned) || in_array($profile->nickname, $banned)) { + common_log(LOG_WARNING, "Attempted post from banned user: $profile->nickname (user id = $profile_id)."); return _('You are banned from posting notices on this site.'); - } + } - $notice = new Notice(); - $notice->profile_id = $profile_id; + $notice = new Notice(); + $notice->profile_id = $profile_id; - $blacklist = common_config('public', 'blacklist'); + $blacklist = common_config('public', 'blacklist'); - # Blacklisted are non-false, but not 1, either + # Blacklisted are non-false, but not 1, either - if ($blacklist && in_array($profile_id, $blacklist)) { - $notice->is_local = -1; - } else { - $notice->is_local = $is_local; - } + if ($blacklist && in_array($profile_id, $blacklist)) { + $notice->is_local = -1; + } else { + $notice->is_local = $is_local; + } $notice->query('BEGIN'); - - $notice->reply_to = $reply_to; - $notice->created = common_sql_now(); - $notice->content = common_shorten_links($content); - $notice->rendered = common_render_content($notice->content, $notice); - $notice->source = $source; - $notice->uri = $uri; - $id = $notice->insert(); + $notice->reply_to = $reply_to; + $notice->created = common_sql_now(); + $notice->content = common_shorten_links($content); + $notice->rendered = common_render_content($notice->content, $notice); + $notice->source = $source; + $notice->uri = $uri; - if (!$id) { - common_log_db_error($notice, 'INSERT', __FILE__); - return _('Problem saving notice.'); - } + $id = $notice->insert(); - # Update the URI after the notice is in the database - if (!$uri) { - $orig = clone($notice); - $notice->uri = common_notice_uri($notice); + if (!$id) { + common_log_db_error($notice, 'INSERT', __FILE__); + return _('Problem saving notice.'); + } - if (!$notice->update($orig)) { - common_log_db_error($notice, 'UPDATE', __FILE__); - return _('Problem saving notice.'); - } - } + # Update the URI after the notice is in the database + if (!$uri) { + $orig = clone($notice); + $notice->uri = common_notice_uri($notice); - # XXX: do we need to change this for remote users? + if (!$notice->update($orig)) { + common_log_db_error($notice, 'UPDATE', __FILE__); + return _('Problem saving notice.'); + } + } - common_save_replies($notice); - $notice->saveTags(); + # XXX: do we need to change this for remote users? - // Add to notice inboxes - - $notice->addToInboxes(); + $notice->saveReplies(); + $notice->saveTags(); + $notice->saveGroups(); + $notice->addToInboxes(); $notice->query('COMMIT'); - - # Clear the cache for subscribed users, so they'll update at next request - # XXX: someone clever could prepend instead of clearing the cache - if (common_config('memcached', 'enabled')) { - $notice->blowCaches(); - } + # Clear the cache for subscribed users, so they'll update at next request + # XXX: someone clever could prepend instead of clearing the cache + + if (common_config('memcached', 'enabled')) { + $notice->blowCaches(); + } - return $notice; - } + return $notice; + } static function checkEditThrottle($profile_id) { $profile = Profile::staticGet($profile_id); @@ -191,356 +193,538 @@ class Notice extends Memcached_DataObject return true; } - function blowCaches($blowLast=false) { - $this->blowSubsCache($blowLast); - $this->blowNoticeCache($blowLast); - $this->blowRepliesCache($blowLast); - $this->blowPublicCache($blowLast); - $this->blowTagCache($blowLast); - } - - function blowTagCache($blowLast=false) { - $cache = common_memcache(); - if ($cache) { - $tag = new Notice_tag(); - $tag->notice_id = $this->id; - if ($tag->find()) { - while ($tag->fetch()) { - $cache->delete(common_cache_key('notice_tag:notice_stream:' . $tag->tag)); - if ($blowLast) { - $cache->delete(common_cache_key('notice_tag:notice_stream:' . $tag->tag . ';last')); - } - } - } - $tag->free(); - unset($tag); - } - } - - function blowSubsCache($blowLast=false) { - $cache = common_memcache(); - if ($cache) { - $user = new User(); - - $user->query('SELECT id ' . - 'FROM user JOIN subscription ON user.id = subscription.subscriber ' . - 'WHERE subscription.subscribed = ' . $this->profile_id); - - while ($user->fetch()) { - $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); - if ($blowLast) { - $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id . ';last')); - } - } - $user->free(); - unset($user); - } - } - - function blowNoticeCache($blowLast=false) { - if ($this->is_local) { - $cache = common_memcache(); - if ($cache) { - $cache->delete(common_cache_key('profile:notices:'.$this->profile_id)); - if ($blowLast) { - $cache->delete(common_cache_key('profile:notices:'.$this->profile_id.';last')); - } - } - } - } - - function blowRepliesCache($blowLast=false) { - $cache = common_memcache(); - if ($cache) { - $reply = new Reply(); - $reply->notice_id = $this->id; - if ($reply->find()) { - while ($reply->fetch()) { - $cache->delete(common_cache_key('user:replies:'.$reply->profile_id)); - if ($blowLast) { - $cache->delete(common_cache_key('user:replies:'.$reply->profile_id.';last')); - } - } - } - $reply->free(); - unset($reply); - } - } - - function blowPublicCache($blowLast=false) { - if ($this->is_local == 1) { - $cache = common_memcache(); - if ($cache) { - $cache->delete(common_cache_key('public')); - if ($blowLast) { - $cache->delete(common_cache_key('public').';last'); - } - } - } - } - - function blowFavesCache($blowLast=false) { - $cache = common_memcache(); - if ($cache) { - $fave = new Fave(); - $fave->notice_id = $this->id; - if ($fave->find()) { - while ($fave->fetch()) { - $cache->delete(common_cache_key('user:faves:'.$fave->user_id)); - if ($blowLast) { - $cache->delete(common_cache_key('user:faves:'.$fave->user_id.';last')); - } - } - } - $fave->free(); - unset($fave); - } - } - - # XXX: too many args; we need to move to named params or even a separate - # class for notice streams - - static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $order=NULL, $since=NULL) { - - if (common_config('memcached', 'enabled')) { - - # Skip the cache if this is a since, since_id or before_id qry - if ($since_id > 0 || $before_id > 0 || $since) { - return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since); - } else { - return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order); - } - } - - return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since); - } - - static function getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since) { - - $needAnd = FALSE; - $needWhere = TRUE; - - if (preg_match('/\bWHERE\b/i', $qry)) { - $needWhere = FALSE; - $needAnd = TRUE; - } - - if ($since_id > 0) { - - if ($needWhere) { - $qry .= ' WHERE '; - $needWhere = FALSE; - } else { - $qry .= ' AND '; - } - - $qry .= ' notice.id > ' . $since_id; - } - - if ($before_id > 0) { - - if ($needWhere) { - $qry .= ' WHERE '; - $needWhere = FALSE; - } else { - $qry .= ' AND '; - } - - $qry .= ' notice.id < ' . $before_id; - } + function blowCaches($blowLast=false) + { + $this->blowSubsCache($blowLast); + $this->blowNoticeCache($blowLast); + $this->blowRepliesCache($blowLast); + $this->blowPublicCache($blowLast); + $this->blowTagCache($blowLast); + $this->blowGroupCache($blowLast); + } - if ($since) { - - if ($needWhere) { - $qry .= ' WHERE '; - $needWhere = FALSE; - } else { - $qry .= ' AND '; - } + function blowGroupCache($blowLast=false) + { + $cache = common_memcache(); + if ($cache) { + $group_inbox = new Group_inbox(); + $group_inbox->notice_id = $this->id; + if ($group_inbox->find()) { + while ($group_inbox->fetch()) { + $cache->delete(common_cache_key('group:notices:'.$group_inbox->group_id)); + if ($blowLast) { + $cache->delete(common_cache_key('group:notices:'.$group_inbox->group_id.';last')); + } + $member = new Group_member(); + $member->group_id = $group_inbox->group_id; + if ($member->find()) { + while ($member->fetch()) { + $cache->delete(common_cache_key('user:notices_with_friends:' . $member->profile_id)); + if ($blowLast) { + $cache->delete(common_cache_key('user:notices_with_friends:' . $member->profile_id . ';last')); + } + } + } + } + } + $group_inbox->free(); + unset($group_inbox); + } + } - $qry .= ' notice.created > \'' . date('Y-m-d H:i:s', $since) . '\''; - } + function blowTagCache($blowLast=false) + { + $cache = common_memcache(); + if ($cache) { + $tag = new Notice_tag(); + $tag->notice_id = $this->id; + if ($tag->find()) { + while ($tag->fetch()) { + $cache->delete(common_cache_key('notice_tag:notice_stream:' . $tag->tag)); + if ($blowLast) { + $cache->delete(common_cache_key('notice_tag:notice_stream:' . $tag->tag . ';last')); + } + } + } + $tag->free(); + unset($tag); + } + } - # Allow ORDER override + function blowSubsCache($blowLast=false) + { + $cache = common_memcache(); + if ($cache) { + $user = new User(); + + $user->query('SELECT id ' . + 'FROM user JOIN subscription ON user.id = subscription.subscriber ' . + 'WHERE subscription.subscribed = ' . $this->profile_id); + + while ($user->fetch()) { + $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); + if ($blowLast) { + $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id . ';last')); + } + } + $user->free(); + unset($user); + } + } - if ($order) { - $qry .= $order; - } else { - $qry .= ' ORDER BY notice.created DESC, notice.id DESC '; - } + function blowNoticeCache($blowLast=false) + { + if ($this->is_local) { + $cache = common_memcache(); + if ($cache) { + $cache->delete(common_cache_key('profile:notices:'.$this->profile_id)); + if ($blowLast) { + $cache->delete(common_cache_key('profile:notices:'.$this->profile_id.';last')); + } + } + } + } + + function blowRepliesCache($blowLast=false) + { + $cache = common_memcache(); + if ($cache) { + $reply = new Reply(); + $reply->notice_id = $this->id; + if ($reply->find()) { + while ($reply->fetch()) { + $cache->delete(common_cache_key('user:replies:'.$reply->profile_id)); + if ($blowLast) { + $cache->delete(common_cache_key('user:replies:'.$reply->profile_id.';last')); + } + } + } + $reply->free(); + unset($reply); + } + } + + function blowPublicCache($blowLast=false) + { + if ($this->is_local == 1) { + $cache = common_memcache(); + if ($cache) { + $cache->delete(common_cache_key('public')); + if ($blowLast) { + $cache->delete(common_cache_key('public').';last'); + } + } + } + } - if (common_config('db','type') == 'pgsql') { - $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; - } else { - $qry .= ' LIMIT ' . $offset . ', ' . $limit; - } + function blowFavesCache($blowLast=false) + { + $cache = common_memcache(); + if ($cache) { + $fave = new Fave(); + $fave->notice_id = $this->id; + if ($fave->find()) { + while ($fave->fetch()) { + $cache->delete(common_cache_key('user:faves:'.$fave->user_id)); + if ($blowLast) { + $cache->delete(common_cache_key('user:faves:'.$fave->user_id.';last')); + } + } + } + $fave->free(); + unset($fave); + } + } - $notice = new Notice(); + # XXX: too many args; we need to move to named params or even a separate + # class for notice streams - $notice->query($qry); + static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $order=null, $since=null) { - return $notice; - } + if (common_config('memcached', 'enabled')) { - # XXX: this is pretty long and should probably be broken up into - # some helper functions + # Skip the cache if this is a since, since_id or before_id qry + if ($since_id > 0 || $before_id > 0 || $since) { + return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since); + } else { + return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order); + } + } - static function getCachedStream($qry, $cachekey, $offset, $limit, $order) { + return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since); + } - # If outside our cache window, just go to the DB + static function getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since) { - if ($offset + $limit > NOTICE_CACHE_WINDOW) { - return Notice::getStreamDirect($qry, $offset, $limit, NULL, NULL, $order, NULL); - } + $needAnd = false; + $needWhere = true; - # Get the cache; if we can't, just go to the DB + if (preg_match('/\bWHERE\b/i', $qry)) { + $needWhere = false; + $needAnd = true; + } - $cache = common_memcache(); + if ($since_id > 0) { - if (!$cache) { - return Notice::getStreamDirect($qry, $offset, $limit, NULL, NULL, $order, NULL); - } + if ($needWhere) { + $qry .= ' WHERE '; + $needWhere = false; + } else { + $qry .= ' AND '; + } - # Get the notices out of the cache + $qry .= ' notice.id > ' . $since_id; + } - $notices = $cache->get(common_cache_key($cachekey)); + if ($before_id > 0) { - # On a cache hit, return a DB-object-like wrapper + if ($needWhere) { + $qry .= ' WHERE '; + $needWhere = false; + } else { + $qry .= ' AND '; + } - if ($notices !== FALSE) { - $wrapper = new NoticeWrapper(array_slice($notices, $offset, $limit)); - return $wrapper; - } + $qry .= ' notice.id < ' . $before_id; + } - # If the cache was invalidated because of new data being - # added, we can try and just get the new stuff. We keep an additional - # copy of the data at the key + ';last' + if ($since) { - # No cache hit. Try to get the *last* cached version + if ($needWhere) { + $qry .= ' WHERE '; + $needWhere = false; + } else { + $qry .= ' AND '; + } - $last_notices = $cache->get(common_cache_key($cachekey) . ';last'); + $qry .= ' notice.created > \'' . date('Y-m-d H:i:s', $since) . '\''; + } - if ($last_notices) { + # Allow ORDER override - # Reverse-chron order, so last ID is last. + if ($order) { + $qry .= $order; + } else { + $qry .= ' ORDER BY notice.created DESC, notice.id DESC '; + } - $last_id = $last_notices[0]->id; + if (common_config('db','type') == 'pgsql') { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $qry .= ' LIMIT ' . $offset . ', ' . $limit; + } - # XXX: this assumes monotonically increasing IDs; a fair - # bet with our DB. + $notice = new Notice(); - $new_notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW, - $last_id, NULL, $order, NULL); + $notice->query($qry); - if ($new_notice) { - $new_notices = array(); - while ($new_notice->fetch()) { - $new_notices[] = clone($new_notice); - } - $new_notice->free(); - $notices = array_slice(array_merge($new_notices, $last_notices), - 0, NOTICE_CACHE_WINDOW); + return $notice; + } - # Store the array in the cache for next time + # XXX: this is pretty long and should probably be broken up into + # some helper functions - $result = $cache->set(common_cache_key($cachekey), $notices); - $result = $cache->set(common_cache_key($cachekey) . ';last', $notices); + static function getCachedStream($qry, $cachekey, $offset, $limit, $order) { - # return a wrapper of the array for use now + # If outside our cache window, just go to the DB - return new NoticeWrapper(array_slice($notices, $offset, $limit)); - } - } + if ($offset + $limit > NOTICE_CACHE_WINDOW) { + return Notice::getStreamDirect($qry, $offset, $limit, null, null, $order, null); + } - # Otherwise, get the full cache window out of the DB + # Get the cache; if we can't, just go to the DB - $notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW, NULL, NULL, $order, NULL); + $cache = common_memcache(); - # If there are no hits, just return the value + if (!$cache) { + return Notice::getStreamDirect($qry, $offset, $limit, null, null, $order, null); + } - if (!$notice) { - return $notice; - } + # Get the notices out of the cache - # Pack results into an array + $notices = $cache->get(common_cache_key($cachekey)); - $notices = array(); + # On a cache hit, return a DB-object-like wrapper - while ($notice->fetch()) { - $notices[] = clone($notice); - } + if ($notices !== false) { + $wrapper = new ArrayWrapper(array_slice($notices, $offset, $limit)); + return $wrapper; + } - $notice->free(); + # If the cache was invalidated because of new data being + # added, we can try and just get the new stuff. We keep an additional + # copy of the data at the key + ';last' - # Store the array in the cache for next time + # No cache hit. Try to get the *last* cached version - $result = $cache->set(common_cache_key($cachekey), $notices); - $result = $cache->set(common_cache_key($cachekey) . ';last', $notices); + $last_notices = $cache->get(common_cache_key($cachekey) . ';last'); - # return a wrapper of the array for use now + if ($last_notices) { - $wrapper = new NoticeWrapper(array_slice($notices, $offset, $limit)); + # Reverse-chron order, so last ID is last. - return $wrapper; - } + $last_id = $last_notices[0]->id; - function publicStream($offset=0, $limit=20, $since_id=0, $before_id=0, $since=NULL) { + # XXX: this assumes monotonically increasing IDs; a fair + # bet with our DB. - $parts = array(); + $new_notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW, + $last_id, null, $order, null); - $qry = 'SELECT * FROM notice '; + if ($new_notice) { + $new_notices = array(); + while ($new_notice->fetch()) { + $new_notices[] = clone($new_notice); + } + $new_notice->free(); + $notices = array_slice(array_merge($new_notices, $last_notices), + 0, NOTICE_CACHE_WINDOW); - if (common_config('public', 'localonly')) { - $parts[] = 'is_local = 1'; - } else { - # -1 == blacklisted - $parts[] = 'is_local != -1'; - } + # Store the array in the cache for next time - if ($parts) { - $qry .= ' WHERE ' . implode(' AND ', $parts); - } + $result = $cache->set(common_cache_key($cachekey), $notices); + $result = $cache->set(common_cache_key($cachekey) . ';last', $notices); - return Notice::getStream($qry, - 'public', - $offset, $limit, $since_id, $before_id, NULL, $since); - } + # return a wrapper of the array for use now - function addToInboxes() { - $enabled = common_config('inboxes', 'enabled'); + return new ArrayWrapper(array_slice($notices, $offset, $limit)); + } + } - if ($enabled === true || $enabled === 'transitional') { - $inbox = new Notice_inbox(); - $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created) ' . - 'SELECT user.id, ' . $this->id . ', "' . $this->created . '" ' . - 'FROM user JOIN subscription ON user.id = subscription.subscriber ' . - 'WHERE subscription.subscribed = ' . $this->profile_id . ' ' . - 'AND NOT EXISTS (SELECT user_id, notice_id ' . - 'FROM notice_inbox ' . - 'WHERE user_id = user.id ' . - 'AND notice_id = ' . $this->id . ' )'; - if ($enabled === 'transitional') { - $qry .= ' AND user.inboxed = 1'; - } - $inbox->query($qry); - } - return; - } + # Otherwise, get the full cache window out of the DB - # Delete from inboxes if we're deleted. + $notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW, null, null, $order, null); - function blowInboxes() { + # If there are no hits, just return the value - $enabled = common_config('inboxes', 'enabled'); + if (!$notice) { + return $notice; + } - if ($enabled === true || $enabled === 'transitional') { - $inbox = new Notice_inbox(); - $inbox->notice_id = $this->id; - $inbox->delete(); - } + # Pack results into an array - return; - } + $notices = array(); -} + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + $notice->free(); + + # Store the array in the cache for next time + + $result = $cache->set(common_cache_key($cachekey), $notices); + $result = $cache->set(common_cache_key($cachekey) . ';last', $notices); + + # return a wrapper of the array for use now + + $wrapper = new ArrayWrapper(array_slice($notices, $offset, $limit)); + + return $wrapper; + } + + function publicStream($offset=0, $limit=20, $since_id=0, $before_id=0, $since=null) + { + + $parts = array(); + + $qry = 'SELECT * FROM notice '; + + if (common_config('public', 'localonly')) { + $parts[] = 'is_local = 1'; + } else { + # -1 == blacklisted + $parts[] = 'is_local != -1'; + } + + if ($parts) { + $qry .= ' WHERE ' . implode(' AND ', $parts); + } + + return Notice::getStream($qry, + 'public', + $offset, $limit, $since_id, $before_id, null, $since); + } + + function addToInboxes() + { + $enabled = common_config('inboxes', 'enabled'); + + if ($enabled === true || $enabled === 'transitional') { + $inbox = new Notice_inbox(); + $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created) ' . + 'SELECT user.id, ' . $this->id . ', "' . $this->created . '" ' . + 'FROM user JOIN subscription ON user.id = subscription.subscriber ' . + 'WHERE subscription.subscribed = ' . $this->profile_id . ' ' . + 'AND NOT EXISTS (SELECT user_id, notice_id ' . + 'FROM notice_inbox ' . + 'WHERE user_id = user.id ' . + 'AND notice_id = ' . $this->id . ' )'; + if ($enabled === 'transitional') { + $qry .= ' AND user.inboxed = 1'; + } + $inbox->query($qry); + } + return; + } + + # Delete from inboxes if we're deleted. + + function blowInboxes() + { + + $enabled = common_config('inboxes', 'enabled'); + + if ($enabled === true || $enabled === 'transitional') { + $inbox = new Notice_inbox(); + $inbox->notice_id = $this->id; + $inbox->delete(); + } + + return; + } + + function saveGroups() + { + $enabled = common_config('inboxes', 'enabled'); + if ($enabled !== true && $enabled !== 'transitional') { + return; + } + + /* extract all !group */ + $count = preg_match_all('/(?:^|\s)!([A-Za-z0-9]{1,64})/', + strtolower($this->content), + $match); + if (!$count) { + return true; + } + + $profile = $this->getProfile(); + /* Add them to the database */ + + foreach (array_unique($match[1]) as $nickname) { + /* XXX: remote groups. */ + $group = User_group::staticGet('nickname', $nickname); + + if (!$group) { + continue; + } + + if ($profile->isMember($group)) { + + $gi = new Group_inbox(); + + $gi->group_id = $group->id; + $gi->notice_id = $this->id; + $gi->created = common_sql_now(); + + $result = $gi->insert(); + + if (!$result) { + common_log_db_error($gi, 'INSERT', __FILE__); + } + + // FIXME: do this in an offline daemon + + $inbox = new Notice_inbox(); + $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created, source) ' . + 'SELECT user.id, ' . $this->id . ', "' . $this->created . '", 2 ' . + 'FROM user JOIN group_member ON user.id = group_member.profile_id ' . + 'WHERE group_member.group_id = ' . $group->id . ' ' . + 'AND NOT EXISTS (SELECT user_id, notice_id ' . + 'FROM notice_inbox ' . + 'WHERE user_id = user.id ' . + 'AND notice_id = ' . $this->id . ' )'; + if ($enabled === 'transitional') { + $qry .= ' AND user.inboxed = 1'; + } + $result = $inbox->query($qry); + } + } + } + + function saveReplies() + { + // Alternative reply format + $tname = false; + if (preg_match('/^T ([A-Z0-9]{1,64}) /', $this->content, $match)) { + $tname = $match[1]; + } + // extract all @messages + $cnt = preg_match_all('/(?:^|\s)@([a-z0-9]{1,64})/', $this->content, $match); + + $names = array(); + + if ($cnt || $tname) { + // XXX: is there another way to make an array copy? + $names = ($tname) ? array_unique(array_merge(array(strtolower($tname)), $match[1])) : array_unique($match[1]); + } + + $sender = Profile::staticGet($this->profile_id); + + $replied = array(); + + // store replied only for first @ (what user/notice what the reply directed, + // we assume first @ is it) + + for ($i=0; $i<count($names); $i++) { + $nickname = $names[$i]; + $recipient = common_relative_profile($sender, $nickname, $this->created); + if (!$recipient) { + 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->update($orig); + } + } + // Don't save replies from blocked profile to local user + $recipient_user = User::staticGet('id', $recipient->id); + if ($recipient_user && $recipient_user->hasBlocked($sender)) { + continue; + } + $reply = new Reply(); + $reply->notice_id = $this->id; + $reply->profile_id = $recipient->id; + $id = $reply->insert(); + if (!$id) { + $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); + common_log(LOG_ERR, 'DB error inserting reply: ' . $last_error->message); + common_server_error(sprintf(_('DB error inserting reply: %s'), $last_error->message)); + return; + } else { + $replied[$recipient->id] = 1; + } + } + + // Hash format replies, too + $cnt = preg_match_all('/(?:^|\s)@#([a-z0-9]{1,64})/', $this->content, $match); + if ($cnt) { + foreach ($match[1] as $tag) { + $tagged = Profile_tag::getTagged($sender->id, $tag); + foreach ($tagged as $t) { + if (!$replied[$t->id]) { + // Don't save replies from blocked profile to local user + $t_user = User::staticGet('id', $t->id); + if ($t_user && $t_user->hasBlocked($sender)) { + continue; + } + $reply = new Reply(); + $reply->notice_id = $this->id; + $reply->profile_id = $t->id; + $id = $reply->insert(); + if (!$id) { + common_log_db_error($reply, 'INSERT', __FILE__); + return; + } + } + } + } + } + } +} diff --git a/classes/NoticeWrapper.php b/classes/NoticeWrapper.php deleted file mode 100644 index f8c0aa381..000000000 --- a/classes/NoticeWrapper.php +++ /dev/null @@ -1,59 +0,0 @@ -<?php -/* - * Laconica - a distributed open-source microblogging tool - * Copyright (C) 2008, Controlez-Vous, 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/classes/Notice.php'); - -class NoticeWrapper extends Notice { - - public $id; // int(4) primary_key not_null - public $profile_id; // int(4) not_null - public $uri; // varchar(255) unique_key - public $content; // varchar(140) - public $rendered; // text() - public $url; // varchar(255) - public $created; // datetime() not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - public $reply_to; // int(4) - public $is_local; // tinyint(1) - public $source; // varchar(32) - - var $notices = NULL; - var $i = -1; - - function __construct($arr) { - $this->notices = $arr; - } - - function fetch() { - static $fields = array('id', 'profile_id', 'uri', 'content', 'rendered', - 'url', 'created', 'modified', 'reply_to', 'is_local', 'source'); - $this->i++; - if ($this->i >= count($this->notices)) { - return false; - } else { - $n = $this->notices[$this->i]; - foreach ($fields as $f) { - $this->$f = $n->$f; - } - return true; - } - } -}
\ No newline at end of file diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php index cc482bd19..81ddb4538 100644 --- a/classes/Notice_inbox.php +++ b/classes/Notice_inbox.php @@ -33,7 +33,8 @@ class Notice_inbox extends Memcached_DataObject public $source; // tinyint(1) default_1 /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Notice_inbox',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Notice_inbox',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/Notice_source.php b/classes/Notice_source.php index e0a41b927..e7568bbca 100644 --- a/classes/Notice_source.php +++ b/classes/Notice_source.php @@ -17,7 +17,8 @@ class Notice_source extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Notice_source',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Notice_source',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/Notice_tag.php b/classes/Notice_tag.php index 5b75ff13f..94f9296d6 100644 --- a/classes/Notice_tag.php +++ b/classes/Notice_tag.php @@ -30,26 +30,28 @@ class Notice_tag extends Memcached_DataObject public $created; // datetime() not_null /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Notice_tag',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Notice_tag',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - - static function getStream($tag, $offset=0, $limit=20) { - $qry = - 'SELECT notice.* ' . - 'FROM notice JOIN notice_tag ON notice.id = notice_tag.notice_id ' . - 'WHERE notice_tag.tag = "%s" '; + + static function getStream($tag, $offset=0, $limit=20) { + $qry = + 'SELECT notice.* ' . + 'FROM notice JOIN notice_tag ON notice.id = notice_tag.notice_id ' . + 'WHERE notice_tag.tag = "%s" '; - return Notice::getStream(sprintf($qry, $tag), - 'notice_tag:notice_stream:' . common_keyize($tag), - $offset, $limit); - } - - function blowCache() { - $cache = common_memcache(); - if ($cache) { - $cache->delete(common_cache_key('notice_tag:notice_stream:' . $this->tag)); - } - } + return Notice::getStream(sprintf($qry, $tag), + 'notice_tag:notice_stream:' . common_keyize($tag), + $offset, $limit); + } + + function blowCache() + { + $cache = common_memcache(); + if ($cache) { + $cache->delete(common_cache_key('notice_tag:notice_stream:' . $this->tag)); + } + } } diff --git a/classes/Profile.php b/classes/Profile.php index b57d7e38d..ab5a48e57 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -41,119 +41,180 @@ class Profile extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Profile',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Profile',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function getAvatar($width, $height=NULL) { - if (is_null($height)) { - $height = $width; - } - return Avatar::pkeyGet(array('profile_id' => $this->id, - 'width' => $width, - 'height' => $height)); - } - - function getOriginalAvatar() { - $avatar = DB_DataObject::factory('avatar'); - $avatar->profile_id = $this->id; - $avatar->original = true; - if ($avatar->find(true)) { - return $avatar; - } else { - return NULL; - } - } - - function setOriginal($source) { - - $info = @getimagesize($source); - - if (!$info) { - return NULL; - } - - $filename = common_avatar_filename($this->id, - image_type_to_extension($info[2]), - NULL, common_timestamp()); - $filepath = common_avatar_path($filename); - - copy($source, $filepath); - - $avatar = new Avatar(); - - $avatar->profile_id = $this->id; - $avatar->width = $info[0]; - $avatar->height = $info[1]; - $avatar->mediatype = image_type_to_mime_type($info[2]); - $avatar->filename = $filename; - $avatar->original = true; - $avatar->url = common_avatar_url($filename); - $avatar->created = DB_DataObject_Cast::dateTime(); # current time - - # XXX: start a transaction here - - if (!$this->delete_avatars()) { - @unlink($filepath); - return NULL; - } - - if (!$avatar->insert()) { - @unlink($filepath); - return NULL; - } - - foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) { - # We don't do a scaled one if original is our scaled size - if (!($avatar->width == $size && $avatar->height == $size)) { - $s = $avatar->scale($size); - if (!$s) { - return NULL; - } - } - } - - return $avatar; - } - - function delete_avatars() { - $avatar = new Avatar(); - $avatar->profile_id = $this->id; - $avatar->find(); - while ($avatar->fetch()) { - $avatar->delete(); - } - return true; - } - - function getBestName() { - return ($this->fullname) ? $this->fullname : $this->nickname; - } + function getAvatar($width, $height=null) + { + if (is_null($height)) { + $height = $width; + } + return Avatar::pkeyGet(array('profile_id' => $this->id, + 'width' => $width, + 'height' => $height)); + } + + function getOriginalAvatar() + { + $avatar = DB_DataObject::factory('avatar'); + $avatar->profile_id = $this->id; + $avatar->original = true; + if ($avatar->find(true)) { + return $avatar; + } else { + return null; + } + } + + function setOriginal($source) + { + + $info = @getimagesize($source); + + if (!$info) { + return null; + } + + $filename = common_avatar_filename($this->id, + image_type_to_extension($info[2]), + null, common_timestamp()); + $filepath = common_avatar_path($filename); + + copy($source, $filepath); + + $avatar = new Avatar(); + + $avatar->profile_id = $this->id; + $avatar->width = $info[0]; + $avatar->height = $info[1]; + $avatar->mediatype = image_type_to_mime_type($info[2]); + $avatar->filename = $filename; + $avatar->original = true; + $avatar->url = common_avatar_url($filename); + $avatar->created = DB_DataObject_Cast::dateTime(); # current time + + # XXX: start a transaction here + + if (!$this->delete_avatars()) { + @unlink($filepath); + return null; + } + + if (!$avatar->insert()) { + @unlink($filepath); + return null; + } + + foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) { + # We don't do a scaled one if original is our scaled size + if (!($avatar->width == $size && $avatar->height == $size)) { + $s = $avatar->scale($size); + if (!$s) { + return null; + } + } + } + + return $avatar; + } + + function crop_avatars($x, $y, $w, $h) + { + + $avatar = $this->getOriginalAvatar(); + $this->delete_avatars(false); # don't delete original + + foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) { + # We don't do a scaled one if original is our scaled size + if (!($avatar->width == $size && $avatar->height == $size)) { + $s = $avatar->scale_and_crop($size, $x, $y, $w, $h); + if (!$s) { + return NULL; + } + } + } + return true; + } + + function delete_avatars($original=true) + { + $avatar = new Avatar(); + $avatar->profile_id = $this->id; + $avatar->find(); + while ($avatar->fetch()) { + if ($avatar->original) { + if ($original == false) { + continue; + } + } + $avatar->delete(); + } + return true; + } + + function getBestName() + { + return ($this->fullname) ? $this->fullname : $this->nickname; + } # Get latest notice on or before date; default now - function getCurrentNotice($dt=NULL) { - $notice = new Notice(); - $notice->profile_id = $this->id; - if ($dt) { - $notice->whereAdd('created < "' . $dt . '"'); - } - $notice->orderBy('created DESC, notice.id DESC'); - $notice->limit(1); - if ($notice->find(true)) { - return $notice; - } - return NULL; - } - - function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { - $qry = - 'SELECT * ' . - 'FROM notice ' . - 'WHERE profile_id = %d '; - - return Notice::getStream(sprintf($qry, $this->id), - 'profile:notices:'.$this->id, - $offset, $limit, $since_id, $before_id); - } + function getCurrentNotice($dt=null) + { + $notice = new Notice(); + $notice->profile_id = $this->id; + if ($dt) { + $notice->whereAdd('created < "' . $dt . '"'); + } + $notice->orderBy('created DESC, notice.id DESC'); + $notice->limit(1); + if ($notice->find(true)) { + return $notice; + } + return null; + } + + function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) + { + $qry = + 'SELECT * ' . + 'FROM notice ' . + 'WHERE profile_id = %d '; + + return Notice::getStream(sprintf($qry, $this->id), + 'profile:notices:'.$this->id, + $offset, $limit, $since_id, $before_id); + } + + function isMember($group) + { + $mem = new Group_member(); + + $mem->group_id = $group->id; + $mem->profile_id = $this->id; + + if ($mem->find()) { + return true; + } else { + return false; + } + } + + function isAdmin($group) + { + $mem = new Group_member(); + + $mem->group_id = $group->id; + $mem->profile_id = $this->id; + $mem->is_admin = 1; + + if ($mem->find()) { + return true; + } else { + return false; + } + } + } diff --git a/classes/Profile_block.php b/classes/Profile_block.php index 6ea26a3bc..551e690e2 100644 --- a/classes/Profile_block.php +++ b/classes/Profile_block.php @@ -36,12 +36,14 @@ class Profile_block extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Profile_block',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Profile_block',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function get($blocker, $blocked) { + function get($blocker, $blocked) + { return Memcached_DataObject::pkeyGet('Profile_block', array('blocker' => $blocker, 'blocked' => $blocked)); diff --git a/classes/Profile_tag.php b/classes/Profile_tag.php index dde19aea2..cb60cbaec 100644 --- a/classes/Profile_tag.php +++ b/classes/Profile_tag.php @@ -16,86 +16,87 @@ class Profile_tag extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Profile_tag',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Profile_tag',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - static function getTags($tagger, $tagged) { - - $tags = array(); + static function getTags($tagger, $tagged) { + + $tags = array(); - # XXX: store this in memcached - - $profile_tag = new Profile_tag(); - $profile_tag->tagger = $tagger; - $profile_tag->tagged = $tagged; - - $profile_tag->find(); - - while ($profile_tag->fetch()) { - $tags[] = $profile_tag->tag; - } - - $profile_tag->free(); - - return $tags; - } - - static function setTags($tagger, $tagged, $newtags) { - - $oldtags = Profile_tag::getTags($tagger, $tagged); - - # Delete stuff that's old that not in new - - $to_delete = array_diff($oldtags, $newtags); - - # Insert stuff that's in new and not in old - - $to_insert = array_diff($newtags, $oldtags); - - $profile_tag = new Profile_tag(); - - $profile_tag->tagger = $tagger; - $profile_tag->tagged = $tagged; - - $profile_tag->query('BEGIN'); - - foreach ($to_delete as $deltag) { - $profile_tag->tag = $deltag; - $result = $profile_tag->delete(); - if (!$result) { - common_log_db_error($profile_tag, 'DELETE', __FILE__); - return false; - } - } - - foreach ($to_insert as $instag) { - $profile_tag->tag = $instag; - $result = $profile_tag->insert(); - if (!$result) { - common_log_db_error($profile_tag, 'INSERT', __FILE__); - return false; - } - } - - $profile_tag->query('COMMIT'); - - return true; - } - - # Return profiles with a given tag - static function getTagged($tagger, $tag) { - $profile = new Profile(); - $profile->query('SELECT profile.* ' . - 'FROM profile JOIN profile_tag ' . - 'ON profile.id = profile_tag.tagged ' . - 'WHERE profile_tag.tagger = ' . $tagger . ' ' . - 'AND profile_tag.tag = "' . $tag . '" '); - $tagged = array(); - while ($profile->fetch()) { - $tagged[] = clone($profile); - } - return $tagged; - } + # XXX: store this in memcached + + $profile_tag = new Profile_tag(); + $profile_tag->tagger = $tagger; + $profile_tag->tagged = $tagged; + + $profile_tag->find(); + + while ($profile_tag->fetch()) { + $tags[] = $profile_tag->tag; + } + + $profile_tag->free(); + + return $tags; + } + + static function setTags($tagger, $tagged, $newtags) { + + $oldtags = Profile_tag::getTags($tagger, $tagged); + + # Delete stuff that's old that not in new + + $to_delete = array_diff($oldtags, $newtags); + + # Insert stuff that's in new and not in old + + $to_insert = array_diff($newtags, $oldtags); + + $profile_tag = new Profile_tag(); + + $profile_tag->tagger = $tagger; + $profile_tag->tagged = $tagged; + + $profile_tag->query('BEGIN'); + + foreach ($to_delete as $deltag) { + $profile_tag->tag = $deltag; + $result = $profile_tag->delete(); + if (!$result) { + common_log_db_error($profile_tag, 'DELETE', __FILE__); + return false; + } + } + + foreach ($to_insert as $instag) { + $profile_tag->tag = $instag; + $result = $profile_tag->insert(); + if (!$result) { + common_log_db_error($profile_tag, 'INSERT', __FILE__); + return false; + } + } + + $profile_tag->query('COMMIT'); + + return true; + } + + # Return profiles with a given tag + static function getTagged($tagger, $tag) { + $profile = new Profile(); + $profile->query('SELECT profile.* ' . + 'FROM profile JOIN profile_tag ' . + 'ON profile.id = profile_tag.tagged ' . + 'WHERE profile_tag.tagger = ' . $tagger . ' ' . + 'AND profile_tag.tag = "' . $tag . '" '); + $tagged = array(); + while ($profile->fetch()) { + $tagged[] = clone($profile); + } + return $tagged; + } } diff --git a/classes/Queue_item.php b/classes/Queue_item.php index 8ba3281de..9b909ec22 100644 --- a/classes/Queue_item.php +++ b/classes/Queue_item.php @@ -16,40 +16,42 @@ class Queue_item extends Memcached_DataObject public $claimed; // datetime() /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Queue_item',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Queue_item',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function sequenceKey() { return array(false, false); } - - static function top($transport) { - - $qi = new Queue_item(); - $qi->transport = $transport; - $qi->orderBy('created'); - $qi->whereAdd('claimed is NULL'); - - $qi->limit(1); - - $cnt = $qi->find(TRUE); - - if ($cnt) { - # XXX: potential race condition - # can we force it to only update if claimed is still NULL - # (or old)? - common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id . ' for transport ' . $transport); - $orig = clone($qi); - $qi->claimed = common_sql_now(); - $result = $qi->update($orig); - if ($result) { - common_log(LOG_INFO, 'claim succeeded.'); - return $qi; - } else { - common_log(LOG_INFO, 'claim failed.'); - } - } - $qi = NULL; - return NULL; - } + function sequenceKey() + { return array(false, false); } + + static function top($transport) { + + $qi = new Queue_item(); + $qi->transport = $transport; + $qi->orderBy('created'); + $qi->whereAdd('claimed is null'); + + $qi->limit(1); + + $cnt = $qi->find(true); + + if ($cnt) { + # XXX: potential race condition + # can we force it to only update if claimed is still null + # (or old)? + common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id . ' for transport ' . $transport); + $orig = clone($qi); + $qi->claimed = common_sql_now(); + $result = $qi->update($orig); + if ($result) { + common_log(LOG_INFO, 'claim succeeded.'); + return $qi; + } else { + common_log(LOG_INFO, 'claim failed.'); + } + } + $qi = null; + return null; + } } diff --git a/classes/Related_group.php b/classes/Related_group.php new file mode 100755 index 000000000..c00ad9c44 --- /dev/null +++ b/classes/Related_group.php @@ -0,0 +1,21 @@ +<?php +/** + * Table Definition for related_group + */ + +class Related_group extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'related_group'; // table name + public $group_id; // int(4) primary_key not_null + public $related_group_id; // int(4) primary_key not_null + public $created; // datetime() not_null + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Related_group',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE +} diff --git a/classes/Remember_me.php b/classes/Remember_me.php index 5bbd6cf17..8dc29bfa3 100644 --- a/classes/Remember_me.php +++ b/classes/Remember_me.php @@ -15,10 +15,12 @@ class Remember_me extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Remember_me',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Remember_me',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function sequenceKey() { return array(false, false); } + function sequenceKey() + { return array(false, false); } } diff --git a/classes/Remote_profile.php b/classes/Remote_profile.php index c961dcca2..5aa6d913e 100644 --- a/classes/Remote_profile.php +++ b/classes/Remote_profile.php @@ -38,7 +38,8 @@ class Remote_profile extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Remote_profile',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Remote_profile',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/Reply.php b/classes/Reply.php index d71ce3afb..af86aaf87 100644 --- a/classes/Reply.php +++ b/classes/Reply.php @@ -16,7 +16,8 @@ class Reply extends Memcached_DataObject public $replied_id; // int(4) /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Reply',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Reply',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/Sms_carrier.php b/classes/Sms_carrier.php index 6ecb51346..ffa12de29 100644 --- a/classes/Sms_carrier.php +++ b/classes/Sms_carrier.php @@ -17,12 +17,14 @@ class Sms_carrier extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Sms_carrier',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Sms_carrier',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - - function toEmailAddress($sms) { - return sprintf($this->email_pattern, $sms); - } + + function toEmailAddress($sms) + { + return sprintf($this->email_pattern, $sms); + } } diff --git a/classes/Subscription.php b/classes/Subscription.php index cc174fcce..3fe0d167f 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -40,12 +40,14 @@ class Subscription extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Subscription',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Subscription',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - - function &pkeyGet($kv) { - return Memcached_DataObject::pkeyGet('Subscription', $kv); - } + + function &pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Subscription', $kv); + } } diff --git a/classes/Token.php b/classes/Token.php index d180ecebe..1fabd72f1 100644 --- a/classes/Token.php +++ b/classes/Token.php @@ -19,7 +19,8 @@ class Token extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Token',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Token',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/User.php b/classes/User.php index 5dab5c701..b1bae8835 100644 --- a/classes/User.php +++ b/classes/User.php @@ -62,89 +62,98 @@ class User extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('User',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function getProfile() { - return Profile::staticGet('id', $this->id); - } - - function isSubscribed($other) { - assert(!is_null($other)); - # XXX: cache results of this query - $sub = Subscription::pkeyGet(array('subscriber' => $this->id, - 'subscribed' => $other->id)); - return (is_null($sub)) ? false : true; - } - - # 'update' won't write key columns, so we have to do it ourselves. - - function updateKeys(&$orig) { - $parts = array(); - foreach (array('nickname', 'email', 'jabber', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) { - if (strcmp($this->$k, $orig->$k) != 0) { - $parts[] = $k . ' = ' . $this->_quote($this->$k); - } - } - if (count($parts) == 0) { - # No changes - return true; - } - $toupdate = implode(', ', $parts); - - $table = $this->tableName(); - if(common_config('db','quote_identifiers')) { - $table = '"' . $table . '"'; - } - $qry = 'UPDATE ' . $table . ' SET ' . $toupdate . - ' WHERE id = ' . $this->id; - $orig->decache(); - $result = $this->query($qry); - if ($result) { - $this->encache(); - } - return $result; - } - - function allowed_nickname($nickname) { - # XXX: should already be validated for size, content, etc. - static $blacklist = array('rss', 'xrds', 'doc', 'main', - 'settings', 'notice', 'user', - 'search', 'avatar', 'tag', 'tags', - 'api', 'message'); - $merged = array_merge($blacklist, common_config('nickname', 'blacklist')); - return !in_array($nickname, $merged); - } - - function getCurrentNotice($dt=NULL) { - $profile = $this->getProfile(); - if (!$profile) { - return NULL; - } - return $profile->getCurrentNotice($dt); - } - - function getCarrier() { - return Sms_carrier::staticGet('id', $this->carrier); - } - - function subscribeTo($other) { - $sub = new Subscription(); - $sub->subscriber = $this->id; - $sub->subscribed = $other->id; - - $sub->created = common_sql_now(); # current time - - if (!$sub->insert()) { - return false; - } - - return true; - } - - function hasBlocked($other) { + function getProfile() + { + return Profile::staticGet('id', $this->id); + } + + function isSubscribed($other) + { + assert(!is_null($other)); + # XXX: cache results of this query + $sub = Subscription::pkeyGet(array('subscriber' => $this->id, + 'subscribed' => $other->id)); + return (is_null($sub)) ? false : true; + } + + # 'update' won't write key columns, so we have to do it ourselves. + + function updateKeys(&$orig) + { + $parts = array(); + foreach (array('nickname', 'email', 'jabber', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) { + if (strcmp($this->$k, $orig->$k) != 0) { + $parts[] = $k . ' = ' . $this->_quote($this->$k); + } + } + if (count($parts) == 0) { + # No changes + return true; + } + $toupdate = implode(', ', $parts); + + $table = $this->tableName(); + if(common_config('db','quote_identifiers')) { + $table = '"' . $table . '"'; + } + $qry = 'UPDATE ' . $table . ' SET ' . $toupdate . + ' WHERE id = ' . $this->id; + $orig->decache(); + $result = $this->query($qry); + if ($result) { + $this->encache(); + } + return $result; + } + + function allowed_nickname($nickname) + { + # XXX: should already be validated for size, content, etc. + static $blacklist = array('rss', 'xrds', 'doc', 'main', + 'settings', 'notice', 'user', + 'search', 'avatar', 'tag', 'tags', + 'api', 'message', 'group', 'groups'); + $merged = array_merge($blacklist, common_config('nickname', 'blacklist')); + return !in_array($nickname, $merged); + } + + function getCurrentNotice($dt=null) + { + $profile = $this->getProfile(); + if (!$profile) { + return null; + } + return $profile->getCurrentNotice($dt); + } + + function getCarrier() + { + return Sms_carrier::staticGet('id', $this->carrier); + } + + function subscribeTo($other) + { + $sub = new Subscription(); + $sub->subscriber = $this->id; + $sub->subscribed = $other->id; + + $sub->created = common_sql_now(); # current time + + if (!$sub->insert()) { + return false; + } + + return true; + } + + function hasBlocked($other) + { $block = Profile_block::get($this->id, $other->id); @@ -158,260 +167,273 @@ class User extends Memcached_DataObject return $result; } - static function register($fields) { + static function register($fields) { - # MAGICALLY put fields into current scope + # MAGICALLY put fields into current scope - extract($fields); + extract($fields); - $profile = new Profile(); + $profile = new Profile(); - $profile->query('BEGIN'); + $profile->query('BEGIN'); - $profile->nickname = $nickname; - $profile->profileurl = common_profile_url($nickname); + $profile->nickname = $nickname; + $profile->profileurl = common_profile_url($nickname); - if ($fullname) { - $profile->fullname = $fullname; - } - if ($homepage) { - $profile->homepage = $homepage; - } - if ($bio) { - $profile->bio = $bio; - } - if ($location) { - $profile->location = $location; - } + if ($fullname) { + $profile->fullname = $fullname; + } + if ($homepage) { + $profile->homepage = $homepage; + } + if ($bio) { + $profile->bio = $bio; + } + if ($location) { + $profile->location = $location; + } - $profile->created = common_sql_now(); + $profile->created = common_sql_now(); - $id = $profile->insert(); + $id = $profile->insert(); - if (!$id) { - common_log_db_error($profile, 'INSERT', __FILE__); - return FALSE; - } + if (!$id) { + common_log_db_error($profile, 'INSERT', __FILE__); + return false; + } - $user = new User(); + $user = new User(); - $user->id = $id; - $user->nickname = $nickname; + $user->id = $id; + $user->nickname = $nickname; + + if ($password) { # may not have a password for OpenID users + $user->password = common_munge_password($password, $id); + } + + # Users who respond to invite email have proven their ownership of that address + + if ($code) { + $invite = Invitation::staticGet($code); + if ($invite && $invite->address && $invite->address_type == 'email' && $invite->address == $email) { + $user->email = $invite->address; + } + } + + $inboxes = common_config('inboxes', 'enabled'); + + if ($inboxes === true || $inboxes == 'transitional') { + $user->inboxed = 1; + } + + $user->created = common_sql_now(); + $user->uri = common_user_uri($user); + + $result = $user->insert(); + + if (!$result) { + common_log_db_error($user, 'INSERT', __FILE__); + return false; + } + + # Everyone is subscribed to themself + + $subscription = new Subscription(); + $subscription->subscriber = $user->id; + $subscription->subscribed = $user->id; + $subscription->created = $user->created; + + $result = $subscription->insert(); + + if (!$result) { + common_log_db_error($subscription, 'INSERT', __FILE__); + return false; + } + + if ($email && !$user->email) { + + $confirm = new Confirm_address(); + $confirm->code = common_confirmation_code(128); + $confirm->user_id = $user->id; + $confirm->address = $email; + $confirm->address_type = 'email'; + + $result = $confirm->insert(); + if (!$result) { + common_log_db_error($confirm, 'INSERT', __FILE__); + return false; + } + } + + if ($code && $user->email) { + $user->emailChanged(); + } + + $profile->query('COMMIT'); + + if ($email && !$user->email) { + mail_confirm_address($user, $confirm->code, $profile->nickname, $email); + } + + return $user; + } - if ($password) { # may not have a password for OpenID users - $user->password = common_munge_password($password, $id); - } + # Things we do when the email changes - # Users who respond to invite email have proven their ownership of that address + function emailChanged() + { - if ($code) { - $invite = Invitation::staticGet($code); - if ($invite && $invite->address && $invite->address_type == 'email' && $invite->address == $email) { - $user->email = $invite->address; - } - } - - $inboxes = common_config('inboxes', 'enabled'); - - if ($inboxes === true || $inboxes == 'transitional') { - $user->inboxed = 1; - } - - $user->created = common_sql_now(); - $user->uri = common_user_uri($user); - - $result = $user->insert(); - - if (!$result) { - common_log_db_error($user, 'INSERT', __FILE__); - return FALSE; - } - - # Everyone is subscribed to themself - - $subscription = new Subscription(); - $subscription->subscriber = $user->id; - $subscription->subscribed = $user->id; - $subscription->created = $user->created; - - $result = $subscription->insert(); - - if (!$result) { - common_log_db_error($subscription, 'INSERT', __FILE__); - return FALSE; - } - - if ($email && !$user->email) { - - $confirm = new Confirm_address(); - $confirm->code = common_confirmation_code(128); - $confirm->user_id = $user->id; - $confirm->address = $email; - $confirm->address_type = 'email'; - - $result = $confirm->insert(); - if (!$result) { - common_log_db_error($confirm, 'INSERT', __FILE__); - return FALSE; - } - } - - if ($code && $user->email) { - $user->emailChanged(); - } - - $profile->query('COMMIT'); - - if ($email && !$user->email) { - mail_confirm_address($user, $confirm->code, $profile->nickname, $email); - } - - return $user; - } - - # Things we do when the email changes - - function emailChanged() { - - $invites = new Invitation(); - $invites->address = $this->email; - $invites->address_type = 'email'; - - if ($invites->find()) { - while ($invites->fetch()) { - $other = User::staticGet($invites->user_id); - subs_subscribe_to($other, $this); - } - } - } - - function hasFave($notice) { - $cache = common_memcache(); - - # XXX: Kind of a hack. - if ($cache) { - # This is the stream of favorite notices, in rev chron - # order. This forces it into cache. - $faves = $this->favoriteNotices(0, NOTICE_CACHE_WINDOW); - $cnt = 0; - while ($faves->fetch()) { - if ($faves->id < $notice->id) { - # If we passed it, it's not a fave - return false; - } else if ($faves->id == $notice->id) { - # If it matches a cached notice, then it's a fave - return true; - } - $cnt++; - } - # If we're not past the end of the cache window, - # then the cache has all available faves, so this one - # is not a fave. - if ($cnt < NOTICE_CACHE_WINDOW) { - return false; - } - # Otherwise, cache doesn't have all faves; - # fall through to the default - } - $fave = Fave::pkeyGet(array('user_id' => $this->id, - 'notice_id' => $notice->id)); - return ((is_null($fave)) ? false : true); - } - function mutuallySubscribed($other) { - return $this->isSubscribed($other) && - $other->isSubscribed($this); - } - - function mutuallySubscribedUsers() { - - # 3-way join; probably should get cached - $qry = 'SELECT user.* ' . - 'FROM subscription sub1 JOIN user ON sub1.subscribed = user.id ' . - 'JOIN subscription sub2 ON user.id = sub2.subscriber ' . - 'WHERE sub1.subscriber = %d and sub2.subscribed = %d ' . - 'ORDER BY user.nickname'; - $user = new User(); - $user->query(sprintf($qry, $this->id, $this->id)); - - return $user; - } - - function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=NULL) { - $qry = - 'SELECT notice.* ' . - 'FROM notice JOIN reply ON notice.id = reply.notice_id ' . - 'WHERE reply.profile_id = %d '; - return Notice::getStream(sprintf($qry, $this->id), - 'user:replies:'.$this->id, - $offset, $limit, $since_id, $before_id, NULL, $since); - } - - function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=NULL) { + $invites = new Invitation(); + $invites->address = $this->email; + $invites->address_type = 'email'; + + if ($invites->find()) { + while ($invites->fetch()) { + $other = User::staticGet($invites->user_id); + subs_subscribe_to($other, $this); + } + } + } + + function hasFave($notice) + { + $cache = common_memcache(); + + # XXX: Kind of a hack. + if ($cache) { + # This is the stream of favorite notices, in rev chron + # order. This forces it into cache. + $faves = $this->favoriteNotices(0, NOTICE_CACHE_WINDOW); + $cnt = 0; + while ($faves->fetch()) { + if ($faves->id < $notice->id) { + # If we passed it, it's not a fave + return false; + } else if ($faves->id == $notice->id) { + # If it matches a cached notice, then it's a fave + return true; + } + $cnt++; + } + # If we're not past the end of the cache window, + # then the cache has all available faves, so this one + # is not a fave. + if ($cnt < NOTICE_CACHE_WINDOW) { + return false; + } + # Otherwise, cache doesn't have all faves; + # fall through to the default + } + $fave = Fave::pkeyGet(array('user_id' => $this->id, + 'notice_id' => $notice->id)); + return ((is_null($fave)) ? false : true); + } + function mutuallySubscribed($other) + { + return $this->isSubscribed($other) && + $other->isSubscribed($this); + } + + function mutuallySubscribedUsers() + { + + # 3-way join; probably should get cached + $qry = 'SELECT user.* ' . + 'FROM subscription sub1 JOIN user ON sub1.subscribed = user.id ' . + 'JOIN subscription sub2 ON user.id = sub2.subscriber ' . + 'WHERE sub1.subscriber = %d and sub2.subscribed = %d ' . + 'ORDER BY user.nickname'; + $user = new User(); + $user->query(sprintf($qry, $this->id, $this->id)); + + return $user; + } + + function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + { + $qry = + 'SELECT notice.* ' . + 'FROM notice JOIN reply ON notice.id = reply.notice_id ' . + 'WHERE reply.profile_id = %d '; + return Notice::getStream(sprintf($qry, $this->id), + 'user:replies:'.$this->id, + $offset, $limit, $since_id, $before_id, null, $since); + } + + function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + { $profile = $this->getProfile(); if (!$profile) { - return NULL; + return null; } else { return $profile->getNotices($offset, $limit, $since_id, $before_id); } - } - - function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE) { - $qry = - 'SELECT notice.* ' . - 'FROM notice JOIN fave ON notice.id = fave.notice_id ' . - 'WHERE fave.user_id = %d '; - return Notice::getStream(sprintf($qry, $this->id), - 'user:faves:'.$this->id, - $offset, $limit); - } - - function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=NULL) { - $enabled = common_config('inboxes', 'enabled'); - - # Complicated code, depending on whether we support inboxes yet - # XXX: make this go away when inboxes become mandatory - - if ($enabled === false || - ($enabled == 'transitional' && $this->inboxed == 0)) { - $qry = - 'SELECT notice.* ' . - 'FROM notice JOIN subscription ON notice.profile_id = subscription.subscribed ' . - 'WHERE subscription.subscriber = %d '; - $order = NULL; - } else if ($enabled === true || - ($enabled == 'transitional' && $this->inboxed == 1)) { - - $qry = - 'SELECT notice.* ' . - 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' . - 'WHERE notice_inbox.user_id = %d '; - $order = null; - } - return Notice::getStream(sprintf($qry, $this->id), - 'user:notices_with_friends:' . $this->id, - $offset, $limit, $since_id, $before_id, - $order, $since); - } - - function blowFavesCache() { - $cache = common_memcache(); - if ($cache) { - # Faves don't happen chronologically, so we need to blow - # ;last cache, too - $cache->delete(common_cache_key('user:faves:'.$this->id)); - $cache->delete(common_cache_key('user:faves:'.$this->id).';last'); - } - } - - function getSelfTags() { - return Profile_tag::getTags($this->id, $this->id); - } - - function setSelfTags($newtags) { - return Profile_tag::setTags($this->id, $this->id, $newtags); - } - - function block($other) { + } + + function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE) + { + $qry = + 'SELECT notice.* ' . + 'FROM notice JOIN fave ON notice.id = fave.notice_id ' . + 'WHERE fave.user_id = %d '; + return Notice::getStream(sprintf($qry, $this->id), + 'user:faves:'.$this->id, + $offset, $limit); + } + + function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + { + $enabled = common_config('inboxes', 'enabled'); + + # Complicated code, depending on whether we support inboxes yet + # XXX: make this go away when inboxes become mandatory + + if ($enabled === false || + ($enabled == 'transitional' && $this->inboxed == 0)) { + $qry = + 'SELECT notice.* ' . + 'FROM notice JOIN subscription ON notice.profile_id = subscription.subscribed ' . + 'WHERE subscription.subscriber = %d '; + $order = null; + } else if ($enabled === true || + ($enabled == 'transitional' && $this->inboxed == 1)) { + + $qry = + 'SELECT notice.* ' . + 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' . + 'WHERE notice_inbox.user_id = %d '; + # NOTE: we override ORDER + $order = null; + } + return Notice::getStream(sprintf($qry, $this->id), + 'user:notices_with_friends:' . $this->id, + $offset, $limit, $since_id, $before_id, + $order, $since); + } + + function blowFavesCache() + { + $cache = common_memcache(); + if ($cache) { + # Faves don't happen chronologically, so we need to blow + # ;last cache, too + $cache->delete(common_cache_key('user:faves:'.$this->id)); + $cache->delete(common_cache_key('user:faves:'.$this->id).';last'); + } + } + + function getSelfTags() + { + return Profile_tag::getTags($this->id, $this->id); + } + + function setSelfTags($newtags) + { + return Profile_tag::setTags($this->id, $this->id, $newtags); + } + + function block($other) + { # Add a new block record @@ -433,8 +455,8 @@ class User extends Memcached_DataObject # Cancel their subscription, if it exists - $sub = Subscription::pkeyGet(array('subscriber' => $other->id, - 'subscribed' => $this->id)); + $sub = Subscription::pkeyGet(array('subscriber' => $other->id, + 'subscribed' => $this->id)); if ($sub) { $result = $sub->delete(); @@ -449,7 +471,8 @@ class User extends Memcached_DataObject return true; } - function unblock($other) { + function unblock($other) + { # Get the block record @@ -469,4 +492,141 @@ class User extends Memcached_DataObject return true; } + function isMember($group) + { + $profile = $this->getProfile(); + return $profile->isMember($group); + } + + function isAdmin($group) + { + $profile = $this->getProfile(); + return $profile->isAdmin($group); + } + + function getGroups($offset=0, $limit=null) + { + $qry = + 'SELECT user_group.* ' . + 'FROM user_group JOIN group_member '. + 'ON user_group.id = group_member.group_id ' . + 'WHERE group_member.profile_id = %d ' . + 'ORDER BY group_member.created DESC '; + + if ($offset) { + if (common_config('db','type') == 'pgsql') { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $qry .= ' LIMIT ' . $offset . ', ' . $limit; + } + } + + $groups = new User_group(); + + $cnt = $groups->query(sprintf($qry, $this->id)); + + return $groups; + } + + function getSubscriptions($offset=0, $limit=null) + { + $qry = + 'SELECT profile.* ' . + 'FROM profile JOIN subscription ' . + 'ON profile.id = subscription.subscribed ' . + 'WHERE subscription.subscriber = %d ' . + 'AND subscription.subscribed != subscription.subscriber ' . + 'ORDER BY subscription.created DESC '; + + if (common_config('db','type') == 'pgsql') { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $qry .= ' LIMIT ' . $offset . ', ' . $limit; + } + + $profile = new Profile(); + + $profile->query(sprintf($qry, $this->id)); + + return $profile; + } + + function getSubscribers($offset=0, $limit=null) + { + $qry = + 'SELECT profile.* ' . + 'FROM profile JOIN subscription ' . + 'ON profile.id = subscription.subscriber ' . + 'WHERE subscription.subscribed = %d ' . + 'AND subscription.subscribed != subscription.subscriber ' . + 'ORDER BY subscription.created DESC '; + + if ($offset) { + if (common_config('db','type') == 'pgsql') { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $qry .= ' LIMIT ' . $offset . ', ' . $limit; + } + } + + $profile = new Profile(); + + $cnt = $profile->query(sprintf($qry, $this->id)); + + return $profile; + } + + function getTaggedSubscribers($tag, $offset=0, $limit=null) + { + $qry = + 'SELECT profile.* ' . + 'FROM profile JOIN subscription ' . + 'ON profile.id = subscription.subscriber ' . + 'JOIN profile_tag ON (profile_tag.tagged = subscription.subscriber ' . + 'AND profile_tag.tagger = subscription.subscribed) ' . + 'WHERE subscription.subscribed = %d ' . + 'AND profile_tag.tag = "%s" ' . + 'AND subscription.subscribed != subscription.subscriber ' . + 'ORDER BY subscription.created DESC '; + + if ($offset) { + if (common_config('db','type') == 'pgsql') { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $qry .= ' LIMIT ' . $offset . ', ' . $limit; + } + } + + $profile = new Profile(); + + $cnt = $profile->query(sprintf($qry, $this->id, $tag)); + + return $profile; + } + + function getTaggedSubscriptions($tag, $offset=0, $limit=null) + { + $qry = + 'SELECT profile.* ' . + 'FROM profile JOIN subscription ' . + 'ON profile.id = subscription.subscribed ' . + 'JOIN profile_tag on (profile_tag.tagged = subscription.subscribed ' . + 'AND profile_tag.tagger = subscription.subscriber) ' . + 'WHERE subscription.subscriber = %d ' . + 'AND profile_tag.tag = "%s" ' . + 'AND subscription.subscribed != subscription.subscriber ' . + 'ORDER BY subscription.created DESC '; + + if (common_config('db','type') == 'pgsql') { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $qry .= ' LIMIT ' . $offset . ', ' . $limit; + } + + $profile = new Profile(); + + $profile->query(sprintf($qry, $this->id, $tag)); + + return $profile; + } } diff --git a/classes/User_group.php b/classes/User_group.php new file mode 100755 index 000000000..98ad77cc0 --- /dev/null +++ b/classes/User_group.php @@ -0,0 +1,167 @@ +<?php +/** + * Table Definition for user_group + */ + +class User_group extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'user_group'; // table name + public $id; // int(4) primary_key not_null + public $nickname; // varchar(64) unique_key + public $fullname; // varchar(255) + public $homepage; // varchar(255) + public $description; // varchar(140) + public $location; // varchar(255) + public $original_logo; // varchar(255) + public $homepage_logo; // varchar(255) + public $stream_logo; // varchar(255) + public $mini_logo; // varchar(255) + public $created; // datetime() not_null + public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_group',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function defaultLogo($size) + { + static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile', + AVATAR_STREAM_SIZE => 'stream', + AVATAR_MINI_SIZE => 'mini'); + return theme_path('default-avatar-'.$sizenames[$size].'.png'); + } + + function homeUrl() + { + return common_local_url('showgroup', + array('nickname' => $this->nickname)); + } + + function permalink() + { + return common_local_url('groupbyid', + array('id' => $this->id)); + } + + function getNotices($offset, $limit) + { + $qry = + 'SELECT notice.* ' . + 'FROM notice JOIN group_inbox ON notice.id = group_inbox.notice_id ' . + 'WHERE group_inbox.group_id = %d '; + return Notice::getStream(sprintf($qry, $this->id), + 'group:notices:'.$this->id, + $offset, $limit); + } + + function allowedNickname($nickname) + { + static $blacklist = array('new'); + return !in_array($nickname, $blacklist); + } + + function getMembers($offset=0, $limit=null) + { + $qry = + 'SELECT profile.* ' . + 'FROM profile JOIN group_member '. + 'ON profile.id = group_member.profile_id ' . + 'WHERE group_member.group_id = %d ' . + 'ORDER BY group_member.created DESC '; + + if (common_config('db','type') == 'pgsql') { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $qry .= ' LIMIT ' . $offset . ', ' . $limit; + } + + $members = new Profile(); + + $cnt = $members->query(sprintf($qry, $this->id)); + + return $members; + } + + function setOriginal($filename, $type) + { + $orig = clone($this); + $this->original_logo = common_avatar_url($filename); + $this->homepage_logo = common_avatar_url($this->scale($filename, + AVATAR_PROFILE_SIZE, + $type)); + $this->stream_logo = common_avatar_url($this->scale($filename, + AVATAR_STREAM_SIZE, + $type)); + $this->mini_logo = common_avatar_url($this->scale($filename, + AVATAR_MINI_SIZE, + $type)); + common_debug(common_log_objstring($this)); + return $this->update($orig); + } + + function scale($filename, $size, $type) + { + $filepath = common_avatar_path($filename); + + if (!file_exists($filepath)) { + $this->serverError(_('Lost our file.')); + return; + } + + $info = @getimagesize($filepath); + + switch ($type) { + case IMAGETYPE_GIF: + $image_src = imagecreatefromgif($filepath); + break; + case IMAGETYPE_JPEG: + $image_src = imagecreatefromjpeg($filepath); + break; + case IMAGETYPE_PNG: + $image_src = imagecreatefrompng($filepath); + break; + default: + $this->serverError(_('Unknown file type')); + return; + } + + $image_dest = imagecreatetruecolor($size, $size); + + $background = imagecolorallocate($image_dest, 0, 0, 0); + ImageColorTransparent($image_dest, $background); + imagealphablending($image_dest, false); + + imagecopyresized($image_dest, $image_src, 0, 0, $x, $y, $size, $size, $info[0], $info[1]); + + $cur = common_current_user(); + + $outname = common_avatar_filename($cur->id, + image_type_to_extension($type), + null, + common_timestamp()); + + $outpath = common_avatar_path($outname); + + switch ($type) { + case IMAGETYPE_GIF: + imagegif($image_dest, $outpath); + break; + case IMAGETYPE_JPEG: + imagejpeg($image_dest, $outpath); + break; + case IMAGETYPE_PNG: + imagepng($image_dest, $outpath); + break; + default: + $this->serverError(_('Unknown file type')); + return; + } + + return $outname; + } +} diff --git a/classes/User_openid.php b/classes/User_openid.php index ad68f7402..f4fda1c72 100644 --- a/classes/User_openid.php +++ b/classes/User_openid.php @@ -17,7 +17,8 @@ class User_openid extends Memcached_DataObject public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_openid',$k,$v); } + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('User_openid',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/classes/laconica.ini b/classes/laconica.ini index db76b2dee..255122a97 100644..100755 --- a/classes/laconica.ini +++ b/classes/laconica.ini @@ -98,6 +98,26 @@ id = K service = K uri = U +[group_inbox] +group_id = 129 +notice_id = 129 +created = 142 + +[group_inbox__keys] +group_id = K +notice_id = K + +[group_member] +group_id = 129 +profile_id = 129 +is_admin = 17 +created = 142 +modified = 384 + +[group_member__keys] +group_id = K +profile_id = K + [invitation] code = 130 user_id = 129 @@ -225,6 +245,15 @@ claimed = 14 notice_id = K transport = K +[related_group] +group_id = 129 +related_group_id = 129 +created = 142 + +[related_group__keys] +group_id = K +related_group_id = K + [remember_me] code = 130 user_id = 129 @@ -332,6 +361,23 @@ jabber = U sms = U uri = U +[user_group] +id = 129 +nickname = 2 +fullname = 2 +homepage = 2 +description = 2 +location = 2 +original_logo = 2 +homepage_logo = 2 +stream_logo = 2 +mini_logo = 2 +created = 142 +modified = 384 + +[user_group__keys] +id = N + [user_openid] canonical = 130 display = 130 |