3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2010 StatusNet, Inc.
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 * Class for activity streams
23 * Includes objects like notices, subscriptions and from plugins.
25 * We extend atomusernoticefeed since it does some nice setup for us.
28 class UserActivityStream extends AtomUserNoticeFeed
30 public $activities = array();
33 const OUTPUT_STRING = 1;
35 public $outputMode = self::OUTPUT_STRING;
40 * @param boolean $indent
41 * @param boolean $outputMode: UserActivityStream::OUTPUT_STRING to return a string,
42 * or UserActivityStream::OUTPUT_RAW to go to raw output.
43 * Raw output mode will attempt to stream, keeping less
44 * data in memory but will leave $this->activities incomplete.
46 function __construct($user, $indent = true, $outputMode = UserActivityStream::OUTPUT_STRING, $after = null)
48 parent::__construct($user, null, $indent);
50 $this->outputMode = $outputMode;
52 if ($this->outputMode == self::OUTPUT_STRING) {
53 // String buffering? Grab all the notices now.
54 $notices = $this->getNotices();
55 } elseif ($this->outputMode == self::OUTPUT_RAW) {
56 // Raw output... need to restructure from the stringer init.
57 $this->xw = new XMLWriter();
58 $this->xw->openURI('php://output');
59 if(is_null($indent)) {
60 $indent = common_config('site', 'indent');
62 $this->xw->setIndent($indent);
64 // We'll fetch notices later.
67 throw new Exception('Invalid outputMode provided to ' . __METHOD__);
70 $this->after = $after;
72 // Assume that everything but notices is feasible
73 // to pull at once and work with in memory...
75 $subscriptions = $this->getSubscriptions();
76 $subscribers = $this->getSubscribers();
77 $groups = $this->getGroups();
79 $objs = array_merge($subscriptions, $subscribers, $groups, $notices);
81 Event::handle('AppendUserActivityStreamObjects', array($this, &$objs));
83 $subscriptions = null;
87 unset($subscriptions);
91 // Sort by create date
93 usort($objs, 'UserActivityStream::compareObject');
95 // We'll keep these around for later, and interleave them into
96 // the output stream with the user's notices.
102 * Interleave the pre-sorted objects with the user's
103 * notices, all in reverse chron order.
105 function renderEntries($format=Feed::ATOM, $handle=null)
110 foreach ($this->objs as $obj) {
114 $act = $obj->asActivity();
115 } catch (Exception $e) {
116 common_log(LOG_ERR, $e->getMessage());
122 if ($this->outputMode == self::OUTPUT_RAW && $start != $end) {
123 // In raw mode, we haven't pre-fetched notices.
124 // Grab the chunks of notices between other activities.
126 $notices = $this->getNoticesBetween($start, $end);
127 foreach ($notices as $noticeAct) {
129 $nact = $noticeAct->asActivity($this->user->getProfile());
130 if ($format == Feed::ATOM) {
131 $nact->outputTo($this, false, false);
134 fwrite($handle, ",");
136 fwrite($handle, json_encode($nact->asArray()));
139 } catch (Exception $e) {
140 common_log(LOG_ERR, $e->getMessage());
146 } catch (Exception $e) {
147 common_log(LOG_ERR, $e->getMessage());
155 if ($format == Feed::ATOM) {
156 // Only show the author sub-element if it's different from default user
157 $act->outputTo($this, false, ($act->actor->id != $this->user->getUri()));
160 fwrite($handle, ",");
162 fwrite($handle, json_encode($act->asArray()));
165 } catch (Exception $e) {
166 common_log(LOG_ERR, $e->getMessage());
175 if ($this->outputMode == self::OUTPUT_RAW) {
176 // Grab anything after the last pre-sorted activity.
178 if (!empty($this->after)) {
179 $notices = $this->getNoticesBetween($this->after, $end);
181 $notices = $this->getNoticesBetween(0, $end);
183 foreach ($notices as $noticeAct) {
185 $nact = $noticeAct->asActivity($this->user->getProfile());
186 if ($format == Feed::ATOM) {
187 $nact->outputTo($this, false, false);
190 fwrite($handle, ",");
192 fwrite($handle, json_encode($nact->asArray()));
195 } catch (Exception $e) {
196 common_log(LOG_ERR, $e->getMessage());
200 } catch (Exception $e) {
201 common_log(LOG_ERR, $e->getMessage());
205 if (empty($this->after) || strtotime($this->user->created) > $this->after) {
206 // We always add the registration activity at the end, even if
207 // they have older activities (from restored backups) in their stream.
210 $ract = $this->user->registrationActivity();
211 if ($format == Feed::ATOM) {
212 $ract->outputTo($this, false, false);
215 fwrite($handle, ",");
217 fwrite($handle, json_encode($ract->asArray()));
220 } catch (Exception $e) {
221 common_log(LOG_ERR, $e->getMessage());
227 function compareObject($a, $b)
229 $ac = strtotime((empty($a->created)) ? $a->modified : $a->created);
230 $bc = strtotime((empty($b->created)) ? $b->modified : $b->created);
232 return (($ac == $bc) ? 0 : (($ac < $bc) ? 1 : -1));
235 function getSubscriptions()
239 $sub = new Subscription();
241 $sub->subscriber = $this->user->id;
243 if (!empty($this->after)) {
244 $sub->whereAdd("created > '" . common_sql_date($this->after) . "'");
248 while ($sub->fetch()) {
249 if ($sub->subscribed != $this->user->id) {
250 $subs[] = clone($sub);
258 function getSubscribers()
262 $sub = new Subscription();
264 $sub->subscribed = $this->user->id;
266 if (!empty($this->after)) {
267 $sub->whereAdd("created > '" . common_sql_date($this->after) . "'");
271 while ($sub->fetch()) {
272 if ($sub->subscriber != $this->user->id) {
273 $subs[] = clone($sub);
283 * @param int $start unix timestamp for earliest
284 * @param int $end unix timestamp for latest
285 * @return array of Notice objects
287 function getNoticesBetween($start=0, $end=0)
291 $notice = new Notice();
293 $notice->profile_id = $this->user->id;
295 // Only stuff after $this->after
297 if (!empty($this->after)) {
299 $start = max($start, $this->after);
302 $end = max($end, $this->after);
307 $tsstart = common_sql_date($start);
308 $notice->whereAdd("created >= '$tsstart'");
311 $tsend = common_sql_date($end);
312 $notice->whereAdd("created < '$tsend'");
315 $notice->orderBy('created DESC');
317 if ($notice->find()) {
318 while ($notice->fetch()) {
319 $notices[] = clone($notice);
326 function getNotices()
328 if (!empty($this->after)) {
329 return $this->getNoticesBetween($this->after);
331 return $this->getNoticesBetween();
339 $gm = new Group_member();
341 $gm->profile_id = $this->user->id;
343 if (!empty($this->after)) {
344 $gm->whereAdd("created > '" . common_sql_date($this->after) . "'");
348 while ($gm->fetch()) {
349 $groups[] = clone($gm);
356 function createdAfter($item) {
357 $created = strtotime((empty($item->created)) ? $item->modified : $item->created);
358 return ($created >= $this->after);
361 function writeJSON($handle)
363 require_once INSTALLDIR.'/lib/activitystreamjsondocument.php';
364 fwrite($handle, '{"items": [');
365 $this->renderEntries(Feed::JSON, $handle);
366 fwrite($handle, ']}');