]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/command.php
XSS vulnerability when remote-subscribing
[quix0rs-gnu-social.git] / lib / command.php
1 <?php
2 /*
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2008, 2009, 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('GNUSOCIAL')) { exit(1); }
21
22 require_once(INSTALLDIR.'/lib/channel.php');
23
24 class Command
25 {
26     protected $scoped = null;   // The Profile of the user performing the command
27     var $user = null;
28
29     function __construct($user=null)
30     {
31         $this->scoped = $user->getProfile();
32         $this->user = $user;
33     }
34
35     /**
36      * Execute the command and send success or error results
37      * back via the given communications channel.
38      *
39      * @param Channel
40      */
41     public function execute($channel)
42     {
43         try {
44             $this->handle($channel);
45         } catch (CommandException $e) {
46             $channel->error($this->user, $e->getMessage());
47         } catch (Exception $e) {
48             common_log(LOG_ERR, "Error handling " . get_class($this) . ": " . $e->getMessage());
49             $channel->error($this->user, $e->getMessage());
50         }
51     }
52
53     /**
54      * Override this with the meat!
55      *
56      * An error to send back to the user may be sent by throwing
57      * a CommandException with a formatted message.
58      *
59      * @param Channel
60      * @throws CommandException
61      */
62     function handle($channel)
63     {
64         return false;
65     }
66
67     /**
68      * Look up a notice from an argument, by poster's name to get last post
69      * or notice_id prefixed with #.
70      *
71      * @return Notice
72      * @throws CommandException
73      */
74     function getNotice($arg)
75     {
76         $notice = null;
77         if (Event::handle('StartCommandGetNotice', array($this, $arg, &$notice))) {
78             if(substr($this->other,0,1)=='#'){
79                 // A specific notice_id #123
80
81                 $notice = Notice::getKV(substr($arg,1));
82                 if (!$notice) {
83                     // TRANS: Command exception text shown when a notice ID is requested that does not exist.
84                     throw new CommandException(_('Notice with that id does not exist.'));
85                 }
86             }
87
88             if (Validate::uri($this->other)) {
89                 // A specific notice by URI lookup
90                 $notice = Notice::getKV('uri', $arg);
91             }
92
93             if (!$notice) {
94                 // Local or remote profile name to get their last notice.
95                 // May throw an exception and report 'no such user'
96                 $recipient = $this->getProfile($arg);
97
98                 $notice = $recipient->getCurrentNotice();
99                 if (!$notice) {
100                     // TRANS: Command exception text shown when a last user notice is requested and it does not exist.
101                     throw new CommandException(_('User has no last notice.'));
102                 }
103             }
104         }
105         Event::handle('EndCommandGetNotice', array($this, $arg, &$notice));
106         if (!$notice) {
107             // TRANS: Command exception text shown when a notice ID is requested that does not exist.
108             throw new CommandException(_('Notice with that id does not exist.'));
109         }
110         return $notice;
111     }
112
113     /**
114      * Look up a local or remote profile by nickname.
115      *
116      * @return Profile
117      * @throws CommandException
118      */
119     function getProfile($arg)
120     {
121         $profile = null;
122         if (Event::handle('StartCommandGetProfile', array($this, $arg, &$profile))) {
123             $profile =
124               common_relative_profile($this->user, common_canonical_nickname($arg));
125         }
126         Event::handle('EndCommandGetProfile', array($this, $arg, &$profile));
127         if (!$profile) {
128             // TRANS: Message given requesting a profile for a non-existing user.
129             // TRANS: %s is the nickname of the user for which the profile could not be found.
130             throw new CommandException(sprintf(_('Could not find a user with nickname %s.'), $arg));
131         }
132         return $profile;
133     }
134
135     /**
136      * Get a local user by name
137      * @return User
138      * @throws CommandException
139      */
140     function getUser($arg)
141     {
142         $user = null;
143         if (Event::handle('StartCommandGetUser', array($this, $arg, &$user))) {
144             $user = User::getKV('nickname', Nickname::normalize($arg));
145         }
146         Event::handle('EndCommandGetUser', array($this, $arg, &$user));
147         if (!$user){
148             // TRANS: Message given getting a non-existing user.
149             // TRANS: %s is the nickname of the user that could not be found.
150             throw new CommandException(sprintf(_('Could not find a local user with nickname %s.'),
151                                $arg));
152         }
153         return $user;
154     }
155
156     /**
157      * Get a local or remote group by name.
158      * @return User_group
159      * @throws CommandException
160      */
161     function getGroup($arg)
162     {
163         $group = null;
164         if (Event::handle('StartCommandGetGroup', array($this, $arg, &$group))) {
165             $group = User_group::getForNickname($arg, $this->user->getProfile());
166         }
167         Event::handle('EndCommandGetGroup', array($this, $arg, &$group));
168         if (!$group) {
169             // TRANS: Command exception text shown when a group is requested that does not exist.
170             throw new CommandException(_('No such group.'));
171         }
172         return $group;
173     }
174 }
175
176 class CommandException extends Exception
177 {
178 }
179
180 class UnimplementedCommand extends Command
181 {
182     function handle($channel)
183     {
184         // TRANS: Error text shown when an unimplemented command is given.
185         $channel->error($this->user, _('Sorry, this command is not yet implemented.'));
186     }
187 }
188
189 class TrackingCommand extends UnimplementedCommand
190 {
191 }
192
193 class TrackOffCommand extends UnimplementedCommand
194 {
195 }
196
197 class TrackCommand extends UnimplementedCommand
198 {
199     var $word = null;
200     function __construct($user, $word)
201     {
202         parent::__construct($user);
203         $this->word = $word;
204     }
205 }
206
207 class UntrackCommand extends UnimplementedCommand
208 {
209     var $word = null;
210     function __construct($user, $word)
211     {
212         parent::__construct($user);
213         $this->word = $word;
214     }
215 }
216
217 class NudgeCommand extends Command
218 {
219     var $other = null;
220     function __construct($user, $other)
221     {
222         parent::__construct($user);
223         $this->other = $other;
224     }
225
226     function handle($channel)
227     {
228         $recipient = $this->getUser($this->other);
229         if ($recipient->id == $this->user->id) {
230             // TRANS: Command exception text shown when a user tries to nudge themselves.
231             throw new CommandException(_('It does not make a lot of sense to nudge yourself!'));
232         } else {
233             if ($recipient->email && $recipient->emailnotifynudge) {
234                 mail_notify_nudge($this->user, $recipient);
235             }
236             // XXX: notify by IM
237             // XXX: notify by SMS
238             // TRANS: Message given having nudged another user.
239             // TRANS: %s is the nickname of the user that was nudged.
240             $channel->output($this->user, sprintf(_('Nudge sent to %s.'),
241                            $recipient->nickname));
242         }
243     }
244 }
245
246 class InviteCommand extends UnimplementedCommand
247 {
248     var $other = null;
249     function __construct($user, $other)
250     {
251         parent::__construct($user);
252         $this->other = $other;
253     }
254 }
255
256 class StatsCommand extends Command
257 {
258     function handle($channel)
259     {
260         $profile = $this->user->getProfile();
261
262         $subs_count   = $profile->subscriptionCount();
263         $subbed_count = $profile->subscriberCount();
264         $notice_count = $profile->noticeCount();
265
266         // TRANS: User statistics text.
267         // TRANS: %1$s is the number of other user the user is subscribed to.
268         // TRANS: %2$s is the number of users that are subscribed to the user.
269         // TRANS: %3$s is the number of notices the user has sent.
270         $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n".
271                                    "Subscribers: %2\$s\n".
272                                    "Notices: %3\$s"),
273                                  $subs_count,
274                                  $subbed_count,
275                                  $notice_count));
276     }
277 }
278
279 class JoinCommand extends Command
280 {
281     var $other = null;
282
283     function __construct($user, $other)
284     {
285         parent::__construct($user);
286         $this->other = $other;
287     }
288
289     function handle($channel)
290     {
291         $group = $this->getGroup($this->other);
292         $cur   = $this->user;
293
294         if ($cur->isMember($group)) {
295             // TRANS: Error text shown a user tries to join a group they already are a member of.
296             $channel->error($cur, _('You are already a member of that group.'));
297             return;
298         }
299         if (Group_block::isBlocked($group, $cur->getProfile())) {
300             // TRANS: Error text shown when a user tries to join a group they are blocked from joining.
301           $channel->error($cur, _('You have been blocked from that group by the admin.'));
302             return;
303         }
304
305         try {
306             $cur->joinGroup($group);
307         } catch (Exception $e) {
308             // TRANS: Message given having failed to add a user to a group.
309             // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
310             $channel->error($cur, sprintf(_('Could not join user %1$s to group %2$s.'),
311                                           $cur->nickname, $group->nickname));
312             return;
313         }
314
315         // TRANS: Message given having added a user to a group.
316         // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
317         $channel->output($cur, sprintf(_('%1$s joined group %2$s.'),
318                                               $cur->nickname,
319                                               $group->nickname));
320     }
321 }
322
323 class DropCommand extends Command
324 {
325     var $other = null;
326
327     function __construct($user, $other)
328     {
329         parent::__construct($user);
330         $this->other = $other;
331     }
332
333     function handle($channel)
334     {
335         $group = $this->getGroup($this->other);
336         $cur   = $this->user;
337
338         if (!$group) {
339             // TRANS: Error text shown when trying to leave a group that does not exist.
340             $channel->error($cur, _('No such group.'));
341             return;
342         }
343
344         if (!$cur->isMember($group)) {
345             // TRANS: Error text shown when trying to leave an existing group the user is not a member of.
346             $channel->error($cur, _('You are not a member of that group.'));
347             return;
348         }
349
350         try {
351             $cur->leaveGroup($group);
352         } catch (Exception $e) {
353             // TRANS: Message given having failed to remove a user from a group.
354             // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
355             $channel->error($cur, sprintf(_('Could not remove user %1$s from group %2$s.'),
356                                           $cur->nickname, $group->nickname));
357             return;
358         }
359
360         // TRANS: Message given having removed a user from a group.
361         // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
362         $channel->output($cur, sprintf(_('%1$s left group %2$s.'),
363                                               $cur->nickname,
364                                               $group->nickname));
365     }
366 }
367
368 class TagCommand extends Command
369 {
370     var $other = null;
371     var $tags = null;
372     function __construct($user, $other, $tags)
373     {
374         parent::__construct($user);
375         $this->other = $other;
376         $this->tags = $tags;
377     }
378
379     function handle($channel)
380     {
381         $profile = $this->getProfile($this->other);
382         $cur     = $this->user->getProfile();
383
384         if (!$profile) {
385             // TRANS: Client error displayed trying to perform an action related to a non-existing profile.
386             $channel->error($cur, _('No such profile.'));
387             return;
388         }
389         if (!$cur->canTag($profile)) {
390             // TRANS: Error displayed when trying to tag a user that cannot be tagged.
391             $channel->error($cur, _('You cannot tag this user.'));
392             return;
393         }
394
395         $privs = array();
396         $tags = preg_split('/[\s,]+/', $this->tags);
397         $clean_tags = array();
398
399         foreach ($tags as $tag) {
400             $private = @$tag[0] === '.';
401             $tag = $clean_tags[] = common_canonical_tag($tag);
402
403             if (!common_valid_profile_tag($tag)) {
404                 // TRANS: Error displayed if a given tag is invalid.
405                 // TRANS: %s is the invalid tag.
406                 $channel->error($cur, sprintf(_('Invalid tag: "%s".'), $tag));
407                 return;
408             }
409             $privs[$tag] = $private;
410         }
411
412         try {
413             foreach ($clean_tags as $tag) {
414                 Profile_tag::setTag($cur->id, $profile->id, $tag, null, $privs[$tag]);
415             }
416         } catch (Exception $e) {
417             // TRANS: Error displayed if tagging a user fails.
418             // TRANS: %1$s is the tagged user, %2$s is the error message (no punctuation).
419             $channel->error($cur, sprintf(_('Error tagging %1$s: %2$s'),
420                                           $profile->nickname, $e->getMessage()));
421             return;
422         }
423
424         // TRANS: Succes message displayed if tagging a user succeeds.
425         // TRANS: %1$s is the tagged user's nickname, %2$s is a list of tags.
426         // TRANS: Plural is decided based on the number of tags added (not part of message).
427         $channel->output($cur, sprintf(_m('%1$s was tagged %2$s',
428                                           '%1$s was tagged %2$s',
429                                           count($clean_tags)),
430                                        $profile->nickname,
431                                        // TRANS: Separator for list of tags.
432                                        implode(_(', '), $clean_tags)));
433     }
434 }
435
436 class UntagCommand extends TagCommand
437 {
438     function handle($channel)
439     {
440         $profile = $this->getProfile($this->other);
441         $cur     = $this->user->getProfile();
442
443         if (!$profile) {
444             // TRANS: Client error displayed trying to perform an action related to a non-existing profile.
445             $channel->error($cur, _('No such profile.'));
446             return;
447         }
448         if (!$cur->canTag($profile)) {
449             // TRANS: Error displayed when trying to tag a user that cannot be tagged.
450             $channel->error($cur, _('You cannot tag this user.'));
451             return;
452         }
453
454         $tags = array_map('common_canonical_tag', preg_split('/[\s,]+/', $this->tags));
455
456         foreach ($tags as $tag) {
457             if (!common_valid_profile_tag($tag)) {
458                 // TRANS: Error displayed if a given tag is invalid.
459                 // TRANS: %s is the invalid tag.
460                 $channel->error($cur, sprintf(_('Invalid tag: "%s"'), $tag));
461                 return;
462             }
463         }
464
465         try {
466             foreach ($tags as $tag) {
467                 Profile_tag::unTag($cur->id, $profile->id, $tag);
468             }
469         } catch (Exception $e) {
470             // TRANS: Error displayed if untagging a user fails.
471             // TRANS: %1$s is the untagged user, %2$s is the error message (no punctuation).
472             $channel->error($cur, sprintf(_('Error untagging %1$s: %2$s'),
473                                           $profile->nickname, $e->getMessage()));
474             return;
475         }
476
477         // TRANS: Succes message displayed if untagging a user succeeds.
478         // TRANS: %1$s is the untagged user's nickname, %2$s is a list of tags.
479         // TRANS: Plural is decided based on the number of tags removed (not part of message).
480         $channel->output($cur, sprintf(_m('The following tag was removed from user %1$s: %2$s.',
481                                          'The following tags were removed from user %1$s: %2$s.',
482                                          count($tags)),
483                                        $profile->nickname,
484                                        // TRANS: Separator for list of tags.
485                                        implode(_(', '), $tags)));
486     }
487 }
488
489 class WhoisCommand extends Command
490 {
491     var $other = null;
492     function __construct($user, $other)
493     {
494         parent::__construct($user);
495         $this->other = $other;
496     }
497
498     function handle($channel)
499     {
500         $recipient = $this->getProfile($this->other);
501
502         // TRANS: Whois output.
503         // TRANS: %1$s nickname of the queried user, %2$s is their profile URL.
504         $whois = sprintf(_m('WHOIS',"%1\$s (%2\$s)"), $recipient->nickname,
505                          $recipient->profileurl);
506         if ($recipient->fullname) {
507             // TRANS: Whois output. %s is the full name of the queried user.
508             $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname);
509         }
510         if ($recipient->location) {
511             // TRANS: Whois output. %s is the location of the queried user.
512             $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location);
513         }
514         if ($recipient->homepage) {
515             // TRANS: Whois output. %s is the homepage of the queried user.
516             $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage);
517         }
518         if ($recipient->bio) {
519             // TRANS: Whois output. %s is the bio information of the queried user.
520             $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio);
521         }
522         $channel->output($this->user, $whois);
523     }
524 }
525
526 class ReplyCommand extends Command
527 {
528     var $other = null;
529     var $text = null;
530     function __construct($user, $other, $text)
531     {
532         parent::__construct($user);
533         $this->other = $other;
534         $this->text = $text;
535     }
536
537     function handle($channel)
538     {
539         $notice = $this->getNotice($this->other);
540         $recipient = $notice->getProfile();
541
542         $len = mb_strlen($this->text);
543
544         if ($len == 0) {
545             // TRANS: Command exception text shown when trying to reply to a notice without providing content for the reply.
546             $channel->error($this->user, _('No content!'));
547             return;
548         }
549
550         $this->text = $this->user->shortenLinks($this->text);
551
552         if (Notice::contentTooLong($this->text)) {
553             // XXX: i18n. Needs plural support.
554             // TRANS: Message given if content of a notice for a reply is too long. %1$d is used for plural.
555             // TRANS: %1$d is the maximum number of characters, %2$d is the number of submitted characters.
556             $channel->error($this->user, sprintf(_m('Notice too long - maximum is %1$d character, you sent %2$d.',
557                                                     'Notice too long - maximum is %1$d characters, you sent %2$d.',
558                                                     Notice::maxContent()),
559                                                  Notice::maxContent(), mb_strlen($this->text)));
560             return;
561         }
562
563         $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(),
564                                   array('reply_to' => $notice->id));
565
566         if ($notice) {
567             // TRANS: Text shown having sent a reply to a notice successfully.
568             // TRANS: %s is the nickname of the user of the notice the reply was sent to.
569             $channel->output($this->user, sprintf(_('Reply to %s sent.'), $recipient->nickname));
570         } else {
571             // TRANS: Error text shown when a reply to a notice fails with an unknown reason.
572             $channel->error($this->user, _('Error saving notice.'));
573         }
574
575     }
576 }
577
578 class GetCommand extends Command
579 {
580     var $other = null;
581
582     function __construct($user, $other)
583     {
584         parent::__construct($user);
585         $this->other = $other;
586     }
587
588     function handle($channel)
589     {
590         $target = $this->getProfile($this->other);
591
592         $notice = $target->getCurrentNotice();
593         if (!$notice) {
594             // TRANS: Error text shown when a last user notice is requested and it does not exist.
595             $channel->error($this->user, _('User has no last notice.'));
596             return;
597         }
598         $notice_content = $notice->content;
599
600         $channel->output($this->user, $target->nickname . ": " . $notice_content);
601     }
602 }
603
604 class SubCommand extends Command
605 {
606     var $other = null;
607
608     function __construct($user, $other)
609     {
610         parent::__construct($user);
611         $this->other = $other;
612     }
613
614     function handle($channel)
615     {
616
617         if (!$this->other) {
618             // TRANS: Error text shown when no username was provided when issuing a subscribe command.
619             $channel->error($this->user, _('Specify the name of the user to subscribe to.'));
620             return;
621         }
622
623         $target = $this->getProfile($this->other);
624
625         try {
626             Subscription::start($this->user->getProfile(), $target);
627             // TRANS: Text shown after having subscribed to another user successfully.
628             // TRANS: %s is the name of the user the subscription was requested for.
629             $channel->output($this->user, sprintf(_('Subscribed to %s.'), $this->other));
630         } catch (Exception $e) {
631             $channel->error($this->user, $e->getMessage());
632         }
633     }
634 }
635
636 class UnsubCommand extends Command
637 {
638     var $other = null;
639
640     function __construct($user, $other)
641     {
642         parent::__construct($user);
643         $this->other = $other;
644     }
645
646     function handle($channel)
647     {
648         if(!$this->other) {
649             // TRANS: Error text shown when no username was provided when issuing an unsubscribe command.
650             $channel->error($this->user, _('Specify the name of the user to unsubscribe from.'));
651             return;
652         }
653
654         $target = $this->getProfile($this->other);
655
656         try {
657             Subscription::cancel($this->user->getProfile(), $target);
658             // TRANS: Text shown after having unsubscribed from another user successfully.
659             // TRANS: %s is the name of the user the unsubscription was requested for.
660             $channel->output($this->user, sprintf(_('Unsubscribed from %s.'), $this->other));
661         } catch (Exception $e) {
662             $channel->error($this->user, $e->getMessage());
663         }
664     }
665 }
666
667 class OffCommand extends Command
668 {
669     var $other = null;
670
671     function __construct($user, $other=null)
672     {
673         parent::__construct($user);
674         $this->other = $other;
675     }
676     function handle($channel)
677     {
678         if ($this->other) {
679             // TRANS: Error text shown when issuing the command "off" with a setting which has not yet been implemented.
680             $channel->error($this->user, _("Command not yet implemented."));
681         } else {
682             if ($channel->off($this->user)) {
683                 // TRANS: Text shown when issuing the command "off" successfully.
684                 $channel->output($this->user, _('Notification off.'));
685             } else {
686                 // TRANS: Error text shown when the command "off" fails for an unknown reason.
687                 $channel->error($this->user, _('Can\'t turn off notification.'));
688             }
689         }
690     }
691 }
692
693 class OnCommand extends Command
694 {
695     var $other = null;
696     function __construct($user, $other=null)
697     {
698         parent::__construct($user);
699         $this->other = $other;
700     }
701
702     function handle($channel)
703     {
704         if ($this->other) {
705             // TRANS: Error text shown when issuing the command "on" with a setting which has not yet been implemented.
706             $channel->error($this->user, _("Command not yet implemented."));
707         } else {
708             if ($channel->on($this->user)) {
709                 // TRANS: Text shown when issuing the command "on" successfully.
710                 $channel->output($this->user, _('Notification on.'));
711             } else {
712                 // TRANS: Error text shown when the command "on" fails for an unknown reason.
713                 $channel->error($this->user, _('Can\'t turn on notification.'));
714             }
715         }
716     }
717 }
718
719 class LoginCommand extends Command
720 {
721     function handle($channel)
722     {
723         $disabled = common_config('logincommand','disabled');
724         $disabled = isset($disabled) && $disabled;
725         if($disabled) {
726             // TRANS: Error text shown when issuing the login command while login is disabled.
727             $channel->error($this->user, _('Login command is disabled.'));
728             return;
729         }
730
731         try {
732             $login_token = Login_token::makeNew($this->user);
733         } catch (Exception $e) {
734             $channel->error($this->user, $e->getMessage());
735         }
736
737         $channel->output($this->user,
738             // TRANS: Text shown after issuing the login command successfully.
739             // TRANS: %s is a logon link..
740             sprintf(_('This link is useable only once and is valid for only 2 minutes: %s.'),
741                     common_local_url('otp',
742                         array('user_id' => $login_token->user_id, 'token' => $login_token->token))));
743     }
744 }
745
746 class LoseCommand extends Command
747 {
748     var $other = null;
749
750     function __construct($user, $other)
751     {
752         parent::__construct($user);
753         $this->other = $other;
754     }
755
756     function execute($channel)
757     {
758         if(!$this->other) {
759             // TRANS: Error text shown when no username was provided when issuing the command.
760             $channel->error($this->user, _('Specify the name of the user to unsubscribe from.'));
761             return;
762         }
763
764         $result = Subscription::cancel($this->getProfile($this->other), $this->user->getProfile());
765
766         if ($result) {
767             // TRANS: Text shown after issuing the lose command successfully (stop another user from following the current user).
768             // TRANS: %s is the name of the user the unsubscription was requested for.
769             $channel->output($this->user, sprintf(_('Unsubscribed %s.'), $this->other));
770         } else {
771             $channel->error($this->user, $result);
772         }
773     }
774 }
775
776 class SubscriptionsCommand extends Command
777 {
778     function handle($channel)
779     {
780         $profile = $this->user->getSubscribed(0);
781         $nicknames=array();
782         while ($profile->fetch()) {
783             $nicknames[]=$profile->nickname;
784         }
785         if(count($nicknames)==0){
786             // TRANS: Text shown after requesting other users a user is subscribed to without having any subscriptions.
787             $out=_('You are not subscribed to anyone.');
788         }else{
789             // TRANS: Text shown after requesting other users a user is subscribed to.
790             // TRANS: This message supports plural forms. This message is followed by a
791             // TRANS: hard coded space and a comma separated list of subscribed users.
792             $out = _m('You are subscribed to this person:',
793                 'You are subscribed to these people:',
794                 count($nicknames));
795             $out .= ' ';
796             $out .= implode(', ',$nicknames);
797         }
798         $channel->output($this->user,$out);
799     }
800 }
801
802 class SubscribersCommand extends Command
803 {
804     function handle($channel)
805     {
806         $profile = $this->user->getSubscribers();
807         $nicknames=array();
808         while ($profile->fetch()) {
809             $nicknames[]=$profile->nickname;
810         }
811         if(count($nicknames)==0){
812             // TRANS: Text shown after requesting other users that are subscribed to a user
813             // TRANS: (followers) without having any subscribers.
814             $out=_('No one is subscribed to you.');
815         }else{
816             // TRANS: Text shown after requesting other users that are subscribed to a user (followers).
817             // TRANS: This message supports plural forms. This message is followed by a
818             // TRANS: hard coded space and a comma separated list of subscribing users.
819             $out = _m('This person is subscribed to you:',
820                 'These people are subscribed to you:',
821                 count($nicknames));
822             $out .= ' ';
823             $out .= implode(', ',$nicknames);
824         }
825         $channel->output($this->user,$out);
826     }
827 }
828
829 class GroupsCommand extends Command
830 {
831     function handle($channel)
832     {
833         $group = $this->user->getGroups();
834         $groups=array();
835         while ($group instanceof User_group && $group->fetch()) {
836             $groups[]=$group->nickname;
837         }
838         if(count($groups)==0){
839             // TRANS: Text shown after requesting groups a user is subscribed to without having
840             // TRANS: any group subscriptions.
841             $out=_('You are not a member of any groups.');
842         }else{
843             // TRANS: Text shown after requesting groups a user is subscribed to.
844             // TRANS: This message supports plural forms. This message is followed by a
845             // TRANS: hard coded space and a comma separated list of subscribed groups.
846             $out = _m('You are a member of this group:',
847                 'You are a member of these groups:',
848                 count($nicknames));
849             $out.=implode(', ',$groups);
850         }
851         $channel->output($this->user,$out);
852     }
853 }
854
855 class HelpCommand extends Command
856 {
857     function handle($channel)
858     {
859         // TRANS: Header line of help text for commands.
860         $out = array(_m('COMMANDHELP', "Commands:"));
861         $commands = array(// TRANS: Help message for IM/SMS command "on".
862                           "on" => _m('COMMANDHELP', "turn on notifications"),
863                           // TRANS: Help message for IM/SMS command "off".
864                           "off" => _m('COMMANDHELP', "turn off notifications"),
865                           // TRANS: Help message for IM/SMS command "help".
866                           "help" => _m('COMMANDHELP', "show this help"),
867                           // TRANS: Help message for IM/SMS command "follow <nickname>".
868                           "follow <nickname>" => _m('COMMANDHELP', "subscribe to user"),
869                           // TRANS: Help message for IM/SMS command "groups".
870                           "groups" => _m('COMMANDHELP', "lists the groups you have joined"),
871                           // TRANS: Help message for IM/SMS command "tag".
872                           "tag <nickname> <tags>" => _m('COMMANDHELP',"tag a user"),
873                           // TRANS: Help message for IM/SMS command "untag".
874                           "untag <nickname> <tags>" => _m('COMMANDHELP',"untag a user"),
875                           // TRANS: Help message for IM/SMS command "subscriptions".
876                           "subscriptions" => _m('COMMANDHELP', "list the people you follow"),
877                           // TRANS: Help message for IM/SMS command "subscribers".
878                           "subscribers" => _m('COMMANDHELP', "list the people that follow you"),
879                           // TRANS: Help message for IM/SMS command "leave <nickname>".
880                           "leave <nickname>" => _m('COMMANDHELP', "unsubscribe from user"),
881                           // TRANS: Help message for IM/SMS command "d <nickname> <text>".
882                           "d <nickname> <text>" => _m('COMMANDHELP', "direct message to user"),
883                           // TRANS: Help message for IM/SMS command "get <nickname>".
884                           "get <nickname>" => _m('COMMANDHELP', "get last notice from user"),
885                           // TRANS: Help message for IM/SMS command "whois <nickname>".
886                           "whois <nickname>" => _m('COMMANDHELP', "get profile info on user"),
887                           // TRANS: Help message for IM/SMS command "lose <nickname>".
888                           "lose <nickname>" => _m('COMMANDHELP', "force user to stop following you"),
889                           // TRANS: Help message for IM/SMS command "reply #<notice_id>".
890                           "reply #<notice_id>" => _m('COMMANDHELP', "reply to notice with a given id"),
891                           // TRANS: Help message for IM/SMS command "reply <nickname>".
892                           "reply <nickname>" => _m('COMMANDHELP', "reply to the last notice from user"),
893                           // TRANS: Help message for IM/SMS command "join <group>".
894                           "join <group>" => _m('COMMANDHELP', "join group"),
895                           // TRANS: Help message for IM/SMS command "login".
896                           "login" => _m('COMMANDHELP', "Get a link to login to the web interface"),
897                           // TRANS: Help message for IM/SMS command "drop <group>".
898                           "drop <group>" => _m('COMMANDHELP', "leave group"),
899                           // TRANS: Help message for IM/SMS command "stats".
900                           "stats" => _m('COMMANDHELP', "get your stats"),
901                           // TRANS: Help message for IM/SMS command "stop".
902                           "stop" => _m('COMMANDHELP', "same as 'off'"),
903                           // TRANS: Help message for IM/SMS command "quit".
904                           "quit" => _m('COMMANDHELP', "same as 'off'"),
905                           // TRANS: Help message for IM/SMS command "sub <nickname>".
906                           "sub <nickname>" => _m('COMMANDHELP', "same as 'follow'"),
907                           // TRANS: Help message for IM/SMS command "unsub <nickname>".
908                           "unsub <nickname>" => _m('COMMANDHELP', "same as 'leave'"),
909                           // TRANS: Help message for IM/SMS command "last <nickname>".
910                           "last <nickname>" => _m('COMMANDHELP', "same as 'get'"),
911                           // TRANS: Help message for IM/SMS command "on <nickname>".
912                           "on <nickname>" => _m('COMMANDHELP', "not yet implemented."),
913                           // TRANS: Help message for IM/SMS command "off <nickname>".
914                           "off <nickname>" => _m('COMMANDHELP', "not yet implemented."),
915                           // TRANS: Help message for IM/SMS command "nudge <nickname>".
916                           "nudge <nickname>" => _m('COMMANDHELP', "remind a user to update."),
917                           // TRANS: Help message for IM/SMS command "invite <phone number>".
918                           "invite <phone number>" => _m('COMMANDHELP', "not yet implemented."),
919                           // TRANS: Help message for IM/SMS command "track <word>".
920                           "track <word>" => _m('COMMANDHELP', "not yet implemented."),
921                           // TRANS: Help message for IM/SMS command "untrack <word>".
922                           "untrack <word>" => _m('COMMANDHELP', "not yet implemented."),
923                           // TRANS: Help message for IM/SMS command "track off".
924                           "track off" => _m('COMMANDHELP', "not yet implemented."),
925                           // TRANS: Help message for IM/SMS command "untrack all".
926                           "untrack all" => _m('COMMANDHELP', "not yet implemented."),
927                           // TRANS: Help message for IM/SMS command "tracks".
928                           "tracks" => _m('COMMANDHELP', "not yet implemented."),
929                           // TRANS: Help message for IM/SMS command "tracking".
930                           "tracking" => _m('COMMANDHELP', "not yet implemented."));
931
932         // Give plugins a chance to add or override...
933         Event::handle('HelpCommandMessages', array($this, &$commands));
934
935         ksort($commands);
936         foreach ($commands as $command => $help) {
937             $out[] = "$command - $help";
938         }
939         $channel->output($this->user, implode("\n", $out));
940     }
941 }