]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Favorite/classes/Fave.php
more activity-like terminology in variable names
[quix0rs-gnu-social.git] / plugins / Favorite / classes / Fave.php
1 <?php
2 /**
3  * Table Definition for fave
4  */
5
6 class Fave extends Managed_DataObject
7 {
8     public $__table = 'fave';                            // table name
9     public $notice_id;                       // int(4)  primary_key not_null
10     public $user_id;                         // int(4)  primary_key not_null
11     public $uri;                             // varchar(255)
12     public $created;                         // datetime  multiple_key not_null
13     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
14
15     public static function schemaDef()
16     {
17         return array(
18             'fields' => array(
19                 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice that is the favorite'),
20                 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user who likes this notice'),
21                 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universally unique identifier, usually a tag URI'),
22                 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
23                 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
24             ),
25             'primary key' => array('notice_id', 'user_id'),
26             'unique keys' => array(
27                 'fave_uri_key' => array('uri'),
28             ),
29             'foreign keys' => array(
30                 'fave_notice_id_fkey' => array('notice', array('notice_id' => 'id')),
31                 'fave_user_id_fkey' => array('profile', array('user_id' => 'id')), // note: formerly referenced notice.id, but we can now record remote users' favorites
32             ),
33             'indexes' => array(
34                 'fave_notice_id_idx' => array('notice_id'),
35                 'fave_user_id_idx' => array('user_id', 'modified'),
36                 'fave_modified_idx' => array('modified'),
37             ),
38         );
39     }
40
41     /**
42      * Save a favorite record.
43      * @fixme post-author notification should be moved here
44      *
45      * @param Profile $actor  the local or remote Profile who favorites
46      * @param Notice  $target the notice that is favorited
47      * @return Fave record on success
48      * @throws Exception on failure
49      */
50     static function addNew(Profile $actor, Notice $target) {
51
52         $fave = null;
53
54         if (Event::handle('StartFavorNotice', array($actor, $target, &$fave))) {
55
56             $fave = new Fave();
57
58             $fave->user_id   = $actor->id;
59             $fave->notice_id = $target->id;
60             $fave->created   = common_sql_now();
61             $fave->modified  = common_sql_now();
62             $fave->uri       = self::newUri($actor,
63                                             $target,
64                                             $fave->created);
65
66             // throws exception (Fave specific until migrated into Managed_DataObject
67             $fave->insert();
68
69             self::blowCacheForProfileId($fave->user_id);
70             self::blowCacheForNoticeId($fave->notice_id);
71             self::blow('popular');
72
73             Event::handle('EndFavorNotice', array($actor, $target));
74         }
75
76         return $fave;
77     }
78
79     // exception throwing takeover!
80     public function insert()
81     {
82         if (parent::insert()===false) {
83             common_log_db_error($this, 'INSERT', __FILE__);
84             throw new ServerException(sprintf(_m('Could not store new object of type %s'), get_called_class()));
85         }
86         self::blowCacheForProfileId($this->user_id);
87         self::blowCacheForNoticeId($this->notice_id);
88         return $this;
89     }
90
91     public function delete($useWhere=false)
92     {
93         $profile = Profile::getKV('id', $this->user_id);
94         $notice  = Notice::getKV('id', $this->notice_id);
95
96         $result = null;
97
98         if (Event::handle('StartDisfavorNotice', array($profile, $notice, &$result))) {
99
100             $result = parent::delete($useWhere);
101
102             self::blowCacheForProfileId($this->user_id);
103             self::blowCacheForNoticeId($this->notice_id);
104             self::blow('popular');
105
106             if ($result) {
107                 Event::handle('EndDisfavorNotice', array($profile, $notice));
108             }
109         }
110
111         return $result;
112     }
113
114     function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false, $since_id=0, $max_id=0)
115     {
116         $stream = new FaveNoticeStream($user_id, $own);
117
118         return $stream->getNotices($offset, $limit, $since_id, $max_id);
119     }
120
121     function idStream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false, $since_id=0, $max_id=0)
122     {
123         $stream = new FaveNoticeStream($user_id, $own);
124
125         return $stream->getNoticeIds($offset, $limit, $since_id, $max_id);
126     }
127
128     function asActivity()
129     {
130         $target = $this->getTarget();
131         $actor  = $this->getActor();
132
133         $act = new Activity();
134
135         $act->verb = ActivityVerb::FAVORITE;
136
137         // FIXME: rationalize this with URL below
138
139         $act->id   = $this->getUri();
140
141         $act->time    = strtotime($this->created);
142         // TRANS: Activity title when marking a notice as favorite.
143         $act->title   = _("Favor");
144         $act->content = $target->rendered ?: $target->content;
145
146         $act->actor     = $actor->asActivityObject();
147         $act->target    = $target->asActivityObject();
148         $act->objects   = array(clone($act->target));
149
150         $url = common_local_url('AtomPubShowFavorite',
151                                           array('profile' => $actor->id,
152                                                 'notice'  => $target->id));
153
154         $act->selfLink = $url;
155         $act->editLink = $url;
156
157         return $act;
158     }
159
160     static function existsForProfile($notice, Profile $scoped)
161     {
162         $fave = self::pkeyGet(array('user_id'=>$scoped->id, 'notice_id'=>$notice->id));
163
164         return ($fave instanceof Fave);
165     }
166
167     /**
168      * Fetch a stream of favorites by profile
169      *
170      * @param integer $profileId Profile that faved
171      * @param integer $offset    Offset from last
172      * @param integer $limit     Number to get
173      *
174      * @return mixed stream of faves, use fetch() to iterate
175      *
176      * @todo Cache results
177      * @todo integrate with Fave::stream()
178      */
179
180     static function byProfile($profileId, $offset, $limit)
181     {
182         $fav = new Fave();
183
184         $fav->user_id = $profileId;
185
186         $fav->orderBy('modified DESC');
187
188         $fav->limit($offset, $limit);
189
190         $fav->find();
191
192         return $fav;
193     }
194
195     static function countByProfile(Profile $profile)
196     {
197         $c = Cache::instance();
198         if (!empty($c)) {
199             $cnt = $c->get(Cache::key('fave:count_by_profile:'.$profile->id));
200             if (is_integer($cnt)) {
201                 return $cnt;
202             }
203         }
204
205         $faves = new Fave();
206         $faves->user_id = $profile->id;
207         $cnt = (int) $faves->count('notice_id');
208
209         if (!empty($c)) {
210             $c->set(Cache::key('fave:count_by_profile:'.$profile->id), $cnt);
211         }
212
213         return $cnt;
214     }
215
216     static protected $_faves = array();
217
218     /**
219      * All faves of this notice
220      *
221      * @param Notice $notice A notice we wish to get faves for (may still be ArrayWrapper)
222      *
223      * @return array Array of Fave objects
224      */
225     static public function byNotice($notice)
226     {
227         if (!isset(self::$_faves[$notice->id])) {
228             self::fillFaves(array($notice->id));
229         }
230         return self::$_faves[$notice->id];
231     }
232
233     static public function fillFaves(array $notice_ids)
234     {
235         $faveMap = Fave::listGet('notice_id', $notice_ids);
236         self::$_faves = array_replace(self::$_faves, $faveMap);
237     }
238
239     static public function blowCacheForProfileId($profile_id)
240     {
241         $cache = Cache::instance();
242         if ($cache) {
243             // Faves don't happen chronologically, so we need to blow
244             // ;last cache, too
245             $cache->delete(Cache::key('fave:ids_by_user:'.$profile_id));
246             $cache->delete(Cache::key('fave:ids_by_user:'.$profile_id.';last'));
247             $cache->delete(Cache::key('fave:ids_by_user_own:'.$profile_id));
248             $cache->delete(Cache::key('fave:ids_by_user_own:'.$profile_id.';last'));
249             $cache->delete(Cache::key('fave:count_by_profile:'.$profile_id));
250         }
251     }
252     static public function blowCacheForNoticeId($notice_id)
253     {
254         $cache = Cache::instance();
255         if ($cache) {
256             $cache->delete(Cache::key('fave:list-ids:notice_id:'.$notice_id));
257         }
258     }
259
260     // Remember that we want the _activity_ notice here, not faves applied
261     // to the supplied Notice (as with byNotice)!
262     static public function fromStored(Notice $stored)
263     {
264         $class = get_called_class();
265         $object = new $class;
266         $object->uri = $stored->uri;
267         if (!$object->find(true)) {
268             throw new NoResultException($object);
269         }
270         return $object;
271     }
272
273     /**
274      * Retrieves the _targeted_ notice of a verb (such as the notice that was
275      * _favorited_, but not the favorite activity itself).
276      *
277      * @param Notice $stored    The activity notice.
278      *
279      * @throws NoResultException when it can't find what it's looking for.
280      */
281     static public function getTargetFromStored(Notice $stored)
282     {
283         return self::fromStored($stored)->getTarget();
284     }
285
286     static public function getObjectType()
287     {
288         return 'activity';
289     }
290
291     public function asActivityObject(Profile $scoped=null)
292     {
293         $actobj = new ActivityObject();
294         $actobj->id = $this->getUri();
295         $actobj->type = ActivityUtils::resolveUri(self::getObjectType());
296         $actobj->actor = $this->getActorObject();
297         $actobj->target = $this->getTargetObject();
298         $actobj->objects = array(clone($actobj->target));
299         $actobj->verb = ActivityVerb::FAVORITE;
300         $actobj->title = ActivityUtils::verbToTitle($actobj->verb);
301         $actobj->content = $this->getTarget()->rendered ?: $this->getTarget()->content;
302         return $actobj;
303     }
304
305     /**
306      * @param ActivityObject $actobj The _favored_ notice (which we're "in-reply-to")
307      * @param Notice         $stored The _activity_ notice, i.e. the favor itself.
308      */
309     static public function parseActivityObject(ActivityObject $actobj, Notice $stored)
310     {
311         $local = ActivityUtils::findLocalObject($actobj->getIdentifiers());
312         if (!$local instanceof Notice) {
313             // $local always returns something, but this was not what we expected. Something is wrong.
314             throw new Exception('Something other than a Notice was returned from findLocalObject');
315         }
316  
317         $actor = $stored->getProfile();
318         $object = new Fave();
319         $object->user_id = $stored->getProfile()->id;
320         $object->notice_id = $local->id;
321         $object->uri = $stored->uri;
322         $object->created = $stored->created;
323         $object->modified = $stored->modified;
324         return $object;
325     }
326
327     static public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null)
328     {
329         $target = self::getTargetFromStored($stored);
330
331         // The following logic was copied from StatusNet's Activity plugin
332         if (ActivityUtils::compareTypes($target->verb, array(ActivityVerb::POST))) {
333             // "I like the thing you posted"
334             $act->objects = $target->asActivity()->objects;
335         } else {
336             // "I like that you did whatever you did"
337             $act->target = $target->asActivityObject();
338             $act->objects = array(clone($act->target));
339         }
340         $act->context->replyToID = $target->getUri();
341         $act->context->replyToUrl = $target->getUrl();
342         $act->title = ActivityUtils::verbToTitle($act->verb);
343     }
344
345     static function saveActivityObject(ActivityObject $actobj, Notice $stored)
346     {
347         $object = self::parseActivityObject($actobj, $stored);
348         $object->insert();  // exception throwing!
349         Event::handle('EndFavorNotice', array($stored->getProfile(), $object->getTarget()));
350         return $object;
351     }
352
353     public function getAttentionArray() {
354         // not all objects can/should carry attentions, so we don't require extending this
355         // the format should be an array with URIs to mentioned profiles
356         return array();
357     }
358
359     public function getTarget()
360     {
361         // throws exception on failure
362         $target = new Notice();
363         $target->id = $this->notice_id;
364         if (!$target->find(true)) {
365             throw new NoResultException($target);
366         }
367
368         return $target;
369     }
370
371     public function getTargetObject()
372     {
373         return $this->getTarget()->asActivityObject();
374     }
375
376     protected $_stored = array();
377
378     public function getStored()
379     {
380         if (!isset($this->_stored[$this->uri])) {
381             $stored = new Notice();
382             $stored->uri = $this->uri;
383             if (!$stored->find(true)) {
384                 throw new NoResultException($stored);
385             }
386             $this->_stored[$this->uri] = $stored;
387         }
388         return $this->_stored[$this->uri];
389     }
390
391     public function getActor()
392     {
393         $profile = new Profile();
394         $profile->id = $this->user_id;
395         if (!$profile->find(true)) {
396             throw new NoResultException($profile);
397         }
398         return $profile;
399     }
400
401     public function getActorObject()
402     {
403         return $this->getActor()->asActivityObject();
404     }
405
406     public function getUri()
407     {
408         if (!empty($this->uri)) {
409             return $this->uri;
410         }
411
412         // We (should've in this case) created it ourselves, so we tag it ourselves
413         return self::newUri($this->getActor(), $this->getTarget(), $this->created);
414     }
415
416     static function newUri(Profile $actor, Managed_DataObject $target, $created=null)
417     {
418         if (is_null($created)) {
419             $created = common_sql_now();
420         }
421         return TagURI::mint(strtolower(get_called_class()).':%d:%s:%d:%s',
422                                         $actor->id,
423                                         ActivityUtils::resolveUri(self::getObjectType(), true),
424                                         $target->id,
425                                         common_date_iso8601($created));
426     }
427 }