]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Blacklist/BlacklistPlugin.php
Merge branch '1.0.x' into schema-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             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                 $msg = sprintf(_m("You may not register with homepage '%s'."),
149                                $homepage);
150                 throw new ClientException($msg);
151             }
152         }
153
154         $nickname = strtolower($action->trimmed('nickname'));
155
156         if (!empty($nickname)) {
157             if (!$this->_checkNickname($nickname)) {
158                 $msg = sprintf(_m("You may not register with nickname '%s'."),
159                                $nickname);
160                 throw new ClientException($msg);
161             }
162         }
163
164         return true;
165     }
166
167     /**
168      * Hook profile update to prevent blacklisted homepages or nicknames
169      *
170      * Throws an exception if there's a blacklisted homepage or nickname.
171      *
172      * @param Action $action Action being called (usually register)
173      *
174      * @return boolean hook value
175      */
176     function onStartProfileSaveForm($action)
177     {
178         $homepage = strtolower($action->trimmed('homepage'));
179
180         if (!empty($homepage)) {
181             if (!$this->_checkUrl($homepage)) {
182                 $msg = sprintf(_m("You may not use homepage '%s'."),
183                                $homepage);
184                 throw new ClientException($msg);
185             }
186         }
187
188         $nickname = strtolower($action->trimmed('nickname'));
189
190         if (!empty($nickname)) {
191             if (!$this->_checkNickname($nickname)) {
192                 $msg = sprintf(_m("You may not use nickname '%s'."),
193                                $nickname);
194                 throw new ClientException($msg);
195             }
196         }
197
198         return true;
199     }
200
201     /**
202      * Hook notice save to prevent blacklisted urls
203      *
204      * Throws an exception if there's a blacklisted url in the content.
205      *
206      * @param Notice &$notice Notice being saved
207      *
208      * @return boolean hook value
209      */
210     function onStartNoticeSave(&$notice)
211     {
212         common_replace_urls_callback($notice->content,
213                                      array($this, 'checkNoticeUrl'));
214         return true;
215     }
216
217     /**
218      * Helper callback for notice save
219      *
220      * Throws an exception if there's a blacklisted url in the content.
221      *
222      * @param string $url URL in the notice content
223      *
224      * @return boolean hook value
225      */
226     function checkNoticeUrl($url)
227     {
228         // It comes in special'd, so we unspecial it
229         // before comparing against patterns
230
231         $url = htmlspecialchars_decode($url);
232
233         if (!$this->_checkUrl($url)) {
234             $msg = sprintf(_m("You may not use URL \"%s\" in notices."),
235                            $url);
236             throw new ClientException($msg);
237         }
238
239         return $url;
240     }
241
242     /**
243      * Helper for checking URLs
244      *
245      * Checks an URL against our patterns for a match.
246      *
247      * @param string $url URL to check
248      *
249      * @return boolean true means it's OK, false means it's bad
250      */
251     private function _checkUrl($url)
252     {
253         $patterns = $this->_getUrlPatterns();
254
255         foreach ($patterns as $pattern) {
256             if ($pattern != '' && preg_match("/$pattern/", $url)) {
257                 return false;
258             }
259         }
260
261         return true;
262     }
263
264     /**
265      * Helper for checking nicknames
266      *
267      * Checks a nickname against our patterns for a match.
268      *
269      * @param string $nickname nickname to check
270      *
271      * @return boolean true means it's OK, false means it's bad
272      */
273     private function _checkNickname($nickname)
274     {
275         $patterns = $this->_getNicknamePatterns();
276
277         foreach ($patterns as $pattern) {
278             if ($pattern != '' && preg_match("/$pattern/", $nickname)) {
279                 return false;
280             }
281         }
282
283         return true;
284     }
285
286     /**
287      * Add our actions to the URL router
288      *
289      * @param Net_URL_Mapper $m URL mapper for this hit
290      *
291      * @return boolean hook return
292      */
293     function onRouterInitialized($m)
294     {
295         $m->connect('admin/blacklist', array('action' => 'blacklistadminpanel'));
296         return true;
297     }
298
299     /**
300      * Auto-load our classes if called
301      *
302      * @param string $cls Class to load
303      *
304      * @return boolean hook return
305      */
306     function onAutoload($cls)
307     {
308         switch (strtolower($cls))
309         {
310         case 'nickname_blacklist':
311         case 'homepage_blacklist':
312             include_once INSTALLDIR.'/plugins/Blacklist/'.ucfirst($cls).'.php';
313             return false;
314         case 'blacklistadminpanelaction':
315             $base = strtolower(mb_substr($cls, 0, -6));
316             include_once INSTALLDIR.'/plugins/Blacklist/'.$base.'.php';
317             return false;
318         default:
319             return true;
320         }
321     }
322
323     /**
324      * Plugin version data
325      *
326      * @param array &$versions array of version blocks
327      *
328      * @return boolean hook value
329      */
330     function onPluginVersion(&$versions)
331     {
332         $versions[] = array('name' => 'Blacklist',
333                             'version' => self::VERSION,
334                             'author' => 'Evan Prodromou',
335                             'homepage' =>
336                             'http://status.net/wiki/Plugin:Blacklist',
337                             'description' =>
338                             _m('Keeps a blacklist of forbidden nickname '.
339                                'and URL patterns.'));
340         return true;
341     }
342
343     /**
344      * Determines if our admin panel can be shown
345      *
346      * @param string  $name  name of the admin panel
347      * @param boolean &$isOK result
348      *
349      * @return boolean hook value
350      */
351     function onAdminPanelCheck($name, &$isOK)
352     {
353         if ($name == 'blacklist') {
354             $isOK = $this->canAdmin;
355             return false;
356         }
357
358         return true;
359     }
360
361     /**
362      * Add our tab to the admin panel
363      *
364      * @param Widget $nav Admin panel nav
365      *
366      * @return boolean hook value
367      */
368     function onEndAdminPanelNav($nav)
369     {
370         if (AdminPanelAction::canAdmin('blacklist')) {
371
372             $action_name = $nav->action->trimmed('action');
373
374             $nav->out->menuItem(common_local_url('blacklistadminpanel'),
375                                 _m('Blacklist'),
376                                 _m('Blacklist configuration'),
377                                 $action_name == 'blacklistadminpanel',
378                                 'nav_blacklist_admin_panel');
379         }
380
381         return true;
382     }
383
384     function onEndDeleteUserForm($action, $user)
385     {
386         $cur = common_current_user();
387
388         if (empty($cur) || !$cur->hasRight(Right::CONFIGURESITE)) {
389             return;
390         }
391
392         $profile = $user->getProfile();
393
394         if (empty($profile)) {
395             return;
396         }
397
398         $action->elementStart('ul', 'form_data');
399         $action->elementStart('li');
400         $this->checkboxAndText($action,
401                                'blacklistnickname',
402                                _m('Add this nickname pattern to blacklist'),
403                                'blacklistnicknamepattern',
404                                $this->patternizeNickname($user->nickname));
405         $action->elementEnd('li');
406
407         if (!empty($profile->homepage)) {
408             $action->elementStart('li');
409             $this->checkboxAndText($action,
410                                    'blacklisthomepage',
411                                    _m('Add this homepage pattern to blacklist'),
412                                    'blacklisthomepagepattern',
413                                    $this->patternizeHomepage($profile->homepage));
414             $action->elementEnd('li');
415         }
416
417         $action->elementEnd('ul');
418     }
419
420     function onEndDeleteUser($action, $user)
421     {
422         if ($action->boolean('blacklisthomepage')) {
423             $pattern = $action->trimmed('blacklisthomepagepattern');
424             Homepage_blacklist::ensurePattern($pattern);
425         }
426
427         if ($action->boolean('blacklistnickname')) {
428             $pattern = $action->trimmed('blacklistnicknamepattern');
429             Nickname_blacklist::ensurePattern($pattern);
430         }
431
432         return true;
433     }
434
435     function checkboxAndText($action, $checkID, $label, $textID, $value)
436     {
437         $action->element('input', array('name' => $checkID,
438                                         'type' => 'checkbox',
439                                         'class' => 'checkbox',
440                                         'id' => $checkID));
441
442         $action->text(' ');
443
444         $action->element('label', array('class' => 'checkbox',
445                                         'for' => $checkID),
446                          $label);
447
448         $action->text(' ');
449
450         $action->element('input', array('name' => $textID,
451                                         'type' => 'text',
452                                         'id' => $textID,
453                                         'value' => $value));
454     }
455
456     function patternizeNickname($nickname)
457     {
458         return $nickname;
459     }
460
461     function patternizeHomepage($homepage)
462     {
463         $hostname = parse_url($homepage, PHP_URL_HOST);
464         return $hostname;
465     }
466 }