]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/OStatus/actions/usersalmon.php
Prevent spurious refusals of legitimate notices posted to users via Salmon.
[quix0rs-gnu-social.git] / plugins / OStatus / actions / usersalmon.php
1 <?php
2 /*
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2010, StatusNet, Inc.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 if (!defined('STATUSNET')) {
21     exit(1);
22 }
23
24 /**
25  * @package OStatusPlugin
26  * @author James Walker <james@status.net>
27  */
28 class UsersalmonAction extends SalmonAction
29 {
30     function prepare($args)
31     {
32         parent::prepare($args);
33
34         $id = $this->trimmed('id');
35
36         if (!$id) {
37             // TRANS: Client error displayed trying to perform an action without providing an ID.
38             $this->clientError(_m('No ID.'));
39         }
40
41         $this->user = User::getKV('id', $id);
42
43         if (empty($this->user)) {
44             // TRANS: Client error displayed when referring to a non-existing user.
45             $this->clientError(_m('No such user.'));
46         }
47
48         $this->target = $this->user;
49
50         return true;
51     }
52
53     /**
54      * We've gotten a post event on the Salmon backchannel, probably a reply.
55      *
56      * @todo validate if we need to handle this post, then call into
57      * ostatus_profile's general incoming-post handling.
58      */
59     function handlePost()
60     {
61         common_log(LOG_INFO, "Received post of '{$this->activity->objects[0]->id}' from '{$this->activity->actor->id}'");
62
63         // @fixme: process all activity objects?
64         switch ($this->activity->objects[0]->type) {
65         case ActivityObject::ARTICLE:
66         case ActivityObject::BLOGENTRY:
67         case ActivityObject::NOTE:
68         case ActivityObject::STATUS:
69         case ActivityObject::COMMENT:
70             break;
71         default:
72             // TRANS: Client exception thrown when an undefied activity is performed.
73             throw new ClientException(_m('Cannot handle that kind of post.'));
74         }
75
76         // Notice must either be a) in reply to a notice by this user
77         // or b) in reply to a notice to the attention of this user
78         // or c) to the attention of this user
79
80         $context = $this->activity->context;
81         $notice = false;
82
83         if (!empty($context->replyToID)) {
84             $notice = Notice::getKV('uri', $context->replyToID);
85         }
86
87         if (!empty($notice) &&
88             ($notice->profile_id == $this->user->id ||
89              array_key_exists($this->user->id, $notice->getReplies())))
90         {
91             // In reply to a notice either from or mentioning this user.
92         } else if (!empty($context->attention) &&
93                    (array_key_exists($this->user->uri, $context->attention) ||
94                     array_key_exists($common_profile_url($this->user->nickname),
95                              $context->attention)))
96         {
97             // To the attention of this user.
98         } else {
99             // TRANS: Client exception.
100             throw new ClientException(_m('Not to anyone in reply to anything.'));
101         }
102
103         $existing = Notice::getKV('uri', $this->activity->objects[0]->id);
104         if ($existing instanceof Notice) {
105             common_log(LOG_ERR, "Not saving notice '".$existing->getUri()."'; already exists.");
106             return;
107         }
108
109         $this->saveNotice();
110     }
111
112     /**
113      * We've gotten a follow/subscribe notification from a remote user.
114      * Save a subscription relationship for them.
115      */
116     function handleFollow()
117     {
118         $oprofile = $this->ensureProfile();
119         if ($oprofile) {
120             common_log(LOG_INFO, sprintf('Setting up subscription from remote %s to local %s', $oprofile->getUri(), $this->user->getNickname()));
121             Subscription::start($oprofile->localProfile(),
122                                 $this->user->getProfile());
123         } else {
124             common_log(LOG_INFO, "Can't set up subscription from remote; missing profile.");
125         }
126     }
127
128     /**
129      * We've gotten an unfollow/unsubscribe notification from a remote user.
130      * Check if we have a subscription relationship for them and kill it.
131      *
132      * @fixme probably catch exceptions on fail?
133      */
134     function handleUnfollow()
135     {
136         $oprofile = $this->ensureProfile();
137         if ($oprofile) {
138             common_log(LOG_INFO, sprintf('Canceling subscription from remote %s to local %s', $oprofile->getUri(), $this->user->getNickname()));
139             Subscription::cancel($oprofile->localProfile(), $this->user->getProfile());
140         } else {
141             common_log(LOG_ERR, "Can't cancel subscription from remote, didn't find the profile");
142         }
143     }
144
145     /**
146      * Remote user likes one of our posts.
147      * Confirm the post is ours, and save a local favorite event.
148      */
149
150     function handleFavorite()
151     {
152         $notice = $this->getNotice($this->activity->objects[0]);
153         $profile = $this->ensureProfile()->localProfile();
154
155         $old = Fave::pkeyGet(array('user_id' => $profile->id,
156                                    'notice_id' => $notice->id));
157
158         if (!empty($old)) {
159             // TRANS: Client exception.
160             throw new ClientException(_m('This is already a favorite.'));
161         }
162
163         if (!Fave::addNew($profile, $notice)) {
164            // TRANS: Client exception.
165            throw new ClientException(_m('Could not save new favorite.'));
166         }
167     }
168
169     /**
170      * Remote user doesn't like one of our posts after all!
171      * Confirm the post is ours, and save a local favorite event.
172      */
173     function handleUnfavorite()
174     {
175         $notice = $this->getNotice($this->activity->objects[0]);
176         $profile = $this->ensureProfile()->localProfile();
177
178         $fave = Fave::pkeyGet(array('user_id' => $profile->id,
179                                    'notice_id' => $notice->id));
180         if (empty($fave)) {
181             // TRANS: Client exception.
182             throw new ClientException(_m('Notice was not favorited!'));
183         }
184
185         $fave->delete();
186     }
187
188     function handleTag()
189     {
190         if ($this->activity->target->type == ActivityObject::_LIST) {
191             if ($this->activity->objects[0]->type != ActivityObject::PERSON) {
192                 // TRANS: Client exception.
193                 throw new ClientException(_m('Not a person object.'));
194                 return false;
195             }
196             // this is a peopletag
197             $tagged = User::getKV('uri', $this->activity->objects[0]->id);
198
199             if (empty($tagged)) {
200                 // TRANS: Client exception.
201                 throw new ClientException(_m('Unidentified profile being listed.'));
202             }
203
204             if ($tagged->id !== $this->user->id) {
205                 // TRANS: Client exception.
206                 throw new ClientException(_m('This user is not the one being listed.'));
207             }
208
209             // save the list
210             $tagger = $this->ensureProfile();
211             $list   = Ostatus_profile::ensureActivityObjectProfile($this->activity->target);
212
213             $ptag = $list->localPeopletag();
214             $result = Profile_tag::setTag($ptag->tagger, $tagged->id, $ptag->tag);
215             if (!$result) {
216                 // TRANS: Client exception.
217                 throw new ClientException(_m('The listing could not be saved.'));
218             }
219         }
220     }
221
222     function handleUntag()
223     {
224         if ($this->activity->target->type == ActivityObject::_LIST) {
225             if ($this->activity->objects[0]->type != ActivityObject::PERSON) {
226                 // TRANS: Client exception.
227                 throw new ClientException(_m('Not a person object.'));
228                 return false;
229             }
230             // this is a peopletag
231             $tagged = User::getKV('uri', $this->activity->objects[0]->id);
232
233             if (empty($tagged)) {
234                 // TRANS: Client exception.
235                 throw new ClientException(_m('Unidentified profile being unlisted.'));
236             }
237
238             if ($tagged->id !== $this->user->id) {
239                 // TRANS: Client exception.
240                 throw new ClientException(_m('This user is not the one being unlisted.'));
241             }
242
243             // save the list
244             $tagger = $this->ensureProfile();
245             $list   = Ostatus_profile::ensureActivityObjectProfile($this->activity->target);
246
247             $ptag = $list->localPeopletag();
248             $result = Profile_tag::unTag($ptag->tagger, $tagged->id, $ptag->tag);
249
250             if (!$result) {
251                 // TRANS: Client exception.
252                 throw new ClientException(_m('The listing could not be deleted.'));
253             }
254         }
255     }
256
257     /**
258      * @param ActivityObject $object
259      * @return Notice
260      * @throws ClientException on invalid input
261      */
262     function getNotice($object)
263     {
264         if (!$object) {
265             // TRANS: Client exception.
266             throw new ClientException(_m('Cannot favorite/unfavorite without an object.'));
267         }
268
269         switch ($object->type) {
270         case ActivityObject::ARTICLE:
271         case ActivityObject::BLOGENTRY:
272         case ActivityObject::NOTE:
273         case ActivityObject::STATUS:
274         case ActivityObject::COMMENT:
275             break;
276         default:
277             // TRANS: Client exception.
278             throw new ClientException(_m('Cannot handle that kind of object for liking/faving.'));
279         }
280
281         $notice = Notice::getKV('uri', $object->id);
282
283         if (empty($notice)) {
284             // TRANS: Client exception. %s is an object ID.
285             throw new ClientException(sprintf(_m('Notice with ID %s unknown.'),$object->id));
286         }
287
288         if ($notice->profile_id != $this->user->id) {
289             // TRANS: Client exception. %1$s is a notice ID, %2$s is a user ID.
290             throw new ClientException(sprintf(_m('Notice with ID %1$s not posted by %2$s.'),$object->id,$this->user->id));
291         }
292
293         return $notice;
294     }
295 }