function getUsers($y, $m, $d, $i) { $u = User::cacheGet("sitemap:user:{$y}:{$m}:{$d}:{$i}"); if ($u === false) { $user = new User(); $begindt = sprintf('%04d-%02d-%02d 00:00:00', $y, $m, $d); // XXX: estimates 1d == 24h, which screws up days // with leap seconds (1d == 24h + 1s). Thankfully they're // few and far between. $theend = strtotime($begindt) + 24 * 60 * 60; $enddt = common_sql_date($theend); $user->selectAdd(); $user->selectAdd('nickname'); $user->whereAdd("created >= '{$begindt}'"); $user->whereAdd("created < '{$enddt}'"); $user->orderBy('created'); $offset = ($i - 1) * SitemapPlugin::USERS_PER_MAP; $limit = SitemapPlugin::USERS_PER_MAP; $user->limit($offset, $limit); $user->find(); while ($user->fetch()) { $u[] = $user->nickname; } $c = Cache::instance(); if (!empty($c)) { $c->set(Cache::key("sitemap:user:{$y}:{$m}:{$d}:{$i}"), $u, Cache::COMPRESSED, time() > $theend ? time() + 90 * 24 * 60 * 60 : time() + 5 * 60); } } return $u; }
function getNotices($y, $m, $d, $i) { $n = Notice::cacheGet("sitemap:notice:{$y}:{$m}:{$d}:{$i}"); if ($n === false) { $notice = new Notice(); $begindt = sprintf('%04d-%02d-%02d 00:00:00', $y, $m, $d); // XXX: estimates 1d == 24h, which screws up days // with leap seconds (1d == 24h + 1s). Thankfully they're // few and far between. $theend = strtotime($begindt) + 24 * 60 * 60; $enddt = common_sql_date($theend); $notice->selectAdd(); $notice->selectAdd('id, created'); $notice->whereAdd("created >= '{$begindt}'"); $notice->whereAdd("created < '{$enddt}'"); $notice->whereAdd('is_local = ' . Notice::LOCAL_PUBLIC); $notice->orderBy('created'); $offset = ($i - 1) * SitemapPlugin::NOTICES_PER_MAP; $limit = SitemapPlugin::NOTICES_PER_MAP; $notice->limit($offset, $limit); $notice->find(); $n = array(); while ($notice->fetch()) { $n[] = array($notice->id, $notice->created); } $c = Cache::instance(); if (!empty($c)) { $c->set(Cache::key("sitemap:notice:{$y}:{$m}:{$d}:{$i}"), $n, Cache::COMPRESSED, time() > $theend ? time() + 90 * 24 * 60 * 60 : time() + 5 * 60); } } return $n; }
/** * Save a favorite record. * @fixme post-author notification should be moved here * * @param Profile $actor the local or remote Profile who favorites * @param Notice $target the notice that is favorited * @return Fave record on success * @throws Exception on failure */ static function addNew(Profile $actor, Notice $target) { if (self::existsForProfile($target, $actor)) { // TRANS: Client error displayed when trying to mark a notice as favorite that already is a favorite. throw new AlreadyFulfilledException(_('You have already favorited this!')); } $act = new Activity(); $act->type = ActivityObject::ACTIVITY; $act->verb = ActivityVerb::FAVORITE; $act->time = time(); $act->id = self::newUri($actor, $target, common_sql_date($act->time)); $act->title = _("Favor"); // TRANS: Message that is the "content" of a favorite (%1$s is the actor's nickname, %2$ is the favorited // notice's nickname and %3$s is the content of the favorited notice.) $act->content = sprintf(_('%1$s favorited something by %2$s: %3$s'), $actor->getNickname(), $target->getProfile()->getNickname(), $target->rendered ?: $target->content); $act->actor = $actor->asActivityObject(); $act->target = $target->asActivityObject(); $act->objects = array(clone $act->target); $url = common_local_url('AtomPubShowFavorite', array('profile' => $actor->id, 'notice' => $target->id)); $act->selfLink = $url; $act->editLink = $url; // saveActivity will in turn also call Fave::saveActivityObject which does // what this function used to do before this commit. $stored = Notice::saveActivity($act, $actor); return $stored; }
function getNotices() { // @fixme there should be a common func for this if (common_config('db', 'type') == 'pgsql') { if (!empty($this->out->tag)) { $tag = pg_escape_string($this->out->tag); } } else { if (!empty($this->out->tag)) { $tag = mysql_escape_string($this->out->tag); } } $weightexpr = common_sql_weight('fave.modified', common_config('popular', 'dropoff')); $cutoff = sprintf("fave.modified > '%s'", common_sql_date(time() - common_config('popular', 'cutoff'))); $qry = "SELECT notice.*, {$weightexpr} as weight "; if (isset($tag)) { $qry .= 'FROM notice_tag, notice JOIN fave ON notice.id = fave.notice_id ' . "WHERE {$cutoff} and notice.id = notice_tag.notice_id and '{$tag}' = notice_tag.tag"; } else { $qry .= 'FROM notice JOIN fave ON notice.id = fave.notice_id ' . "WHERE {$cutoff}"; } $qry .= ' GROUP BY notice.id,notice.profile_id,notice.content,notice.uri,' . 'notice.rendered,notice.url,notice.created,notice.modified,' . 'notice.reply_to,notice.is_local,notice.source,notice.conversation, ' . 'notice.lat,notice.lon,location_id,location_ns,notice.repeat_of' . ' ORDER BY weight DESC'; $offset = 0; $limit = NOTICES_PER_SECTION + 1; $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; $notice = Memcached_DataObject::cachedQuery('Notice', $qry, 1200); return $notice; }
public function setupFeedSub(FeedSub $feedsub, $interval = 300) { $orig = clone $feedsub; $feedsub->sub_state = 'nohub'; $feedsub->sub_start = common_sql_date(time()); $feedsub->sub_end = ''; $feedsub->last_update = common_sql_date(time() - $interval); // force polling as soon as we can $feedsub->update($orig); }
function getUpdates($seconds) { $notice = new Notice(); # XXX: cache this. Depends on how big this protocol becomes; # Re-doing this query every 15 seconds isn't the end of the world. $divider = common_sql_date(time() - $seconds); $notice->query('SELECT profile_id, max(id) AS max_id ' . 'FROM ( ' . 'SELECT profile_id, id FROM notice ' . (common_config('db', 'type') == 'pgsql' ? 'WHERE extract(epoch from created) > (extract(epoch from now()) - ' . $seconds . ') ' : 'WHERE created > "' . $divider . '" ') . ') AS latest ' . 'GROUP BY profile_id'); $updates = array(); while ($notice->fetch()) { $updates[] = array($notice->profile_id, $notice->max_id); } return $updates; }
function lookup_nonce($consumer, $token, $nonce, $timestamp) { $n = new Nonce(); $n->consumer_key = $consumer->key; $n->ts = common_sql_date($timestamp); $n->nonce = $nonce; if ($n->find(true)) { return true; } else { $n->created = DB_DataObject_Cast::dateTime(); $n->insert(); return false; } }
function cleanupChannels() { $rc = new Realtime_channel(); $rc->selectAdd(); $rc->selectAdd('channel_key'); $rc->whereAdd('modified < "' . common_sql_date(time() - Realtime_channel::TIMEOUT) . '"'); if ($rc->find()) { $keys = $rc->fetchAll(); foreach ($keys as $key) { $rc = Realtime_channel::staticGet('channel_key', $key); if (!empty($rc)) { printfv("Deleting realtime channel '{$key}'\n"); $rc->delete(); } } } }
function importActivityStream($user, $doc) { $feed = $doc->documentElement; $entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry'); for ($i = $entries->length - 1; $i >= 0; $i--) { $entry = $entries->item($i); $activity = new Activity($entry, $feed); $object = $activity->objects[0]; if (!have_option('q', 'quiet')) { print $activity->content . "\n"; } $html = getTweetHtml($object->link); $config = array('safe' => 1, 'deny_attribute' => 'class,rel,id,style,on*'); $html = htmLawed($html, $config); $content = html_entity_decode(strip_tags($html), ENT_QUOTES, 'UTF-8'); $notice = Notice::saveNew($user->id, $content, 'importtwitter', array('uri' => $object->id, 'url' => $object->link, 'rendered' => $html, 'created' => common_sql_date($activity->time), 'replies' => array(), 'groups' => array())); } }
/** * Validates a requested lease length, sets length plus * subscription start & end dates. * * Does not save to database -- use before insert() or update(). * * @param int $length in seconds */ function setLease($length) { assert(is_int($length)); $min = 86400; $max = 86400 * 30; if ($length == 0) { // We want to garbage collect dead subscriptions! $length = $max; } elseif ($length < $min) { $length = $min; } else { if ($length > $max) { $length = $max; } } $this->lease = $length; $this->start_sub = common_sql_now(); $this->end_sub = common_sql_date(time() + $length); }
/** * Content area * * Shows the list of popular notices * * @return void */ function showContent() { $groupId = intval($this->group->id); $weightexpr = common_sql_weight('fave.modified', common_config('popular', 'dropoff')); $cutoff = sprintf("fave.modified > '%s'", common_sql_date(time() - common_config('popular', 'cutoff'))); $qry = 'SELECT notice.*, ' . $weightexpr . ' as weight ' . 'FROM notice ' . "JOIN group_inbox ON notice.id = group_inbox.notice_id " . 'JOIN fave ON notice.id = fave.notice_id ' . "WHERE {$cutoff} AND group_id = {$groupId} " . 'GROUP BY id,profile_id,uri,content,rendered,url,created,notice.modified,reply_to,is_local,source,notice.conversation ' . 'ORDER BY weight DESC'; $offset = ($this->page - 1) * NOTICES_PER_PAGE; $limit = NOTICES_PER_PAGE + 1; if (common_config('db', 'type') == 'pgsql') { $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; } else { $qry .= ' LIMIT ' . $offset . ', ' . $limit; } $notice = Memcached_DataObject::cachedQuery('Notice', $qry, 600); $nl = new NoticeList($notice, $this); $cnt = $nl->show(); if ($cnt == 0) { //$this->showEmptyList(); } $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, $this->page, 'groupfavorited', array('nickname' => $this->group->nickname)); }
function getNoticeIds($offset, $limit, $since_id, $max_id) { $weightexpr = common_sql_weight('modified', common_config('popular', 'dropoff')); $cutoff = sprintf("modified > '%s'", common_sql_date(time() - common_config('popular', 'cutoff'))); $fave = new Fave(); $fave->selectAdd(); $fave->selectAdd('notice_id'); $fave->selectAdd("{$weightexpr} as weight"); $fave->whereAdd($cutoff); $fave->orderBy('weight DESC'); $fave->groupBy('notice_id'); if (!is_null($offset)) { $fave->limit($offset, $limit); } // FIXME: $since_id, $max_id are ignored $ids = array(); if ($fave->find()) { while ($fave->fetch()) { $ids[] = $fave->notice_id; } } return $ids; }
public function onAppendUserActivityStreamObjects(UserActivityStream $uas, array &$objs) { $fave = new Fave(); $fave->user_id = $uas->getUser()->id; if (!empty($uas->after)) { $fave->whereAdd("modified > '" . common_sql_date($uas->after) . "'"); } if ($fave->find()) { while ($fave->fetch()) { $objs[] = clone $fave; } } return true; }
/** * Save PuSH subscription confirmation. * Sets approximate lease start and end times and finalizes state. * * @param int $lease_seconds provided hub.lease_seconds parameter, if given */ public function confirmSubscribe($lease_seconds) { $original = clone $this; $this->sub_state = 'active'; $this->sub_start = common_sql_date(time()); if ($lease_seconds > 0) { $this->sub_end = common_sql_date(time() + $lease_seconds); } else { $this->sub_end = null; // Backwards compatibility to StatusNet (PuSH <0.4 supported permanent subs) } $this->modified = common_sql_now(); return $this->update($original); }
function postNote($activity) { $note = $activity->objects[0]; // Use summary as fallback for content if (!empty($note->content)) { $sourceContent = $note->content; } else { if (!empty($note->summary)) { $sourceContent = $note->summary; } else { if (!empty($note->title)) { $sourceContent = $note->title; } else { // @fixme fetch from $sourceUrl? // TRANS: Client error displayed when posting a notice without content through the API. // TRANS: %d is the notice ID (number). $this->clientError(sprintf(_('No content for notice %d.'), $note->id)); } } } // Get (safe!) HTML and text versions of the content $rendered = common_purify($sourceContent); $content = common_strip_html($rendered); $shortened = $this->auth_user->shortenLinks($content); $options = array('is_local' => Notice::LOCAL_PUBLIC, 'rendered' => $rendered, 'replies' => array(), 'groups' => array(), 'tags' => array(), 'urls' => array()); // accept remote URI (not necessarily a good idea) common_debug("Note ID is {$note->id}"); if (!empty($note->id)) { $notice = Notice::getKV('uri', trim($note->id)); if (!empty($notice)) { // TRANS: Client error displayed when using another format than AtomPub. // TRANS: %s is the notice URI. $this->clientError(sprintf(_('Notice with URI "%s" already exists.'), $note->id)); } common_log(LOG_NOTICE, "Saving client-supplied notice URI '{$note->id}'"); $options['uri'] = $note->id; } // accept remote create time (also maybe not such a good idea) if (!empty($activity->time)) { common_log(LOG_NOTICE, "Saving client-supplied create time {$activity->time}"); $options['created'] = common_sql_date($activity->time); } // Check for optional attributes... if ($activity->context instanceof ActivityContext) { foreach ($activity->context->attention as $uri => $type) { try { $profile = Profile::fromUri($uri); if ($profile->isGroup()) { $options['groups'][] = $profile->id; } else { $options['replies'][] = $uri; } } catch (UnknownUriException $e) { common_log(LOG_WARNING, sprintf('AtomPub post with unknown attention URI %s', $uri)); } } // Maintain direct reply associations // @fixme what about conversation ID? if (!empty($activity->context->replyToID)) { $orig = Notice::getKV('uri', $activity->context->replyToID); if (!empty($orig)) { $options['reply_to'] = $orig->id; } } $location = $activity->context->location; if ($location) { $options['lat'] = $location->lat; $options['lon'] = $location->lon; if ($location->location_id) { $options['location_ns'] = $location->location_ns; $options['location_id'] = $location->location_id; } } } // Atom categories <-> hashtags foreach ($activity->categories as $cat) { if ($cat->term) { $term = common_canonical_tag($cat->term); if ($term) { $options['tags'][] = $term; } } } // Atom enclosures -> attachment URLs foreach ($activity->enclosures as $href) { // @fixme save these locally or....? $options['urls'][] = $href; } $saved = Notice::saveNew($this->target->id, $content, 'atompub', $options); return $saved; }
static function gc($maxlifetime) { self::logdeb("garbage collection (maxlifetime = {$maxlifetime})"); $epoch = common_sql_date(time() - $maxlifetime); $ids = array(); $session = new Session(); $session->whereAdd('modified < "' . $epoch . '"'); $session->selectAdd(); $session->selectAdd('id'); $limit = common_config('sessions', 'gc_limit'); if ($limit > 0) { // On large sites, too many sessions to expire // at once will just result in failure. $session->limit($limit); } $session->find(); while ($session->fetch()) { $ids[] = $session->id; } $session->free(); self::logdeb("Found " . count($ids) . " ids to delete."); foreach ($ids as $id) { self::logdeb("Destroying session '{$id}'."); self::destroy($id); } }
/** * Import a single bookmark * * Takes a <dt>/<dd> pair. The <dt> has a single * <a> in it with some non-standard attributes. * * A <dt><dt><dd> sequence will appear as a <dt> with * anothe <dt> as a child. We handle this case recursively. * * @param User $user User to import data as * @param DOMElement $dt <dt> element * @param DOMElement $dd <dd> element * * @return Notice imported notice */ function importBookmark($user, $dt, $dd = null) { $as = $dt->getElementsByTagName('a'); if ($as->length == 0) { // TRANS: Client exception thrown when a bookmark in an import file is incorrectly formatted. throw new ClientException(_m("No <A> tag in a <DT>.")); } $a = $as->item(0); $private = $a->getAttribute('private'); if ($private != 0) { // TRANS: Client exception thrown when a bookmark in an import file is private. throw new ClientException(_m('Skipping private bookmark.')); } if (!empty($dd)) { $description = $dd->nodeValue; } else { $description = null; } $addDate = $a->getAttribute('add_date'); $data = array('profile_id' => $user->id, 'title' => $a->nodeValue, 'description' => $description, 'url' => $a->getAttribute('href'), 'tags' => $a->getAttribute('tags'), 'created' => common_sql_date(intval($addDate))); $qm = QueueManager::get(); $qm->enqueue($data, 'dlcsbkmk'); }
static function saveNew($profile, $start_time, $end_time, $title, $location, $description, $url, $options = array()) { if (array_key_exists('uri', $options)) { $other = Happening::getKV('uri', $options['uri']); if (!empty($other)) { // TRANS: Client exception thrown when trying to create an event that already exists. throw new ClientException(_m('Event already exists.')); } } $ev = new Happening(); $ev->id = UUID::gen(); $ev->profile_id = $profile->id; $ev->start_time = common_sql_date($start_time); $ev->end_time = common_sql_date($end_time); $ev->title = $title; $ev->location = $location; $ev->description = $description; $ev->url = $url; if (array_key_exists('created', $options)) { $ev->created = $options['created']; } else { $ev->created = common_sql_now(); } if (array_key_exists('uri', $options)) { $ev->uri = $options['uri']; } else { $ev->uri = common_local_url('showevent', array('id' => $ev->id)); } $ev->insert(); // XXX: does this get truncated? // TRANS: Event description. %1$s is a title, %2$s is start time, %3$s is end time, // TRANS: %4$s is location, %5$s is a description. $content = sprintf(_m('"%1$s" %2$s - %3$s (%4$s): %5$s'), $title, common_exact_date($ev->start_time), common_exact_date($ev->end_time), $location, $description); // TRANS: Rendered microformats2 tagged event description. // TRANS: %1$s is a title, %2$s is start time, %3$s is start time, // TRANS: %4$s is end time, %5$s is end time, %6$s is location, %7$s is description. // TRANS: Class names should not be translated. $rendered = sprintf(_m('<div class="h-event">' . '<p class="p-name p-summary">%1$s</p> ' . '<time class="dt-start" datetime="%2$s">%3$s</time> - ' . '<time class="dt-end" datetime="%4$s">%5$s</time> ' . '(<span class="p-location">%6$s</span>): ' . '<div class="p-description">%7$s</div> ' . '</div>'), htmlspecialchars($title), htmlspecialchars(common_date_iso8601($ev->start_time)), htmlspecialchars(common_exact_date($ev->start_time)), htmlspecialchars(common_date_iso8601($ev->end_time)), htmlspecialchars(common_exact_date($ev->end_time)), htmlspecialchars($location), htmlspecialchars($description)); $options = array_merge(array('object_type' => Happening::OBJECT_TYPE), $options); if (!array_key_exists('uri', $options)) { $options['uri'] = $ev->uri; } if (!empty($url)) { $options['urls'] = array($url); } $saved = Notice::saveNew($profile->id, $content, array_key_exists('source', $options) ? $options['source'] : 'web', $options); return $saved; }
/** * Process an incoming post activity from this remote feed. * @param Activity $activity * @param string $method 'push' or 'salmon' * @return mixed saved Notice or false * @todo FIXME: Break up this function, it's getting nasty long */ public function processPost($activity, $method) { $notice = null; $oprofile = $this->checkAuthorship($activity); if (empty($oprofile)) { return null; } // It's not always an ActivityObject::NOTE, but... let's just say it is. $note = $activity->objects[0]; // The id URI will be used as a unique identifier for for the notice, // protecting against duplicate saves. It isn't required to be a URL; // tag: URIs for instance are found in Google Buzz feeds. $sourceUri = $note->id; $dupe = Notice::staticGet('uri', $sourceUri); if ($dupe) { common_log(LOG_INFO, "OStatus: ignoring duplicate post: {$sourceUri}"); return $dupe; } // We'll also want to save a web link to the original notice, if provided. $sourceUrl = null; if ($note->link) { $sourceUrl = $note->link; } else { if ($activity->link) { $sourceUrl = $activity->link; } else { if (preg_match('!^https?://!', $note->id)) { $sourceUrl = $note->id; } } } // Use summary as fallback for content if (!empty($note->content)) { $sourceContent = $note->content; } else { if (!empty($note->summary)) { $sourceContent = $note->summary; } else { if (!empty($note->title)) { $sourceContent = $note->title; } else { // @todo FIXME: Fetch from $sourceUrl? // TRANS: Client exception. %s is a source URI. throw new ClientException(sprintf(_m('No content for notice %s.'), $sourceUri)); } } } // Get (safe!) HTML and text versions of the content $rendered = $this->purify($sourceContent); $content = html_entity_decode(strip_tags($rendered), ENT_QUOTES, 'UTF-8'); $shortened = common_shorten_links($content); // If it's too long, try using the summary, and make the // HTML an attachment. $attachment = null; if (Notice::contentTooLong($shortened)) { $attachment = $this->saveHTMLFile($note->title, $rendered); $summary = html_entity_decode(strip_tags($note->summary), ENT_QUOTES, 'UTF-8'); if (empty($summary)) { $summary = $content; } $shortSummary = common_shorten_links($summary); if (Notice::contentTooLong($shortSummary)) { $url = common_shorten_url($sourceUrl); $shortSummary = substr($shortSummary, 0, Notice::maxContent() - (mb_strlen($url) + 2)); $content = $shortSummary . ' ' . $url; // We mark up the attachment link specially for the HTML output // so we can fold-out the full version inline. // @todo FIXME i18n: This tooltip will be saved with the site's default language // TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime // TRANS: this will usually be replaced with localised text from StatusNet core messages. $showMoreText = _m('Show more'); $attachUrl = common_local_url('attachment', array('attachment' => $attachment->id)); $rendered = common_render_text($shortSummary) . '<a href="' . htmlspecialchars($attachUrl) . '"' . ' class="attachment more"' . ' title="' . htmlspecialchars($showMoreText) . '">' . '…' . '</a>'; } } $options = array('is_local' => Notice::REMOTE, 'url' => $sourceUrl, 'uri' => $sourceUri, 'rendered' => $rendered, 'replies' => array(), 'groups' => array(), 'peopletags' => array(), 'tags' => array(), 'urls' => array()); // Check for optional attributes... if (!empty($activity->time)) { $options['created'] = common_sql_date($activity->time); } if ($activity->context) { // Any individual or group attn: targets? $replies = $activity->context->attention; $options['groups'] = $this->filterReplies($oprofile, $replies); $options['replies'] = $replies; // Maintain direct reply associations // @todo FIXME: What about conversation ID? if (!empty($activity->context->replyToID)) { $orig = Notice::staticGet('uri', $activity->context->replyToID); if (!empty($orig)) { $options['reply_to'] = $orig->id; } } $location = $activity->context->location; if ($location) { $options['lat'] = $location->lat; $options['lon'] = $location->lon; if ($location->location_id) { $options['location_ns'] = $location->location_ns; $options['location_id'] = $location->location_id; } } } if ($this->isPeopletag()) { $options['peopletags'][] = $this->localPeopletag(); } // Atom categories <-> hashtags foreach ($activity->categories as $cat) { if ($cat->term) { $term = common_canonical_tag($cat->term); if ($term) { $options['tags'][] = $term; } } } // Atom enclosures -> attachment URLs foreach ($activity->enclosures as $href) { // @todo FIXME: Save these locally or....? $options['urls'][] = $href; } try { $saved = Notice::saveNew($oprofile->profile_id, $content, 'ostatus', $options); if ($saved) { Ostatus_source::saveNew($saved, $this, $method); if (!empty($attachment)) { File_to_post::processNew($attachment->id, $saved->id); } } } catch (Exception $e) { common_log(LOG_ERR, "OStatus save of remote message {$sourceUri} failed: " . $e->getMessage()); throw $e; } common_log(LOG_INFO, "OStatus saved remote message {$sourceUri} as notice id {$saved->id}"); return $saved; }
function getGroups() { $groups = array(); $gm = new Group_member(); $gm->profile_id = $this->user->id; if (!empty($this->after)) { $gm->whereAdd("created > '" . common_sql_date($this->after) . "'"); } if ($gm->find()) { while ($gm->fetch()) { $groups[] = clone $gm; } } return $groups; }
/** * Normalize timestamp format. * @param string $ts * @return string */ private function timestamp($ts) { return common_sql_date(strtotime($ts)); }
function initFaveURI() { printfnq("Ensuring all faves have a URI..."); $fave = new Fave(); $fave->whereAdd('uri IS NULL'); if ($fave->find()) { while ($fave->fetch()) { try { $fave->decache(); $fave->query(sprintf('update fave ' . 'set uri = "%s", ' . ' modified = "%s" ' . 'where user_id = %d ' . 'and notice_id = %d', Fave::newURI($fave->user_id, $fave->notice_id, $fave->modified), common_sql_date(strtotime($fave->modified)), $fave->user_id, $fave->notice_id)); } catch (Exception $e) { common_log(LOG_ERR, "Error updated fave URI: " . $e->getMessage()); } } } printfnq("DONE.\n"); }
/** * Process an incoming post activity from this remote feed. * @param Activity $activity * @param string $method 'push' or 'salmon' * @return mixed saved Notice or false * @fixme break up this function, it's getting nasty long */ public function processPost($activity, $method) { if ($this->isGroup()) { // A group feed will contain posts from multiple authors. // @fixme validate these profiles in some way! $oprofile = self::ensureActorProfile($activity); if ($oprofile->isGroup()) { // Groups can't post notices in StatusNet. common_log(LOG_WARNING, "OStatus: skipping post with group listed as author: {$oprofile->uri} in feed from {$this->uri}"); return false; } } else { $actor = $activity->actor; if (empty($actor)) { // OK here! assume the default } else { if ($actor->id == $this->uri || $actor->link == $this->uri) { $this->updateFromActivityObject($actor); } else { throw new Exception("Got an actor '{$actor->title}' ({$actor->id}) on single-user feed for {$this->uri}"); } } $oprofile = $this; } // It's not always an ActivityObject::NOTE, but... let's just say it is. $note = $activity->objects[0]; // The id URI will be used as a unique identifier for for the notice, // protecting against duplicate saves. It isn't required to be a URL; // tag: URIs for instance are found in Google Buzz feeds. $sourceUri = $note->id; $dupe = Notice::staticGet('uri', $sourceUri); if ($dupe) { common_log(LOG_INFO, "OStatus: ignoring duplicate post: {$sourceUri}"); return false; } // We'll also want to save a web link to the original notice, if provided. $sourceUrl = null; if ($note->link) { $sourceUrl = $note->link; } else { if ($activity->link) { $sourceUrl = $activity->link; } else { if (preg_match('!^https?://!', $note->id)) { $sourceUrl = $note->id; } } } // Use summary as fallback for content if (!empty($note->content)) { $sourceContent = $note->content; } else { if (!empty($note->summary)) { $sourceContent = $note->summary; } else { if (!empty($note->title)) { $sourceContent = $note->title; } else { // @fixme fetch from $sourceUrl? throw new ClientException("No content for notice {$sourceUri}"); } } } // Get (safe!) HTML and text versions of the content $rendered = $this->purify($sourceContent); $content = html_entity_decode(strip_tags($rendered)); $shortened = common_shorten_links($content); // If it's too long, try using the summary, and make the // HTML an attachment. $attachment = null; if (Notice::contentTooLong($shortened)) { $attachment = $this->saveHTMLFile($note->title, $rendered); $summary = html_entity_decode(strip_tags($note->summary)); if (empty($summary)) { $summary = $content; } $shortSummary = common_shorten_links($summary); if (Notice::contentTooLong($shortSummary)) { $url = common_shorten_url($sourceUrl); $shortSummary = substr($shortSummary, 0, Notice::maxContent() - (mb_strlen($url) + 2)); $content = $shortSummary . ' ' . $url; // We mark up the attachment link specially for the HTML output // so we can fold-out the full version inline. $attachUrl = common_local_url('attachment', array('attachment' => $attachment->id)); $rendered = common_render_text($shortSummary) . '<a href="' . htmlspecialchars($attachUrl) . '"' . ' class="attachment more"' . ' title="' . htmlspecialchars(_m('Show more')) . '">' . '…' . '</a>'; } } $options = array('is_local' => Notice::REMOTE_OMB, 'url' => $sourceUrl, 'uri' => $sourceUri, 'rendered' => $rendered, 'replies' => array(), 'groups' => array(), 'tags' => array(), 'urls' => array()); // Check for optional attributes... if (!empty($activity->time)) { $options['created'] = common_sql_date($activity->time); } if ($activity->context) { // Any individual or group attn: targets? $replies = $activity->context->attention; $options['groups'] = $this->filterReplies($oprofile, $replies); $options['replies'] = $replies; // Maintain direct reply associations // @fixme what about conversation ID? if (!empty($activity->context->replyToID)) { $orig = Notice::staticGet('uri', $activity->context->replyToID); if (!empty($orig)) { $options['reply_to'] = $orig->id; } } $location = $activity->context->location; if ($location) { $options['lat'] = $location->lat; $options['lon'] = $location->lon; if ($location->location_id) { $options['location_ns'] = $location->location_ns; $options['location_id'] = $location->location_id; } } } // Atom categories <-> hashtags foreach ($activity->categories as $cat) { if ($cat->term) { $term = common_canonical_tag($cat->term); if ($term) { $options['tags'][] = $term; } } } // Atom enclosures -> attachment URLs foreach ($activity->enclosures as $href) { // @fixme save these locally or....? $options['urls'][] = $href; } try { $saved = Notice::saveNew($oprofile->profile_id, $content, 'ostatus', $options); if ($saved) { Ostatus_source::saveNew($saved, $this, $method); if (!empty($attachment)) { File_to_post::processNew($attachment->id, $saved->id); } } } catch (Exception $e) { common_log(LOG_ERR, "OStatus save of remote message {$sourceUri} failed: " . $e->getMessage()); throw $e; } common_log(LOG_INFO, "OStatus saved remote message {$sourceUri} as notice id {$saved->id}"); return $saved; }
/** * Save PuSH subscription confirmation. * Sets approximate lease start and end times and finalizes state. * * @param int $lease_seconds provided hub.lease_seconds parameter, if given */ public function confirmSubscribe($lease_seconds = 0) { $original = clone $this; $this->sub_state = 'active'; $this->sub_start = common_sql_date(time()); if ($lease_seconds > 0) { $this->sub_end = common_sql_date(time() + $lease_seconds); } else { $this->sub_end = null; } $this->modified = common_sql_now(); return $this->update($original); }
function common_sql_now() { return common_sql_date(time()); }
static function checkDupes($profile_id, $content) { $profile = Profile::getKV($profile_id); if (!$profile instanceof Profile) { return false; } $notice = $profile->getNotices(0, CachingNoticeStream::CACHE_WINDOW); if (!empty($notice)) { $last = 0; while ($notice->fetch()) { if (time() - strtotime($notice->created) >= common_config('site', 'dupelimit')) { return true; } else { if ($notice->content == $content) { return false; } } } } // If we get here, oldest item in cache window is not // old enough for dupe limit; do direct check against DB $notice = new Notice(); $notice->profile_id = $profile_id; $notice->content = $content; $threshold = common_sql_date(time() - common_config('site', 'dupelimit')); $notice->whereAdd(sprintf("created > '%s'", $notice->escape($threshold))); $cnt = $notice->count(); return $cnt == 0; }
function postNote($user, $author, $activity) { $note = $activity->objects[0]; $sourceUri = $note->id; $notice = Notice::getKV('uri', $sourceUri); if ($notice instanceof Notice) { common_log(LOG_INFO, "Notice {$sourceUri} already exists."); if ($this->trusted) { $profile = $notice->getProfile(); $uri = $profile->getUri(); if ($uri === $author->id) { common_log(LOG_INFO, sprintf('Updating notice author from %s to %s', $author->id, $user->getUri())); $orig = clone $notice; $notice->profile_id = $user->id; $notice->update($orig); return; } else { // TRANS: Client exception thrown when trying to import a notice by another user. // TRANS: %1$s is the source URI of the notice, %2$s is the URI of the author. throw new ClientException(sprintf(_('Already know about notice %1$s and ' . ' it has a different author %2$s.'), $sourceUri, $uri)); } } else { // TRANS: Client exception thrown when trying to overwrite the author information for a non-trusted user during import. throw new ClientException(_('Not overwriting author info for non-trusted user.')); } } // Use summary as fallback for content if (!empty($note->content)) { $sourceContent = $note->content; } else { if (!empty($note->summary)) { $sourceContent = $note->summary; } else { if (!empty($note->title)) { $sourceContent = $note->title; } else { // @fixme fetch from $sourceUrl? // TRANS: Client exception thrown when trying to import a notice without content. // TRANS: %s is the notice URI. throw new ClientException(sprintf(_('No content for notice %s.'), $sourceUri)); } } } // Get (safe!) HTML and text versions of the content $rendered = common_purify($sourceContent); $content = common_strip_html($rendered); $shortened = $user->shortenLinks($content); $options = array('is_local' => Notice::LOCAL_PUBLIC, 'uri' => $sourceUri, 'rendered' => $rendered, 'replies' => array(), 'groups' => array(), 'tags' => array(), 'urls' => array(), 'distribute' => false); // Check for optional attributes... if (!empty($activity->time)) { $options['created'] = common_sql_date($activity->time); } if ($activity->context) { // Any individual or group attn: targets? list($options['groups'], $options['replies']) = $this->filterAttention($activity->context->attention); // Maintain direct reply associations // @fixme what about conversation ID? if (!empty($activity->context->replyToID)) { $orig = Notice::getKV('uri', $activity->context->replyToID); if ($orig instanceof Notice) { $options['reply_to'] = $orig->id; } } $location = $activity->context->location; if ($location) { $options['lat'] = $location->lat; $options['lon'] = $location->lon; if ($location->location_id) { $options['location_ns'] = $location->location_ns; $options['location_id'] = $location->location_id; } } } // Atom categories <-> hashtags foreach ($activity->categories as $cat) { if ($cat->term) { $term = common_canonical_tag($cat->term); if ($term) { $options['tags'][] = $term; } } } // Atom enclosures -> attachment URLs foreach ($activity->enclosures as $href) { // @fixme save these locally or....? $options['urls'][] = $href; } common_log(LOG_INFO, "Saving notice {$options['uri']}"); $saved = Notice::saveNew($user->id, $content, 'restore', $options); return $saved; }
static function getAllChannels($action, $arg1, $arg2) { $channel = new Realtime_channel(); $channel->action = $action; if (is_null($arg1)) { $channel->whereAdd('arg1 is null'); } else { $channel->arg1 = $arg1; } if (is_null($arg2)) { $channel->whereAdd('arg2 is null'); } else { $channel->arg2 = $arg2; } $channel->whereAdd('modified > "' . common_sql_date(time() - self::TIMEOUT) . '"'); $channels = array(); if ($channel->find()) { $channels = $channel->fetchAll(); } return $channels; }
static function gc($maxlifetime) { self::logdeb("garbage collection (maxlifetime = {$maxlifetime})"); $epoch = common_sql_date(time() - $maxlifetime); $ids = array(); $session = new Session(); $session->whereAdd('modified < "' . $epoch . '"'); $session->selectAdd(); $session->selectAdd('id'); $session->find(); while ($session->fetch()) { $ids[] = $session->id; } $session->free(); self::logdeb("Found " . count($ids) . " ids to delete."); foreach ($ids as $id) { self::logdeb("Destroying session '{$id}'."); self::destroy($id); } }
function parseDate($fieldname, $datestr, $required = false) { if (empty($datestr)) { if ($required) { $msg = sprintf(_m('You must supply a date for "%s".'), $fieldname); throw new Exception($msg); } } else { $ts = strtotime($datestr); if ($ts === false) { throw new Exception(sprintf(_m('Invalid date entered for "%1$s": %2$s.'), $fieldname, $ts)); } return common_sql_date($ts); } return null; }