]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/twittersettings.php
Merge branch 'master' of /var/www/trunk
[quix0rs-gnu-social.git] / actions / twittersettings.php
1 <?php
2 /**
3  * Laconica, the distributed open-source microblogging tool
4  *
5  * Settings for Twitter integration
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  Settings
23  * @package   Laconica
24  * @author    Evan Prodromou <evan@controlyourself.ca>
25  * @copyright 2008-2009 Control Yourself, Inc.
26  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27  * @link      http://laconi.ca/
28  */
29
30 if (!defined('LACONICA')) {
31     exit(1);
32 }
33
34 require_once INSTALLDIR.'/lib/connectsettingsaction.php';
35
36 define('SUBSCRIPTIONS', 80);
37
38 /**
39  * Settings for Twitter integration
40  *
41  * @category Settings
42  * @package  Laconica
43  * @author   Evan Prodromou <evan@controlyourself.ca>
44  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
45  * @link     http://laconi.ca/
46  *
47  * @see      SettingsAction
48  */
49
50 class TwittersettingsAction extends ConnectSettingsAction
51 {
52     /**
53      * Title of the page
54      *
55      * @return string Title of the page
56      */
57
58     function title()
59     {
60         _('Twitter settings');
61     }
62
63     /**
64      * Instructions for use
65      *
66      * @return instructions for use
67      */
68
69     function getInstructions()
70     {
71         return _('Add your Twitter account to automatically send '.
72                  ' your notices to Twitter, ' .
73                  'and subscribe to Twitter friends already here.');
74     }
75
76     /**
77      * Content area of the page
78      *
79      * Shows a form for associating a Twitter account with this
80      * Laconica account. Also lets the user set preferences.
81      *
82      * @return void
83      */
84
85     function showContent()
86     {
87         $user = common_current_user();
88
89         $profile = $user->getProfile();
90
91         $fuser = null;
92
93         $flink = Foreign_link::getByUserID($user->id, 1); // 1 == Twitter
94
95         if ($flink) {
96             $fuser = $flink->getForeignUser();
97         }
98
99         $this->elementStart('form', array('method' => 'post',
100                                           'id' => 'form_settings_twitter',
101                                           'class' => 'form_settings',
102                                           'action' =>
103                                           common_local_url('twittersettings')));
104         $this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
105         $this->element('legend', null, _('Twitter Account'));
106         $this->hidden('token', common_session_token());
107         $this->elementStart('ul', 'form_data');
108         if ($fuser) {
109             $this->elementStart('li');
110             $this->element('span', 'twitter_user', $fuser->nickname);
111             $this->element('a', array('href' => $fuser->uri), $fuser->uri);
112             $this->element('p', 'form_guide',
113                            _('Current verified Twitter account.'));
114             $this->hidden('flink_foreign_id', $flink->foreign_id);
115             $this->submit('remove', _('Remove'));
116             $this->elementEnd('li');
117         } else {
118             $this->elementStart('li');
119             $this->input('twitter_username', _('Twitter user name'),
120                          ($this->arg('twitter_username')) ?
121                          $this->arg('twitter_username') :
122                          $profile->nickname,
123                          _('No spaces, please.')); // hey, it's what Twitter says
124             $this->elementEnd('li');
125             $this->elementStart('li');
126             $this->password('twitter_password', _('Twitter password'));
127             $this->elementend('li');
128         }
129         $this->elementEnd('ul');
130         $this->elementEnd('fieldset');
131
132         $this->elementStart('fieldset',
133                             array('id' => 'settings_twitter_preferences'));
134         $this->element('legend', null, _('Preferences'));
135
136         $this->elementStart('ul', 'form_data');
137         $this->elementStart('li');
138         $this->checkbox('noticesync',
139                         _('Automatically send my notices to Twitter.'),
140                         ($flink) ?
141                         ($flink->noticesync & FOREIGN_NOTICE_SEND) :
142                         true);
143         $this->elementEnd('li');
144         $this->elementStart('li');
145         $this->checkbox('replysync',
146                         _('Send local "@" replies to Twitter.'),
147                         ($flink) ?
148                         ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) :
149                         true);
150         $this->elementEnd('li');
151         $this->elementStart('li');
152         $this->checkbox('friendsync',
153                         _('Subscribe to my Twitter friends here.'),
154                         ($flink) ?
155                         ($flink->friendsync & FOREIGN_FRIEND_RECV) :
156                         false);
157         $this->elementEnd('li');
158         $this->elementEnd('ul');
159
160         if ($flink) {
161             $this->submit('save', _('Save'));
162         } else {
163             $this->submit('add', _('Add'));
164         }
165         $this->elementEnd('fieldset');
166
167         $this->showTwitterSubscriptions();
168
169         $this->elementEnd('form');
170     }
171
172     /**
173      * Gets some of the user's Twitter friends
174      *
175      * Gets the number of Twitter friends that are on this
176      * instance of Laconica.
177      *
178      * @return array array of User objects
179      */
180
181     function subscribedTwitterUsers()
182     {
183
184         $current_user = common_current_user();
185
186         $qry = 'SELECT user.* ' .
187           'FROM subscription ' .
188           'JOIN user ON subscription.subscribed = user.id ' .
189           'JOIN foreign_link ON foreign_link.user_id = user.id ' .
190           'WHERE subscriber = %d ' .
191           'ORDER BY user.nickname';
192
193         $user = new User();
194
195         $user->query(sprintf($qry, $current_user->id));
196
197         $users = array();
198
199         while ($user->fetch()) {
200
201             // Don't include the user's own self-subscription
202             if ($user->id != $current_user->id) {
203                 $users[] = clone($user);
204             }
205         }
206
207         return $users;
208     }
209
210     /**
211      * Show user's Twitter friends
212      *
213      * Gets the number of Twitter friends that are on this
214      * instance of Laconica, and shows their mini-avatars.
215      *
216      * @return void
217      */
218
219     function showTwitterSubscriptions()
220     {
221
222         $friends = $this->subscribedTwitterUsers();
223
224         $friends_count = count($friends);
225
226         if ($friends_count > 0) {
227
228             $this->element('h3', null, _('Twitter Friends'));
229             $this->elementStart('div', array('id' => 'subscriptions'));
230             $this->elementStart('ul', array('id' => 'subscriptions_avatars'));
231
232             for ($i = 0; $i < min($friends_count, SUBSCRIPTIONS); $i++) {
233
234                 $other = Profile::staticGet($friends[$i]->id);
235
236                 if (!$other) {
237                     common_log_db_error($subs, 'SELECT', __FILE__);
238                     continue;
239                 }
240
241                 $this->elementStart('li');
242                 $this->elementStart('a', array('title' => ($other->fullname) ?
243                                                $other->fullname :
244                                                $other->nickname,
245                                                'href' => $other->profileurl,
246                                                'rel' => 'contact',
247                                                'class' => 'subscription'));
248
249                 $avatar = $other->getAvatar(AVATAR_MINI_SIZE);
250
251                 $avatar_url = ($avatar) ?
252                   common_avatar_display_url($avatar) :
253                   common_default_avatar(AVATAR_MINI_SIZE);
254
255                 $this->element('img', array('src' => $avatar_url,
256                                             'width' => AVATAR_MINI_SIZE,
257                                             'height' => AVATAR_MINI_SIZE,
258                                             'class' => 'avatar mini',
259                                             'alt' =>  ($other->fullname) ?
260                                             $other->fullname :
261                                             $other->nickname));
262                 $this->elementEnd('a');
263                 $this->elementEnd('li');
264
265             }
266
267             $this->elementEnd('ul');
268             $this->elementEnd('div');
269
270         }
271     }
272
273     /**
274      * Handle posts to this form
275      *
276      * Based on the button that was pressed, muxes out to other functions
277      * to do the actual task requested.
278      *
279      * All sub-functions reload the form with a message -- success or failure.
280      *
281      * @return void
282      */
283
284     function handlePost()
285     {
286
287         // CSRF protection
288         $token = $this->trimmed('token');
289         if (!$token || $token != common_session_token()) {
290             $this->showForm(_('There was a problem with your session token. '.
291                               'Try again, please.'));
292             return;
293         }
294
295         if ($this->arg('save')) {
296             $this->savePreferences();
297         } else if ($this->arg('add')) {
298             $this->addTwitterAccount();
299         } else if ($this->arg('remove')) {
300             $this->removeTwitterAccount();
301         } else {
302             $this->showForm(_('Unexpected form submission.'));
303         }
304     }
305
306     /**
307      * Associate a Twitter account with the user's account
308      *
309      * Validates post input; verifies it against Twitter; and if
310      * successful stores in the database.
311      *
312      * @return void
313      */
314
315     function addTwitterAccount()
316     {
317         $screen_name = $this->trimmed('twitter_username');
318         $password    = $this->trimmed('twitter_password');
319         $noticesync  = $this->boolean('noticesync');
320         $replysync   = $this->boolean('replysync');
321         $friendsync  = $this->boolean('friendsync');
322
323         if (!Validate::string($screen_name,
324                               array('min_length' => 1,
325                                     'max_length' => 15,
326                                     'format' => VALIDATE_NUM.VALIDATE_ALPHA.'_'))) {
327             $this->showForm(_('Username must have only numbers, '.
328                               'upper- and lowercase letters, '.
329                               'and underscore (_). 15 chars max.'));
330             return;
331         }
332
333         if (!$this->verifyCredentials($screen_name, $password)) {
334             $this->showForm(_('Could not verify your Twitter credentials!'));
335             return;
336         }
337
338         $twit_user = twitter_user_info($screen_name, $password);
339
340         if (!$twit_user) {
341             $this->showForm(sprintf(_('Unable to retrieve account information '.
342                                       'For "%s" from Twitter.'),
343                                     $screen_name));
344             return;
345         }
346
347         if (!save_twitter_user($twit_user->id, $screen_name)) {
348             $this->showForm(_('Unable to save your Twitter settings!'));
349             return;
350         }
351
352         $user = common_current_user();
353
354         $flink = new Foreign_link();
355
356         $flink->user_id     = $user->id;
357         $flink->foreign_id  = $twit_user->id;
358         $flink->service     = 1; // Twitter
359         $flink->credentials = $password;
360         $flink->created     = common_sql_now();
361
362         $flink->set_flags($noticesync, $replysync, $friendsync);
363
364         $flink_id = $flink->insert();
365
366         if (!$flink_id) {
367             common_log_db_error($flink, 'INSERT', __FILE__);
368             $this->showForm(_('Unable to save your Twitter settings!'));
369             return;
370         }
371
372         if ($friendsync) {
373             save_twitter_friends($user, $twit_user->id, $screen_name, $password);
374         }
375
376         $this->showForm(_('Twitter settings saved.'), true);
377     }
378
379     /**
380      * Disassociate an existing Twitter account from this account
381      *
382      * @return void
383      */
384
385     function removeTwitterAccount()
386     {
387         $user = common_current_user();
388
389         $flink = Foreign_link::getByUserID($user->id, 1);
390
391         $flink_foreign_id = $this->arg('flink_foreign_id');
392
393         // Maybe an old tab open...?
394         if ($flink->foreign_id != $flink_foreign_id) {
395             $this->showForm(_('That is not your Twitter account.'));
396             return;
397         }
398
399         $result = $flink->delete();
400
401         if (!$result) {
402             common_log_db_error($flink, 'DELETE', __FILE__);
403             $this->serverError(_('Couldn\'t remove Twitter user.'));
404             return;
405         }
406
407         $this->showForm(_('Twitter account removed.'), true);
408     }
409
410     /**
411      * Save user's Twitter-bridging preferences
412      *
413      * @return void
414      */
415
416     function savePreferences()
417     {
418         $noticesync = $this->boolean('noticesync');
419         $friendsync = $this->boolean('friendsync');
420         $replysync  = $this->boolean('replysync');
421
422         $user = common_current_user();
423
424         $flink = Foreign_link::getByUserID($user->id, 1);
425
426         if (!$flink) {
427             common_log_db_error($flink, 'SELECT', __FILE__);
428             $this->showForm(_('Couldn\'t save Twitter preferences.'));
429             return;
430         }
431
432         $twitter_id = $flink->foreign_id;
433         $password   = $flink->credentials;
434
435         $fuser = $flink->getForeignUser();
436
437         if (!$fuser) {
438             common_log_db_error($fuser, 'SELECT', __FILE__);
439             $this->showForm(_('Couldn\'t save Twitter preferences.'));
440             return;
441         }
442
443         $screen_name = $fuser->nickname;
444
445         $original = clone($flink);
446
447         $flink->set_flags($noticesync, $replysync, $friendsync);
448
449         $result = $flink->update($original);
450
451         if ($result === false) {
452             common_log_db_error($flink, 'UPDATE', __FILE__);
453             $this->showForm(_('Couldn\'t save Twitter preferences.'));
454             return;
455         }
456
457         if ($friendsync) {
458             save_twitter_friends($user, $flink->foreign_id, $screen_name, $password);
459         }
460
461         $this->showForm(_('Twitter preferences saved.'), true);
462     }
463
464     /**
465      * Verifies a username and password against Twitter's API
466      *
467      * @param string $screen_name Twitter user name
468      * @param string $password    Twitter password
469      *
470      * @return boolean success flag
471      */
472
473     function verifyCredentials($screen_name, $password)
474     {
475         $uri = 'http://twitter.com/account/verifyCredentials.json';
476
477         $data = get_twitter_data($uri, $screen_name, $password);
478
479         if (!$data) {
480             return false;
481         }
482
483         $user = json_decode($data);
484
485         if (!$user) {
486             return false;
487         }
488
489         $twitter_id = $user->id;
490
491         if ($twitter_id) {
492             return $twitter_id;
493         }
494
495         return false;
496     }
497
498 }