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 = GNUSOCIAL_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', Nickname_blacklist::schemaDef());
84 $schema->ensureTable('homepage_blacklist', Homepage_blacklist::schemaDef());
90 * Retrieve an array from configuration
92 * Carefully checks a section.
94 * @param string $section Configuration section
95 * @param string $setting Configuration setting
97 * @return array configuration values
99 function _configArray($section, $setting)
101 $config = common_config($section, $setting);
103 if (empty($config)) {
105 } else if (is_array($config)) {
107 } else if (is_string($config)) {
108 return explode("\r\n", $config);
110 // TRANS: Exception thrown if the Blacklist plugin configuration is incorrect.
111 // TRANS: %1$s is a configuration section, %2$s is a configuration setting.
112 throw new Exception(sprintf(_m('Unknown data type for config %1$s + %2$s.'),$section, $setting));
117 * Hook registration to prevent blacklisted homepages or nicknames
119 * Throws an exception if there's a blacklisted homepage or nickname.
121 * @param Action $action Action being called (usually register)
123 * @return boolean hook value
125 function onStartRegisterUser(&$user, &$profile)
127 $homepage = strtolower($profile->homepage);
129 if (!empty($homepage)) {
130 if (!$this->_checkUrl($homepage)) {
131 // TRANS: Validation failure for URL. %s is the URL.
132 $msg = sprintf(_m("You may not register with homepage \"%s\"."),
134 throw new ClientException($msg);
138 $nickname = strtolower($profile->nickname);
140 if (!empty($nickname)) {
141 if (!$this->_checkNickname($nickname)) {
142 // TRANS: Validation failure for nickname. %s is the nickname.
143 $msg = sprintf(_m("You may not register with nickname \"%s\"."),
145 throw new ClientException($msg);
153 * Hook profile update to prevent blacklisted homepages or nicknames
155 * Throws an exception if there's a blacklisted homepage or nickname.
157 * @param Action $action Action being called (usually register)
159 * @return boolean hook value
161 function onStartProfileSaveForm($action)
163 $homepage = strtolower($action->trimmed('homepage'));
165 if (!empty($homepage)) {
166 if (!$this->_checkUrl($homepage)) {
167 // TRANS: Validation failure for URL. %s is the URL.
168 $msg = sprintf(_m("You may not use homepage \"%s\"."),
170 throw new ClientException($msg);
174 $nickname = strtolower($action->trimmed('nickname'));
176 if (!empty($nickname)) {
177 if (!$this->_checkNickname($nickname)) {
178 // TRANS: Validation failure for nickname. %s is the nickname.
179 $msg = sprintf(_m("You may not use nickname \"%s\"."),
181 throw new ClientException($msg);
189 * Hook notice save to prevent blacklisted urls
191 * Throws an exception if there's a blacklisted url in the content.
193 * @param Notice &$notice Notice being saved
195 * @return boolean hook value
197 function onStartNoticeSave(&$notice)
199 common_replace_urls_callback($notice->content,
200 array($this, 'checkNoticeUrl'));
205 * Helper callback for notice save
207 * Throws an exception if there's a blacklisted url in the content.
209 * @param string $url URL in the notice content
211 * @return boolean hook value
213 function checkNoticeUrl($url)
215 // It comes in special'd, so we unspecial it
216 // before comparing against patterns
218 $url = htmlspecialchars_decode($url);
220 if (!$this->_checkUrl($url)) {
221 // TRANS: Validation failure for URL. %s is the URL.
222 $msg = sprintf(_m("You may not use URL \"%s\" in notices."),
224 throw new ClientException($msg);
231 * Helper for checking URLs
233 * Checks an URL against our patterns for a match.
235 * @param string $url URL to check
237 * @return boolean true means it's OK, false means it's bad
239 private function _checkUrl($url)
241 $patterns = $this->_getUrlPatterns();
243 foreach ($patterns as $pattern) {
244 if ($pattern != '' && preg_match("/$pattern/", $url)) {
253 * Helper for checking nicknames
255 * Checks a nickname against our patterns for a match.
257 * @param string $nickname nickname to check
259 * @return boolean true means it's OK, false means it's bad
261 private function _checkNickname($nickname)
263 $patterns = $this->_getNicknamePatterns();
265 foreach ($patterns as $pattern) {
266 if ($pattern != '' && preg_match("/$pattern/", $nickname)) {
275 * Add our actions to the URL router
277 * @param URLMapper $m URL mapper for this hit
279 * @return boolean hook return
281 public function onRouterInitialized(URLMapper $m)
283 $m->connect('panel/blacklist', array('action' => 'blacklistadminpanel'));
288 * Plugin version data
290 * @param array &$versions array of version blocks
292 * @return boolean hook value
294 function onPluginVersion(array &$versions)
296 $versions[] = array('name' => 'Blacklist',
297 'version' => self::VERSION,
298 'author' => 'Evan Prodromou',
300 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/Blacklist',
302 // TRANS: Plugin description.
303 _m('Keeps a blacklist of forbidden nickname '.
304 'and URL patterns.'));
309 * Determines if our admin panel can be shown
311 * @param string $name name of the admin panel
312 * @param boolean &$isOK result
314 * @return boolean hook value
316 function onAdminPanelCheck($name, &$isOK)
318 if ($name == 'blacklist') {
319 $isOK = $this->canAdmin;
327 * Add our tab to the admin panel
329 * @param Widget $nav Admin panel nav
331 * @return boolean hook value
333 function onEndAdminPanelNav($nav)
335 if (AdminPanelAction::canAdmin('blacklist')) {
337 $action_name = $nav->action->trimmed('action');
339 $nav->out->menuItem(common_local_url('blacklistadminpanel'),
340 // TRANS: Menu item in admin panel.
341 _m('MENU','Blacklist'),
342 // TRANS: Tooltip for menu item in admin panel.
343 _m('TOOLTIP','Blacklist configuration.'),
344 $action_name == 'blacklistadminpanel',
345 'nav_blacklist_admin_panel');
351 function onEndDeleteUserForm($action, $user)
353 $cur = common_current_user();
355 if (empty($cur) || !$cur->hasRight(Right::CONFIGURESITE)) {
359 $profile = $user->getProfile();
361 if (empty($profile)) {
365 $action->elementStart('ul', 'form_data');
366 $action->elementStart('li');
367 $this->checkboxAndText($action,
369 // TRANS: Checkbox label in the blacklist user form.
370 _m('Add this nickname pattern to blacklist'),
371 'blacklistnicknamepattern',
372 $this->patternizeNickname($user->nickname));
373 $action->elementEnd('li');
375 if (!empty($profile->homepage)) {
376 $action->elementStart('li');
377 $this->checkboxAndText($action,
379 // TRANS: Checkbox label in the blacklist user form.
380 _m('Add this homepage pattern to blacklist'),
381 'blacklisthomepagepattern',
382 $this->patternizeHomepage($profile->homepage));
383 $action->elementEnd('li');
386 $action->elementEnd('ul');
389 function onEndDeleteUser($action, $user)
391 if ($action->boolean('blacklisthomepage')) {
392 $pattern = $action->trimmed('blacklisthomepagepattern');
393 Homepage_blacklist::ensurePattern($pattern);
396 if ($action->boolean('blacklistnickname')) {
397 $pattern = $action->trimmed('blacklistnicknamepattern');
398 Nickname_blacklist::ensurePattern($pattern);
404 function checkboxAndText($action, $checkID, $label, $textID, $value)
406 $action->element('input', array('name' => $checkID,
407 'type' => 'checkbox',
408 'class' => 'checkbox',
413 $action->element('label', array('class' => 'checkbox',
419 $action->element('input', array('name' => $textID,
425 function patternizeNickname($nickname)
430 function patternizeHomepage($homepage)
432 $hostname = parse_url($homepage, PHP_URL_HOST);
436 function onStartHandleFeedEntry($activity)
438 return $this->_checkActivity($activity);
441 function onStartHandleSalmon($activity)
443 return $this->_checkActivity($activity);
446 function _checkActivity($activity)
448 $actor = $activity->actor;
454 $homepage = strtolower($actor->link);
456 if (!empty($homepage)) {
457 if (!$this->_checkUrl($homepage)) {
458 // TRANS: Exception thrown trying to post a notice while having set a blocked homepage URL. %s is the blocked URL.
459 $msg = sprintf(_m("Users from \"%s\" are blocked."),
461 throw new ClientException($msg);
465 if (!empty($actor->poco)) {
466 $nickname = strtolower($actor->poco->preferredUsername);
468 if (!empty($nickname)) {
469 if (!$this->_checkNickname($nickname)) {
470 // TRANS: Exception thrown trying to post a notice while having a blocked nickname. %s is the blocked nickname.
471 $msg = sprintf(_m("Notices from nickname \"%s\" are disallowed."),
473 throw new ClientException($msg);
482 * Check URLs and homepages for blacklisted users.
484 function onStartSubscribe(Profile $subscriber, Profile $other)
486 foreach (array($other->profileurl, $other->homepage) as $url) {
492 $url = strtolower($url);
494 if (!$this->_checkUrl($url)) {
495 // TRANS: Client exception thrown trying to subscribe to a person with a blocked homepage or site URL. %s is the blocked URL.
496 $msg = sprintf(_m("Users from \"%s\" are blocked."),
498 throw new ClientException($msg);
502 $nickname = $other->nickname;
504 if (!empty($nickname)) {
505 if (!$this->_checkNickname($nickname)) {
506 // TRANS: Client exception thrown trying to subscribe to a person with a blocked nickname. %s is the blocked nickname.
507 $msg = sprintf(_m("Cannot subscribe to nickname \"%s\"."),
509 throw new ClientException($msg);