]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Blacklist/BlacklistPlugin.php
Merge branch '0.9.x' into twitstream
[quix0rs-gnu-social.git] / plugins / Blacklist / BlacklistPlugin.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * Plugin to prevent use of nicknames or URLs on a blacklist
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  Action
23  * @package   StatusNet
24  * @author    Evan Prodromou <evan@status.net>
25  * @copyright 2010 StatusNet Inc.
26  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27  * @link      http://status.net/
28  */
29
30 if (!defined('STATUSNET')) {
31     exit(1);
32 }
33
34 /**
35  * Plugin to prevent use of nicknames or URLs on a blacklist
36  *
37  * @category Plugin
38  * @package  StatusNet
39  * @author   Evan Prodromou <evan@status.net>
40  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
41  * @link     http://status.net/
42  */
43 class BlacklistPlugin extends Plugin
44 {
45     const VERSION = STATUSNET_VERSION;
46
47     public $nicknames = array();
48     public $urls      = array();
49     public $canAdmin  = true;
50
51     function _getNicknamePatterns()
52     {
53         $confNicknames = $this->_configArray('blacklist', 'nicknames');
54
55         $dbNicknames = Nickname_blacklist::getPatterns();
56
57         return array_merge($this->nicknames,
58                            $confNicknames,
59                            $dbNicknames);
60     }
61
62     function _getUrlPatterns()
63     {
64         $confURLs = $this->_configArray('blacklist', 'urls');
65
66         $dbURLs = Homepage_blacklist::getPatterns();
67
68         return array_merge($this->urls,
69                            $confURLs,
70                            $dbURLs);
71     }
72
73     /**
74      * Database schema setup
75      *
76      * @return boolean hook value
77      */
78     function onCheckSchema()
79     {
80         $schema = Schema::get();
81
82         // For storing blacklist patterns for nicknames
83         $schema->ensureTable('nickname_blacklist',
84                              array(new ColumnDef('pattern',
85                                                  'varchar',
86                                                  255,
87                                                  false,
88                                                  'PRI'),
89                                    new ColumnDef('created',
90                                                  'datetime',
91                                                  null,
92                                                  false)));
93
94         $schema->ensureTable('homepage_blacklist',
95                              array(new ColumnDef('pattern',
96                                                  'varchar',
97                                                  255,
98                                                  false,
99                                                  'PRI'),
100                                    new ColumnDef('created',
101                                                  'datetime',
102                                                  null,
103                                                  false)));
104
105         return true;
106     }
107
108     /**
109      * Retrieve an array from configuration
110      *
111      * Carefully checks a section.
112      *
113      * @param string $section Configuration section
114      * @param string $setting Configuration setting
115      *
116      * @return array configuration values
117      */
118     function _configArray($section, $setting)
119     {
120         $config = common_config($section, $setting);
121
122         if (empty($config)) {
123             return array();
124         } else if (is_array($config)) {
125             return $config;
126         } else if (is_string($config)) {
127             return explode("\r\n", $config);
128         } else {
129             throw new Exception("Unknown data type for config $section + $setting");
130         }
131     }
132
133     /**
134      * Hook registration to prevent blacklisted homepages or nicknames
135      *
136      * Throws an exception if there's a blacklisted homepage or nickname.
137      *
138      * @param Action $action Action being called (usually register)
139      *
140      * @return boolean hook value
141      */
142     function onStartRegistrationTry($action)
143     {
144         $homepage = strtolower($action->trimmed('homepage'));
145
146         if (!empty($homepage)) {
147             if (!$this->_checkUrl($homepage)) {
148                 // TRANS: Validation failure for URL. %s is the URL.
149                 $msg = sprintf(_m("You may not register with homepage \"%s\"."),
150                                $homepage);
151                 throw new ClientException($msg);
152             }
153         }
154
155         $nickname = strtolower($action->trimmed('nickname'));
156
157         if (!empty($nickname)) {
158             if (!$this->_checkNickname($nickname)) {
159                 // TRANS: Validation failure for nickname. %s is the nickname.
160                 $msg = sprintf(_m("You may not register with nickname \"%s\"."),
161                                $nickname);
162                 throw new ClientException($msg);
163             }
164         }
165
166         return true;
167     }
168
169     /**
170      * Hook profile update to prevent blacklisted homepages or nicknames
171      *
172      * Throws an exception if there's a blacklisted homepage or nickname.
173      *
174      * @param Action $action Action being called (usually register)
175      *
176      * @return boolean hook value
177      */
178     function onStartProfileSaveForm($action)
179     {
180         $homepage = strtolower($action->trimmed('homepage'));
181
182         if (!empty($homepage)) {
183             if (!$this->_checkUrl($homepage)) {
184                 // TRANS: Validation failure for URL. %s is the URL.
185                 $msg = sprintf(_m("You may not use homepage \"%s\"."),
186                                $homepage);
187                 throw new ClientException($msg);
188             }
189         }
190
191         $nickname = strtolower($action->trimmed('nickname'));
192
193         if (!empty($nickname)) {
194             if (!$this->_checkNickname($nickname)) {
195                 // TRANS: Validation failure for nickname. %s is the nickname.
196                 $msg = sprintf(_m("You may not use nickname \"%s\"."),
197                                $nickname);
198                 throw new ClientException($msg);
199             }
200         }
201
202         return true;
203     }
204
205     /**
206      * Hook notice save to prevent blacklisted urls
207      *
208      * Throws an exception if there's a blacklisted url in the content.
209      *
210      * @param Notice &$notice Notice being saved
211      *
212      * @return boolean hook value
213      */
214     function onStartNoticeSave(&$notice)
215     {
216         common_replace_urls_callback($notice->content,
217                                      array($this, 'checkNoticeUrl'));
218         return true;
219     }
220
221     /**
222      * Helper callback for notice save
223      *
224      * Throws an exception if there's a blacklisted url in the content.
225      *
226      * @param string $url URL in the notice content
227      *
228      * @return boolean hook value
229      */
230     function checkNoticeUrl($url)
231     {
232         // It comes in special'd, so we unspecial it
233         // before comparing against patterns
234
235         $url = htmlspecialchars_decode($url);
236
237         if (!$this->_checkUrl($url)) {
238             // TRANS: Validation failure for URL. %s is the URL.
239             $msg = sprintf(_m("You may not use URL \"%s\" in notices."),
240                            $url);
241             throw new ClientException($msg);
242         }
243
244         return $url;
245     }
246
247     /**
248      * Helper for checking URLs
249      *
250      * Checks an URL against our patterns for a match.
251      *
252      * @param string $url URL to check
253      *
254      * @return boolean true means it's OK, false means it's bad
255      */
256     private function _checkUrl($url)
257     {
258         $patterns = $this->_getUrlPatterns();
259
260         foreach ($patterns as $pattern) {
261             if ($pattern != '' && preg_match("/$pattern/", $url)) {
262                 return false;
263             }
264         }
265
266         return true;
267     }
268
269     /**
270      * Helper for checking nicknames
271      *
272      * Checks a nickname against our patterns for a match.
273      *
274      * @param string $nickname nickname to check
275      *
276      * @return boolean true means it's OK, false means it's bad
277      */
278     private function _checkNickname($nickname)
279     {
280         $patterns = $this->_getNicknamePatterns();
281
282         foreach ($patterns as $pattern) {
283             if ($pattern != '' && preg_match("/$pattern/", $nickname)) {
284                 return false;
285             }
286         }
287
288         return true;
289     }
290
291     /**
292      * Add our actions to the URL router
293      *
294      * @param Net_URL_Mapper $m URL mapper for this hit
295      *
296      * @return boolean hook return
297      */
298     function onRouterInitialized($m)
299     {
300         $m->connect('admin/blacklist', array('action' => 'blacklistadminpanel'));
301         return true;
302     }
303
304     /**
305      * Auto-load our classes if called
306      *
307      * @param string $cls Class to load
308      *
309      * @return boolean hook return
310      */
311     function onAutoload($cls)
312     {
313         switch (strtolower($cls))
314         {
315         case 'nickname_blacklist':
316         case 'homepage_blacklist':
317             include_once INSTALLDIR.'/plugins/Blacklist/'.ucfirst($cls).'.php';
318             return false;
319         case 'blacklistadminpanelaction':
320             $base = strtolower(mb_substr($cls, 0, -6));
321             include_once INSTALLDIR.'/plugins/Blacklist/'.$base.'.php';
322             return false;
323         default:
324             return true;
325         }
326     }
327
328     /**
329      * Plugin version data
330      *
331      * @param array &$versions array of version blocks
332      *
333      * @return boolean hook value
334      */
335     function onPluginVersion(&$versions)
336     {
337         $versions[] = array('name' => 'Blacklist',
338                             'version' => self::VERSION,
339                             'author' => 'Evan Prodromou',
340                             'homepage' =>
341                             'http://status.net/wiki/Plugin:Blacklist',
342                             'description' =>
343                             _m('Keeps a blacklist of forbidden nickname '.
344                                'and URL patterns.'));
345         return true;
346     }
347
348     /**
349      * Determines if our admin panel can be shown
350      *
351      * @param string  $name  name of the admin panel
352      * @param boolean &$isOK result
353      *
354      * @return boolean hook value
355      */
356     function onAdminPanelCheck($name, &$isOK)
357     {
358         if ($name == 'blacklist') {
359             $isOK = $this->canAdmin;
360             return false;
361         }
362
363         return true;
364     }
365
366     /**
367      * Add our tab to the admin panel
368      *
369      * @param Widget $nav Admin panel nav
370      *
371      * @return boolean hook value
372      */
373     function onEndAdminPanelNav($nav)
374     {
375         if (AdminPanelAction::canAdmin('blacklist')) {
376
377             $action_name = $nav->action->trimmed('action');
378
379             $nav->out->menuItem(common_local_url('blacklistadminpanel'),
380                                 // TRANS: Menu item in admin panel.
381                                 _m('MENU','Blacklist'),
382                                 // TRANS: Tooltip for menu item in admin panel.
383                                 _m('TOOLTIP','Blacklist configuration'),
384                                 $action_name == 'blacklistadminpanel',
385                                 'nav_blacklist_admin_panel');
386         }
387
388         return true;
389     }
390
391     function onEndDeleteUserForm($action, $user)
392     {
393         $cur = common_current_user();
394
395         if (empty($cur) || !$cur->hasRight(Right::CONFIGURESITE)) {
396             return;
397         }
398
399         $profile = $user->getProfile();
400
401         if (empty($profile)) {
402             return;
403         }
404
405         $action->elementStart('ul', 'form_data');
406         $action->elementStart('li');
407         $this->checkboxAndText($action,
408                                'blacklistnickname',
409                                // TRANS: Checkbox with text label in the delete user form.
410                                _m('Add this nickname pattern to blacklist'),
411                                'blacklistnicknamepattern',
412                                $this->patternizeNickname($user->nickname));
413         $action->elementEnd('li');
414
415         if (!empty($profile->homepage)) {
416             $action->elementStart('li');
417             $this->checkboxAndText($action,
418                                    'blacklisthomepage',
419                                    // TRANS: Checkbox with text label in the delete user form.
420                                    _m('Add this homepage pattern to blacklist'),
421                                    'blacklisthomepagepattern',
422                                    $this->patternizeHomepage($profile->homepage));
423             $action->elementEnd('li');
424         }
425
426         $action->elementEnd('ul');
427     }
428
429     function onEndDeleteUser($action, $user)
430     {
431         if ($action->boolean('blacklisthomepage')) {
432             $pattern = $action->trimmed('blacklisthomepagepattern');
433             Homepage_blacklist::ensurePattern($pattern);
434         }
435
436         if ($action->boolean('blacklistnickname')) {
437             $pattern = $action->trimmed('blacklistnicknamepattern');
438             Nickname_blacklist::ensurePattern($pattern);
439         }
440
441         return true;
442     }
443
444     function checkboxAndText($action, $checkID, $label, $textID, $value)
445     {
446         $action->element('input', array('name' => $checkID,
447                                         'type' => 'checkbox',
448                                         'class' => 'checkbox',
449                                         'id' => $checkID));
450
451         $action->text(' ');
452
453         $action->element('label', array('class' => 'checkbox',
454                                         'for' => $checkID),
455                          $label);
456
457         $action->text(' ');
458
459         $action->element('input', array('name' => $textID,
460                                         'type' => 'text',
461                                         'id' => $textID,
462                                         'value' => $value));
463     }
464
465     function patternizeNickname($nickname)
466     {
467         return $nickname;
468     }
469
470     function patternizeHomepage($homepage)
471     {
472         $hostname = parse_url($homepage, PHP_URL_HOST);
473         return $hostname;
474     }
475
476     function onStartHandleFeedEntry($activity)
477     {
478         return $this->_checkActivity($activity);
479     }
480
481     function onStartHandleSalmon($activity)
482     {
483         return $this->_checkActivity($activity);
484     }
485
486     function _checkActivity($activity)
487     {
488         $actor = $activity->actor;
489
490         if (empty($actor)) {
491             return true;
492         }
493
494         $homepage = strtolower($actor->link);
495
496         if (!empty($homepage)) {
497             if (!$this->_checkUrl($homepage)) {
498                 // TRANS: Exception thrown trying to post a notice while having set a blocked homepage URL. %s is the blocked URL.
499                 $msg = sprintf(_m("Users from \"%s\" blocked."),
500                                $homepage);
501                 throw new ClientException($msg);
502             }
503         }
504
505         $nickname = strtolower($actor->poco->preferredUsername);
506
507         if (!empty($nickname)) {
508             if (!$this->_checkNickname($nickname)) {
509                 // TRANS: Exception thrown trying to post a notice while having a blocked nickname. %s is the blocked nickname.
510                 $msg = sprintf(_m("Posts from nickname \"%s\" disallowed."),
511                                $nickname);
512                 throw new ClientException($msg);
513             }
514         }
515
516         return true;
517     }
518
519     /**
520      * Check URLs and homepages for blacklisted users.
521      */
522     function onStartSubscribe($subscriber, $other)
523     {
524         foreach (array($other->profileurl, $other->homepage) as $url) {
525
526             if (empty($url)) {
527                 continue;
528             }
529
530             $url = strtolower($url);
531
532             if (!$this->_checkUrl($url)) {
533                 // TRANS: Client exception thrown trying to subscribe to a person with a blocked homepage or site URL. %s is the blocked URL.
534                 $msg = sprintf(_m("Users from \"%s\" blocked."),
535                                $url);
536                 throw new ClientException($msg);
537             }
538         }
539
540         $nickname = $other->nickname;
541
542         if (!empty($nickname)) {
543             if (!$this->_checkNickname($nickname)) {
544                 // TRANS: Client exception thrown trying to subscribe to a person with a blocked nickname. %s is the blocked nickname.
545                 $msg = sprintf(_m("Can't subscribe to nickname \"%s\"."),
546                                $nickname);
547                 throw new ClientException($msg);
548             }
549         }
550
551         return true;
552     }
553 }