]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Blacklist/BlacklistPlugin.php
Merge branch 'master' into 1.0.x
[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             // TRANS: Exception thrown if the Blacklist plugin configuration is incorrect.
130             // TRANS: %1$s is a configuration section, %2$s is a configuration setting.
131             throw new Exception(sprintf(_m('Unknown data type for config %1$s + %2$s.'),$section, $setting));
132         }
133     }
134
135     /**
136      * Hook registration to prevent blacklisted homepages or nicknames
137      *
138      * Throws an exception if there's a blacklisted homepage or nickname.
139      *
140      * @param Action $action Action being called (usually register)
141      *
142      * @return boolean hook value
143      */
144     function onStartRegisterUser(&$user, &$profile)
145     {
146         $homepage = strtolower($profile->homepage);
147
148         if (!empty($homepage)) {
149             if (!$this->_checkUrl($homepage)) {
150                 // TRANS: Validation failure for URL. %s is the URL.
151                 $msg = sprintf(_m("You may not register with homepage \"%s\"."),
152                                $homepage);
153                 throw new ClientException($msg);
154             }
155         }
156
157         $nickname = strtolower($profile->nickname);
158
159         if (!empty($nickname)) {
160             if (!$this->_checkNickname($nickname)) {
161                 // TRANS: Validation failure for nickname. %s is the nickname.
162                 $msg = sprintf(_m("You may not register with nickname \"%s\"."),
163                                $nickname);
164                 throw new ClientException($msg);
165             }
166         }
167
168         return true;
169     }
170
171     /**
172      * Hook profile update to prevent blacklisted homepages or nicknames
173      *
174      * Throws an exception if there's a blacklisted homepage or nickname.
175      *
176      * @param Action $action Action being called (usually register)
177      *
178      * @return boolean hook value
179      */
180     function onStartProfileSaveForm($action)
181     {
182         $homepage = strtolower($action->trimmed('homepage'));
183
184         if (!empty($homepage)) {
185             if (!$this->_checkUrl($homepage)) {
186                 // TRANS: Validation failure for URL. %s is the URL.
187                 $msg = sprintf(_m("You may not use homepage \"%s\"."),
188                                $homepage);
189                 throw new ClientException($msg);
190             }
191         }
192
193         $nickname = strtolower($action->trimmed('nickname'));
194
195         if (!empty($nickname)) {
196             if (!$this->_checkNickname($nickname)) {
197                 // TRANS: Validation failure for nickname. %s is the nickname.
198                 $msg = sprintf(_m("You may not use nickname \"%s\"."),
199                                $nickname);
200                 throw new ClientException($msg);
201             }
202         }
203
204         return true;
205     }
206
207     /**
208      * Hook notice save to prevent blacklisted urls
209      *
210      * Throws an exception if there's a blacklisted url in the content.
211      *
212      * @param Notice &$notice Notice being saved
213      *
214      * @return boolean hook value
215      */
216     function onStartNoticeSave(&$notice)
217     {
218         common_replace_urls_callback($notice->content,
219                                      array($this, 'checkNoticeUrl'));
220         return true;
221     }
222
223     /**
224      * Helper callback for notice save
225      *
226      * Throws an exception if there's a blacklisted url in the content.
227      *
228      * @param string $url URL in the notice content
229      *
230      * @return boolean hook value
231      */
232     function checkNoticeUrl($url)
233     {
234         // It comes in special'd, so we unspecial it
235         // before comparing against patterns
236
237         $url = htmlspecialchars_decode($url);
238
239         if (!$this->_checkUrl($url)) {
240             // TRANS: Validation failure for URL. %s is the URL.
241             $msg = sprintf(_m("You may not use URL \"%s\" in notices."),
242                            $url);
243             throw new ClientException($msg);
244         }
245
246         return $url;
247     }
248
249     /**
250      * Helper for checking URLs
251      *
252      * Checks an URL against our patterns for a match.
253      *
254      * @param string $url URL to check
255      *
256      * @return boolean true means it's OK, false means it's bad
257      */
258     private function _checkUrl($url)
259     {
260         $patterns = $this->_getUrlPatterns();
261
262         foreach ($patterns as $pattern) {
263             if ($pattern != '' && preg_match("/$pattern/", $url)) {
264                 return false;
265             }
266         }
267
268         return true;
269     }
270
271     /**
272      * Helper for checking nicknames
273      *
274      * Checks a nickname against our patterns for a match.
275      *
276      * @param string $nickname nickname to check
277      *
278      * @return boolean true means it's OK, false means it's bad
279      */
280     private function _checkNickname($nickname)
281     {
282         $patterns = $this->_getNicknamePatterns();
283
284         foreach ($patterns as $pattern) {
285             if ($pattern != '' && preg_match("/$pattern/", $nickname)) {
286                 return false;
287             }
288         }
289
290         return true;
291     }
292
293     /**
294      * Add our actions to the URL router
295      *
296      * @param Net_URL_Mapper $m URL mapper for this hit
297      *
298      * @return boolean hook return
299      */
300     function onRouterInitialized($m)
301     {
302         $m->connect('panel/blacklist', array('action' => 'blacklistadminpanel'));
303         return true;
304     }
305
306     /**
307      * Auto-load our classes if called
308      *
309      * @param string $cls Class to load
310      *
311      * @return boolean hook return
312      */
313     function onAutoload($cls)
314     {
315         switch (strtolower($cls))
316         {
317         case 'nickname_blacklist':
318         case 'homepage_blacklist':
319             include_once INSTALLDIR.'/plugins/Blacklist/'.ucfirst($cls).'.php';
320             return false;
321         case 'blacklistadminpanelaction':
322             $base = strtolower(mb_substr($cls, 0, -6));
323             include_once INSTALLDIR.'/plugins/Blacklist/'.$base.'.php';
324             return false;
325         default:
326             return true;
327         }
328     }
329
330     /**
331      * Plugin version data
332      *
333      * @param array &$versions array of version blocks
334      *
335      * @return boolean hook value
336      */
337     function onPluginVersion(&$versions)
338     {
339         $versions[] = array('name' => 'Blacklist',
340                             'version' => self::VERSION,
341                             'author' => 'Evan Prodromou',
342                             'homepage' =>
343                             'http://status.net/wiki/Plugin:Blacklist',
344                             'description' =>
345                             // TRANS: Plugin description.
346                             _m('Keeps a blacklist of forbidden nickname '.
347                                'and URL patterns.'));
348         return true;
349     }
350
351     /**
352      * Determines if our admin panel can be shown
353      *
354      * @param string  $name  name of the admin panel
355      * @param boolean &$isOK result
356      *
357      * @return boolean hook value
358      */
359     function onAdminPanelCheck($name, &$isOK)
360     {
361         if ($name == 'blacklist') {
362             $isOK = $this->canAdmin;
363             return false;
364         }
365
366         return true;
367     }
368
369     /**
370      * Add our tab to the admin panel
371      *
372      * @param Widget $nav Admin panel nav
373      *
374      * @return boolean hook value
375      */
376     function onEndAdminPanelNav($nav)
377     {
378         if (AdminPanelAction::canAdmin('blacklist')) {
379
380             $action_name = $nav->action->trimmed('action');
381
382             $nav->out->menuItem(common_local_url('blacklistadminpanel'),
383                                 // TRANS: Menu item in admin panel.
384                                 _m('MENU','Blacklist'),
385                                 // TRANS: Tooltip for menu item in admin panel.
386                                 _m('TOOLTIP','Blacklist configuration.'),
387                                 $action_name == 'blacklistadminpanel',
388                                 'nav_blacklist_admin_panel');
389         }
390
391         return true;
392     }
393
394     function onEndDeleteUserForm($action, $user)
395     {
396         $cur = common_current_user();
397
398         if (empty($cur) || !$cur->hasRight(Right::CONFIGURESITE)) {
399             return;
400         }
401
402         $profile = $user->getProfile();
403
404         if (empty($profile)) {
405             return;
406         }
407
408         $action->elementStart('ul', 'form_data');
409         $action->elementStart('li');
410         $this->checkboxAndText($action,
411                                'blacklistnickname',
412                                // TRANS: Checkbox label in the blacklist user form.
413                                _m('Add this nickname pattern to blacklist'),
414                                'blacklistnicknamepattern',
415                                $this->patternizeNickname($user->nickname));
416         $action->elementEnd('li');
417
418         if (!empty($profile->homepage)) {
419             $action->elementStart('li');
420             $this->checkboxAndText($action,
421                                    'blacklisthomepage',
422                                    // TRANS: Checkbox label in the blacklist user form.
423                                    _m('Add this homepage pattern to blacklist'),
424                                    'blacklisthomepagepattern',
425                                    $this->patternizeHomepage($profile->homepage));
426             $action->elementEnd('li');
427         }
428
429         $action->elementEnd('ul');
430     }
431
432     function onEndDeleteUser($action, $user)
433     {
434         if ($action->boolean('blacklisthomepage')) {
435             $pattern = $action->trimmed('blacklisthomepagepattern');
436             Homepage_blacklist::ensurePattern($pattern);
437         }
438
439         if ($action->boolean('blacklistnickname')) {
440             $pattern = $action->trimmed('blacklistnicknamepattern');
441             Nickname_blacklist::ensurePattern($pattern);
442         }
443
444         return true;
445     }
446
447     function checkboxAndText($action, $checkID, $label, $textID, $value)
448     {
449         $action->element('input', array('name' => $checkID,
450                                         'type' => 'checkbox',
451                                         'class' => 'checkbox',
452                                         'id' => $checkID));
453
454         $action->text(' ');
455
456         $action->element('label', array('class' => 'checkbox',
457                                         'for' => $checkID),
458                          $label);
459
460         $action->text(' ');
461
462         $action->element('input', array('name' => $textID,
463                                         'type' => 'text',
464                                         'id' => $textID,
465                                         'value' => $value));
466     }
467
468     function patternizeNickname($nickname)
469     {
470         return $nickname;
471     }
472
473     function patternizeHomepage($homepage)
474     {
475         $hostname = parse_url($homepage, PHP_URL_HOST);
476         return $hostname;
477     }
478
479     function onStartHandleFeedEntry($activity)
480     {
481         return $this->_checkActivity($activity);
482     }
483
484     function onStartHandleSalmon($activity)
485     {
486         return $this->_checkActivity($activity);
487     }
488
489     function _checkActivity($activity)
490     {
491         $actor = $activity->actor;
492
493         if (empty($actor)) {
494             return true;
495         }
496
497         $homepage = strtolower($actor->link);
498
499         if (!empty($homepage)) {
500             if (!$this->_checkUrl($homepage)) {
501                 // TRANS: Exception thrown trying to post a notice while having set a blocked homepage URL. %s is the blocked URL.
502                 $msg = sprintf(_m("Users from \"%s\" are blocked."),
503                                $homepage);
504                 throw new ClientException($msg);
505             }
506         }
507
508         $nickname = strtolower($actor->poco->preferredUsername);
509
510         if (!empty($nickname)) {
511             if (!$this->_checkNickname($nickname)) {
512                 // TRANS: Exception thrown trying to post a notice while having a blocked nickname. %s is the blocked nickname.
513                 $msg = sprintf(_m("Notices from nickname \"%s\" disallowed."),
514                                $nickname);
515                 throw new ClientException($msg);
516             }
517         }
518
519         return true;
520     }
521
522     /**
523      * Check URLs and homepages for blacklisted users.
524      */
525     function onStartSubscribe($subscriber, $other)
526     {
527         foreach (array($other->profileurl, $other->homepage) as $url) {
528
529             if (empty($url)) {
530                 continue;
531             }
532
533             $url = strtolower($url);
534
535             if (!$this->_checkUrl($url)) {
536                 // TRANS: Client exception thrown trying to subscribe to a person with a blocked homepage or site URL. %s is the blocked URL.
537                 $msg = sprintf(_m("Users from \"%s\" are blocked."),
538                                $url);
539                 throw new ClientException($msg);
540             }
541         }
542
543         $nickname = $other->nickname;
544
545         if (!empty($nickname)) {
546             if (!$this->_checkNickname($nickname)) {
547                 // TRANS: Client exception thrown trying to subscribe to a person with a blocked nickname. %s is the blocked nickname.
548                 $msg = sprintf(_m("Cannot subscribe to nickname \"%s\"."),
549                                $nickname);
550                 throw new ClientException($msg);
551             }
552         }
553
554         return true;
555     }
556 }