3 * StatusNet, the distributed open-source microblogging tool
5 * Plugin to prevent use of nicknames or URLs on a blacklist
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.
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.
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/>.
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/
30 if (!defined('STATUSNET')) {
35 * Plugin to prevent use of nicknames or URLs on a blacklist
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/
43 class BlacklistPlugin extends Plugin
45 const VERSION = STATUSNET_VERSION;
47 public $nicknames = array();
48 public $urls = array();
49 public $canAdmin = true;
51 function _getNicknamePatterns()
53 $confNicknames = $this->_configArray('blacklist', 'nicknames');
55 $dbNicknames = Nickname_blacklist::getPatterns();
57 return array_merge($this->nicknames,
62 function _getUrlPatterns()
64 $confURLs = $this->_configArray('blacklist', 'urls');
66 $dbURLs = Homepage_blacklist::getPatterns();
68 return array_merge($this->urls,
74 * Database schema setup
76 * @return boolean hook value
78 function onCheckSchema()
80 $schema = Schema::get();
82 // For storing blacklist patterns for nicknames
83 $schema->ensureTable('nickname_blacklist',
84 array(new ColumnDef('pattern',
89 new ColumnDef('created',
94 $schema->ensureTable('homepage_blacklist',
95 array(new ColumnDef('pattern',
100 new ColumnDef('created',
109 * Retrieve an array from configuration
111 * Carefully checks a section.
113 * @param string $section Configuration section
114 * @param string $setting Configuration setting
116 * @return array configuration values
118 function _configArray($section, $setting)
120 $config = common_config($section, $setting);
122 if (empty($config)) {
124 } else if (is_array($config)) {
126 } else if (is_string($config)) {
127 return explode("\r\n", $config);
129 throw new Exception("Unknown data type for config $section + $setting");
134 * Hook registration to prevent blacklisted homepages or nicknames
136 * Throws an exception if there's a blacklisted homepage or nickname.
138 * @param Action $action Action being called (usually register)
140 * @return boolean hook value
142 function onStartRegistrationTry($action)
144 $homepage = strtolower($action->trimmed('homepage'));
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\"."),
151 throw new ClientException($msg);
155 $nickname = strtolower($action->trimmed('nickname'));
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\"."),
162 throw new ClientException($msg);
170 * Hook profile update to prevent blacklisted homepages or nicknames
172 * Throws an exception if there's a blacklisted homepage or nickname.
174 * @param Action $action Action being called (usually register)
176 * @return boolean hook value
178 function onStartProfileSaveForm($action)
180 $homepage = strtolower($action->trimmed('homepage'));
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\"."),
187 throw new ClientException($msg);
191 $nickname = strtolower($action->trimmed('nickname'));
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\"."),
198 throw new ClientException($msg);
206 * Hook notice save to prevent blacklisted urls
208 * Throws an exception if there's a blacklisted url in the content.
210 * @param Notice &$notice Notice being saved
212 * @return boolean hook value
214 function onStartNoticeSave(&$notice)
216 common_replace_urls_callback($notice->content,
217 array($this, 'checkNoticeUrl'));
222 * Helper callback for notice save
224 * Throws an exception if there's a blacklisted url in the content.
226 * @param string $url URL in the notice content
228 * @return boolean hook value
230 function checkNoticeUrl($url)
232 // It comes in special'd, so we unspecial it
233 // before comparing against patterns
235 $url = htmlspecialchars_decode($url);
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."),
241 throw new ClientException($msg);
248 * Helper for checking URLs
250 * Checks an URL against our patterns for a match.
252 * @param string $url URL to check
254 * @return boolean true means it's OK, false means it's bad
256 private function _checkUrl($url)
258 $patterns = $this->_getUrlPatterns();
260 foreach ($patterns as $pattern) {
261 if ($pattern != '' && preg_match("/$pattern/", $url)) {
270 * Helper for checking nicknames
272 * Checks a nickname against our patterns for a match.
274 * @param string $nickname nickname to check
276 * @return boolean true means it's OK, false means it's bad
278 private function _checkNickname($nickname)
280 $patterns = $this->_getNicknamePatterns();
282 foreach ($patterns as $pattern) {
283 if ($pattern != '' && preg_match("/$pattern/", $nickname)) {
292 * Add our actions to the URL router
294 * @param Net_URL_Mapper $m URL mapper for this hit
296 * @return boolean hook return
298 function onRouterInitialized($m)
300 $m->connect('panel/blacklist', array('action' => 'blacklistadminpanel'));
305 * Auto-load our classes if called
307 * @param string $cls Class to load
309 * @return boolean hook return
311 function onAutoload($cls)
313 switch (strtolower($cls))
315 case 'nickname_blacklist':
316 case 'homepage_blacklist':
317 include_once INSTALLDIR.'/plugins/Blacklist/'.ucfirst($cls).'.php';
319 case 'blacklistadminpanelaction':
320 $base = strtolower(mb_substr($cls, 0, -6));
321 include_once INSTALLDIR.'/plugins/Blacklist/'.$base.'.php';
329 * Plugin version data
331 * @param array &$versions array of version blocks
333 * @return boolean hook value
335 function onPluginVersion(&$versions)
337 $versions[] = array('name' => 'Blacklist',
338 'version' => self::VERSION,
339 'author' => 'Evan Prodromou',
341 'http://status.net/wiki/Plugin:Blacklist',
343 _m('Keeps a blacklist of forbidden nickname '.
344 'and URL patterns.'));
349 * Determines if our admin panel can be shown
351 * @param string $name name of the admin panel
352 * @param boolean &$isOK result
354 * @return boolean hook value
356 function onAdminPanelCheck($name, &$isOK)
358 if ($name == 'blacklist') {
359 $isOK = $this->canAdmin;
367 * Add our tab to the admin panel
369 * @param Widget $nav Admin panel nav
371 * @return boolean hook value
373 function onEndAdminPanelNav($nav)
375 if (AdminPanelAction::canAdmin('blacklist')) {
377 $action_name = $nav->action->trimmed('action');
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');
391 function onEndDeleteUserForm($action, $user)
393 $cur = common_current_user();
395 if (empty($cur) || !$cur->hasRight(Right::CONFIGURESITE)) {
399 $profile = $user->getProfile();
401 if (empty($profile)) {
405 $action->elementStart('ul', 'form_data');
406 $action->elementStart('li');
407 $this->checkboxAndText($action,
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');
415 if (!empty($profile->homepage)) {
416 $action->elementStart('li');
417 $this->checkboxAndText($action,
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');
426 $action->elementEnd('ul');
429 function onEndDeleteUser($action, $user)
431 if ($action->boolean('blacklisthomepage')) {
432 $pattern = $action->trimmed('blacklisthomepagepattern');
433 Homepage_blacklist::ensurePattern($pattern);
436 if ($action->boolean('blacklistnickname')) {
437 $pattern = $action->trimmed('blacklistnicknamepattern');
438 Nickname_blacklist::ensurePattern($pattern);
444 function checkboxAndText($action, $checkID, $label, $textID, $value)
446 $action->element('input', array('name' => $checkID,
447 'type' => 'checkbox',
448 'class' => 'checkbox',
453 $action->element('label', array('class' => 'checkbox',
459 $action->element('input', array('name' => $textID,
465 function patternizeNickname($nickname)
470 function patternizeHomepage($homepage)
472 $hostname = parse_url($homepage, PHP_URL_HOST);
476 function onStartHandleFeedEntry($activity)
478 return $this->_checkActivity($activity);
481 function onStartHandleSalmon($activity)
483 return $this->_checkActivity($activity);
486 function _checkActivity($activity)
488 $actor = $activity->actor;
494 $homepage = strtolower($actor->link);
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."),
501 throw new ClientException($msg);
505 $nickname = strtolower($actor->poco->preferredUsername);
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."),
512 throw new ClientException($msg);
520 * Check URLs and homepages for blacklisted users.
522 function onStartSubscribe($subscriber, $other)
524 foreach (array($other->profileurl, $other->homepage) as $url) {
530 $url = strtolower($url);
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."),
536 throw new ClientException($msg);
540 $nickname = $other->nickname;
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\"."),
547 throw new ClientException($msg);