static function getNoticeIDsByFile(File $file) { $f2p = new File_to_post(); $f2p->selectAdd(); $f2p->selectAdd('post_id'); $f2p->file_id = $file->getID(); $ids = array(); if (!$f2p->find()) { throw new NoResultException($f2p); } return $f2p->fetchAll('post_id'); }
function processNew($file_id, $notice_id) { static $seen = array(); if (empty($seen[$notice_id]) || !in_array($file_id, $seen[$notice_id])) { $f2p = File_to_post::pkeyGet(array('post_id' => $notice_id, 'file_id' => $file_id)); if (empty($f2p)) { $f2p = new File_to_post(); $f2p->file_id = $file_id; $f2p->post_id = $notice_id; $f2p->insert(); } if (empty($seen[$notice_id])) { $seen[$notice_id] = array($file_id); } else { $seen[$notice_id][] = $file_id; } } }
/** * Stream of notices linking to this URL * * @param integer $offset Offset to show; default is 0 * @param integer $limit Limit of notices to show * @param integer $since_id Since this notice * @param integer $max_id Before this notice * * @return array ids of notices that link to this file */ function getNoticeIds($offset, $limit, $since_id, $max_id) { $f2p = new File_to_post(); $f2p->selectAdd(); $f2p->selectAdd('post_id'); $f2p->file_id = $this->file->id; Notice::addWhereSinceId($f2p, $since_id, 'post_id', 'modified'); Notice::addWhereMaxId($f2p, $max_id, 'post_id', 'modified'); $f2p->orderBy('modified DESC, post_id DESC'); if (!is_null($offset)) { $f2p->limit($offset, $limit); } $ids = array(); if ($f2p->find()) { while ($f2p->fetch()) { $ids[] = $f2p->post_id; } } return $ids; }
static function fillAttachments(&$notices) { $ids = self::_idsOf($notices); $f2pMap = File_to_post::listGet('post_id', $ids); $fileIds = array(); foreach ($f2pMap as $noticeId => $f2ps) { foreach ($f2ps as $f2p) { $fileIds[] = $f2p->file_id; } } $fileIds = array_unique($fileIds); $fileMap = File::pivotGet('id', $fileIds); foreach ($notices as $notice) { $files = array(); $f2ps = $f2pMap[$notice->id]; foreach ($f2ps as $f2p) { $files[] = $fileMap[$f2p->file_id]; } $notice->_setAttachments($files); } }
/** * 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 processNew($given_url, $notice_id = null) { if (empty($given_url)) { return -1; } // error, no url to process $given_url = File_redirection::_canonUrl($given_url); if (empty($given_url)) { return -1; } // error, no url to process $file = File::staticGet('url', $given_url); if (empty($file)) { $file_redir = File_redirection::staticGet('url', $given_url); if (empty($file_redir)) { $redir_data = File_redirection::where($given_url); if (is_array($redir_data)) { $redir_url = $redir_data['url']; } elseif (is_string($redir_data)) { $redir_url = $redir_data; $redir_data = array(); } else { throw new ServerException("Can't process url '{$given_url}'"); } // TODO: max field length if ($redir_url === $given_url || strlen($redir_url) > 255) { $x = File::saveNew($redir_data, $given_url); $file_id = $x->id; } else { $x = File::processNew($redir_url, $notice_id); $file_id = $x->id; File_redirection::saveNew($redir_data, $file_id, $given_url); } } else { $file_id = $file_redir->file_id; } } else { $file_id = $file->id; $x = $file; } if (empty($x)) { $x = File::staticGet($file_id); if (empty($x)) { throw new ServerException("Robin thinks something is impossible."); } } if (!empty($notice_id)) { File_to_post::processNew($file_id, $notice_id); } return $x; }
/** * 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; }
function attachToNotice($notice) { File_to_post::processNew($this->fileRecord->id, $notice->id); $this->maybeAddRedir($this->fileRecord->id, common_local_url('file', array('notice' => $notice->id))); }
public function attachToNotice(Notice $notice) { File_to_post::processNew($this->fileRecord, $notice); }
function attachments() { // XXX: cache this $att = array(); $f2p = new File_to_post(); $f2p->post_id = $this->id; if ($f2p->find()) { while ($f2p->fetch()) { $f = File::staticGet($f2p->file_id); if ($f) { $att[] = clone $f; } } } return $att; }
function processNew($given_url, $notice_id) { if (empty($given_url)) { return -1; } // error, no url to process $given_url = File_redirection::_canonUrl($given_url); if (empty($given_url)) { return -1; } // error, no url to process $file = File::staticGet('url', $given_url); if (empty($file)) { $file_redir = File_redirection::staticGet('url', $given_url); if (empty($file_redir)) { common_debug("processNew() '{$given_url}' not a known redirect.\n"); $redir_data = File_redirection::where($given_url); $redir_url = $redir_data['url']; if ($redir_url === $given_url) { $x = File::saveNew($redir_data, $given_url); $file_id = $x->id; } else { $x = File::processNew($redir_url, $notice_id); $file_id = $x->id; File_redirection::saveNew($redir_data, $file_id, $given_url); } } else { $file_id = $file_redir->file_id; } } else { $file_id = $file->id; $x = $file; } if (empty($x)) { $x = File::staticGet($file_id); if (empty($x)) { die('Impossible!'); } } File_to_post::processNew($file_id, $notice_id); return $x; }
function clearFiles() { $f2p = new File_to_post(); $f2p->post_id = $this->id; if ($f2p->find()) { while ($f2p->fetch()) { $f2p->delete(); } } // FIXME: decide whether to delete File objects // ...and related (actual) files }
print "You must provide a file ID.\n"; exit(1); } $verbose = have_option('v', 'verbose'); if (!have_option('y', 'yes')) { try { $filename = $file->getFilename(); } catch (Exception $e) { $filename = '(remote file or no filename)'; } print "About to PERMANENTLY delete file ({$filename}) with id=={$file->id}) AND its related notices. Are you sure? [y/N] "; $response = fgets(STDIN); if (strtolower(trim($response)) != 'y') { print "Aborting.\n"; exit(0); } } print "Finding notices...\n"; try { $ids = File_to_post::getNoticeIDsByFile($file); $notice = Notice::multiGet('id', $ids); while ($notice->fetch()) { print "Deleting notice {$notice->id}" . ($verbose ? ": {$notice->content}\n" : "\n"); $notice->delete(); } } catch (NoResultException $e) { print "No notices found with this File attached.\n"; } print "Deleting File object together with possibly locally stored copy.\n"; $file->delete(); print "DONE.\n";
function noticeCount() { $cacheKey = sprintf('file:notice-count:%d', $this->id); $count = self::cacheGet($cacheKey); if ($count === false) { $f2p = new File_to_post(); $f2p->file_id = $this->id; $count = $f2p->count(); self::cacheSet($cacheKey, $count); } return $count; }
function showNotice($notice) { $profile = $notice->getProfile(); if (empty($profile)) { common_log(LOG_WARNING, sprintf("Notice %d has no profile", $notice->id)); return; } $this->out->elementStart('li', 'hentry notice'); $this->out->elementStart('div', 'entry-title'); $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); $this->out->elementStart('span', 'vcard author'); $this->out->elementStart('a', array('title' => $profile->fullname ? $profile->fullname : $profile->nickname, 'href' => $profile->profileurl, 'class' => 'url')); $this->out->element('img', array('src' => $avatar ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_MINI_SIZE), 'width' => AVATAR_MINI_SIZE, 'height' => AVATAR_MINI_SIZE, 'class' => 'avatar photo', 'alt' => $profile->fullname ? $profile->fullname : $profile->nickname)); $this->out->text(' '); $this->out->element('span', 'fn nickname', $profile->nickname); $this->out->elementEnd('a'); $this->out->elementEnd('span'); $this->out->elementStart('p', 'entry-content'); $this->out->raw($notice->rendered); $notice_link_cfg = common_config('site', 'notice_link'); if ('direct' === $notice_link_cfg) { $this->out->text(' ('); $this->out->element('a', array('href' => $notice->uri), 'see'); $this->out->text(')'); } elseif ('attachment' === $notice_link_cfg) { if ($count = $notice->hasAttachments()) { // link to attachment(s) pages if (1 === $count) { $f2p = File_to_post::staticGet('post_id', $notice->id); $href = common_local_url('attachment', array('attachment' => $f2p->file_id)); $att_class = 'attachment'; } else { $href = common_local_url('attachments', array('notice' => $notice->id)); $att_class = 'attachments'; } $clip = Theme::path('images/icons/clip.png', 'base'); $this->out->elementStart('a', array('class' => $att_class, 'style' => "font-style: italic;", 'href' => $href, 'title' => "# of attachments: {$count}")); $this->out->raw(" ({$count} "); $this->out->element('img', array('style' => 'display: inline', 'align' => 'top', 'width' => 20, 'height' => 20, 'src' => $clip, 'alt' => 'alt')); $this->out->text(')'); $this->out->elementEnd('a'); } else { $this->out->text(' ('); $this->out->element('a', array('href' => $notice->uri), 'see'); $this->out->text(')'); } } $this->out->elementEnd('p'); if (!empty($notice->value)) { $this->out->elementStart('p'); $this->out->text($notice->value); $this->out->elementEnd('p'); } $this->out->elementEnd('div'); $this->out->elementEnd('li'); }
/** * @fixme refactor this mess, it's gotten pretty scary. * @param bool $followRedirects */ function processNew($given_url, $notice_id = null, $followRedirects = true) { if (empty($given_url)) { return -1; } // error, no url to process $given_url = File_redirection::_canonUrl($given_url); if (empty($given_url)) { return -1; } // error, no url to process $file = File::staticGet('url', $given_url); if (empty($file)) { $file_redir = File_redirection::staticGet('url', $given_url); if (empty($file_redir)) { // @fixme for new URLs this also looks up non-redirect data // such as target content type, size, etc, which we need // for File::saveNew(); so we call it even if not following // new redirects. $redir_data = File_redirection::where($given_url); if (is_array($redir_data)) { $redir_url = $redir_data['url']; } elseif (is_string($redir_data)) { $redir_url = $redir_data; $redir_data = array(); } else { throw new ServerException("Can't process url '{$given_url}'"); } // TODO: max field length if ($redir_url === $given_url || strlen($redir_url) > 255 || !$followRedirects) { $x = File::saveNew($redir_data, $given_url); $file_id = $x->id; } else { // This seems kind of messed up... for now skipping this part // if we're already under a redirect, so we don't go into // horrible infinite loops if we've been given an unstable // redirect (where the final destination of the first request // doesn't match what we get when we ask for it again). // // Seen in the wild with clojure.org, which redirects through // wikispaces for auth and appends session data in the URL params. $x = File::processNew($redir_url, $notice_id, false); $file_id = $x->id; File_redirection::saveNew($redir_data, $file_id, $given_url); } } else { $file_id = $file_redir->file_id; } } else { $file_id = $file->id; $x = $file; } if (empty($x)) { $x = File::staticGet($file_id); if (empty($x)) { throw new ServerException("Robin thinks something is impossible."); } } if (!empty($notice_id)) { File_to_post::processNew($file_id, $notice_id); } return $x; }
public function delete($useWhere = false) { // Delete the file, if it exists locally if (!empty($this->filename) && file_exists(self::path($this->filename))) { $deleted = @unlink(self::path($this->filename)); if (!$deleted) { common_log(LOG_ERR, sprintf('Could not unlink existing file: "%s"', self::path($this->filename))); } } // Clear out related things in the database and filesystem, such as thumbnails if (Event::handle('FileDeleteRelated', array($this))) { $thumbs = new File_thumbnail(); $thumbs->file_id = $this->id; if ($thumbs->find()) { while ($thumbs->fetch()) { $thumbs->delete(); } } $f2p = new File_to_post(); $f2p->file_id = $this->id; if ($f2p->find()) { while ($f2p->fetch()) { $f2p->delete(); } } } // And finally remove the entry from the database return parent::delete($useWhere); }