summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--EVENTS.txt15
-rw-r--r--actions/apiblockcreate.php13
-rw-r--r--actions/apiblockdestroy.php13
-rw-r--r--actions/block.php8
-rw-r--r--actions/unblock.php13
-rw-r--r--classes/User.php6
-rw-r--r--js/geometa.js123
-rw-r--r--plugins/Blacklist/BlacklistPlugin.php203
-rw-r--r--plugins/UserFlag/UserFlagPlugin.php138
-rw-r--r--plugins/UserFlag/User_flag_profile.php78
-rw-r--r--plugins/UserFlag/adminprofileflag.php218
-rw-r--r--plugins/UserFlag/clearflag.php138
-rw-r--r--plugins/UserFlag/clearflagform.php92
-rw-r--r--plugins/UserFlag/flagprofile.php26
-rw-r--r--scripts/setconfig.php98
15 files changed, 1013 insertions, 169 deletions
diff --git a/EVENTS.txt b/EVENTS.txt
index 96250f64c..64e345b69 100644
--- a/EVENTS.txt
+++ b/EVENTS.txt
@@ -640,3 +640,18 @@ EndLog: After writing to the logs
- $msg
- $filename
+StartBlockProfile: when we're about to block
+- $user: the person doing the block
+- $profile: the person getting blocked, can be remote
+
+EndBlockProfile: when a block has succeeded
+- $user: the person doing the block
+- $profile: the person blocked, can be remote
+
+StartUnblockProfile: when we're about to unblock
+- $user: the person doing the unblock
+- $profile: the person getting unblocked, can be remote
+
+EndUnblockProfile: when an unblock has succeeded
+- $user: the person doing the unblock
+- $profile: the person unblocked, can be remote
diff --git a/actions/apiblockcreate.php b/actions/apiblockcreate.php
index e79dec32d..c26485f59 100644
--- a/actions/apiblockcreate.php
+++ b/actions/apiblockcreate.php
@@ -109,9 +109,16 @@ class ApiBlockCreateAction extends ApiAuthAction
return;
}
- if ($this->user->hasBlocked($this->other)
- || $this->user->block($this->other)
- ) {
+ if (!$this->user->hasBlocked($this->other)) {
+ if (Event::handle('StartBlockProfile', array($this->user, $this->other))) {
+ $result = $this->user->block($this->other);
+ if ($result) {
+ Event::handle('EndBlockProfile', array($this->user, $this->other));
+ }
+ }
+ }
+
+ if ($this->user->hasBlocked($this->other)) {
$this->initDocument($this->format);
$this->showProfile($this->other, $this->format);
$this->endDocument($this->format);
diff --git a/actions/apiblockdestroy.php b/actions/apiblockdestroy.php
index 328f18ab0..666f308f4 100644
--- a/actions/apiblockdestroy.php
+++ b/actions/apiblockdestroy.php
@@ -97,9 +97,16 @@ class ApiBlockDestroyAction extends ApiAuthAction
return;
}
- if (!$this->user->hasBlocked($this->other)
- || $this->user->unblock($this->other)
- ) {
+ if ($this->user->hasBlocked($this->other)) {
+ if (Event::handle('StartUnblockProfile', array($this->user, $this->other))) {
+ $result = $this->user->unblock($this->other);
+ if ($result) {
+ Event::handle('EndUnblockProfile', array($this->user, $this->other));
+ }
+ }
+ }
+
+ if (!$this->user->hasBlocked($this->other)) {
$this->initDocument($this->format);
$this->showProfile($this->other, $this->format);
$this->endDocument($this->format);
diff --git a/actions/block.php b/actions/block.php
index 71a34e087..5fae45dff 100644
--- a/actions/block.php
+++ b/actions/block.php
@@ -156,7 +156,12 @@ class BlockAction extends ProfileFormAction
{
$cur = common_current_user();
- $result = $cur->block($this->profile);
+ if (Event::handle('StartBlockProfile', array($cur, $this->profile))) {
+ $result = $cur->block($this->profile);
+ if ($result) {
+ Event::handle('EndBlockProfile', array($cur, $this->profile));
+ }
+ }
if (!$result) {
$this->serverError(_('Failed to save block information.'));
@@ -164,4 +169,3 @@ class BlockAction extends ProfileFormAction
}
}
}
-
diff --git a/actions/unblock.php b/actions/unblock.php
index c60458cd3..0f63e1dae 100644
--- a/actions/unblock.php
+++ b/actions/unblock.php
@@ -71,8 +71,17 @@ class UnblockAction extends ProfileFormAction
function handlePost()
{
- $cur = common_current_user();
- $result = $cur->unblock($this->profile);
+ $cur = common_current_user();
+
+ $result = false;
+
+ if (Event::handle('StartUnblockProfile', array($cur, $this->profile))) {
+ $result = $cur->unblock($this->profile);
+ if ($result) {
+ Event::handle('EndUnblockProfile', array($cur, $this->profile));
+ }
+ }
+
if (!$result) {
$this->serverError(_('Error removing the block.'));
return;
diff --git a/classes/User.php b/classes/User.php
index 484dc8c82..6708d95b6 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -625,7 +625,11 @@ class User extends Memcached_DataObject
// Cancel their subscription, if it exists
- subs_unsubscribe_to($other->getUser(),$this->getProfile());
+ $otherUser = User::staticGet('id', $other->id);
+
+ if (!empty($otherUser)) {
+ subs_unsubscribe_to($otherUser, $this->getProfile());
+ }
$block->query('COMMIT');
diff --git a/js/geometa.js b/js/geometa.js
index 21deb1885..87e3c99a1 100644
--- a/js/geometa.js
+++ b/js/geometa.js
@@ -1,4 +1,4 @@
-// A shim to implement the W3C Geolocation API Specification using Gears or the Ajax API
+// A shim to implement the W3C Geolocation API Specification using Gears
if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) (function(){
// -- BEGIN GEARS_INIT
@@ -96,122 +96,9 @@ var GearsGeoLocation = (function() {
};
});
-var AjaxGeoLocation = (function() {
- // -- PRIVATE
- var loading = false;
- var loadGoogleLoader = function() {
- if (!hasGoogleLoader() && !loading) {
- loading = true;
- var s = document.createElement('script');
- s.src = (document.location.protocol == "https:"?"https://":"http://") + 'www.google.com/jsapi?callback=_google_loader_apiLoaded';
- s.type = "text/javascript";
- document.getElementsByTagName('body')[0].appendChild(s);
- }
- };
-
- var queue = [];
- var addLocationQueue = function(callback) {
- queue.push(callback);
- }
-
- var runLocationQueue = function() {
- if (hasGoogleLoader()) {
- while (queue.length > 0) {
- var call = queue.pop();
- call();
- }
- }
- }
-
- window['_google_loader_apiLoaded'] = function() {
- runLocationQueue();
- }
-
- var hasGoogleLoader = function() {
- return (window['google'] && google['loader']);
- }
-
- var checkGoogleLoader = function(callback) {
- if (hasGoogleLoader()) return true;
-
- addLocationQueue(callback);
-
- loadGoogleLoader();
-
- return false;
- };
-
- loadGoogleLoader(); // start to load as soon as possible just in case
-
- // -- PUBLIC
- return {
- shim: true,
-
- type: "ClientLocation",
-
- lastPosition: null,
-
- getCurrentPosition: function(successCallback, errorCallback, options) {
- var self = this;
- if (!checkGoogleLoader(function() {
- self.getCurrentPosition(successCallback, errorCallback, options);
- })) return;
-
- if (google.loader.ClientLocation) {
- var cl = google.loader.ClientLocation;
-
- var position = {
- coords: {
- latitude: cl.latitude,
- longitude: cl.longitude,
- altitude: null,
- accuracy: 43000, // same as Gears accuracy over wifi?
- altitudeAccuracy: null,
- heading: null,
- speed: null,
- },
- // extra info that is outside of the bounds of the core API
- address: {
- city: cl.address.city,
- country: cl.address.country,
- country_code: cl.address.country_code,
- region: cl.address.region
- },
- timestamp: new Date()
- };
-
- successCallback(position);
-
- this.lastPosition = position;
- } else if (errorCallback === "function") {
- errorCallback({ code: 3, message: "Using the Google ClientLocation API and it is not able to calculate a location."});
- }
- },
-
- watchPosition: function(successCallback, errorCallback, options) {
- this.getCurrentPosition(successCallback, errorCallback, options);
-
- var self = this;
- var watchId = setInterval(function() {
- self.getCurrentPosition(successCallback, errorCallback, options);
- }, 10000);
-
- return watchId;
- },
-
- clearWatch: function(watchId) {
- clearInterval(watchId);
- },
-
- getPermission: function(siteName, imageUrl, extraMessage) {
- // for now just say yes :)
- return true;
- }
-
- };
-});
-
-// If you have Gears installed use that, else use Ajax ClientLocation
-navigator.geolocation = (window.google && google.gears) ? GearsGeoLocation() : AjaxGeoLocation();
+// If you have Gears installed use that
+if (window.google && google.gears) {
+ navigator.geolocation = GearsGeoLocation();
+}
})();
diff --git a/plugins/Blacklist/BlacklistPlugin.php b/plugins/Blacklist/BlacklistPlugin.php
new file mode 100644
index 000000000..655b0926b
--- /dev/null
+++ b/plugins/Blacklist/BlacklistPlugin.php
@@ -0,0 +1,203 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to prevent use of nicknames or URLs on a blacklist
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Plugin to prevent use of nicknames or URLs on a blacklist
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class BlacklistPlugin extends Plugin
+{
+ public $nicknames = array();
+ public $urls = array();
+
+ /**
+ * Hook registration to prevent blacklisted homepages or nicknames
+ *
+ * Throws an exception if there's a blacklisted homepage or nickname.
+ *
+ * @param Action $action Action being called (usually register)
+ *
+ * @return boolean hook value
+ */
+
+ function onStartRegistrationTry($action)
+ {
+ $homepage = strtolower($action->trimmed('homepage'));
+
+ if (!empty($homepage)) {
+ if (!$this->_checkUrl($homepage)) {
+ $msg = sprintf(_m("You may not register with homepage '%s'"),
+ $homepage);
+ throw new ClientException($msg);
+ }
+ }
+
+ $nickname = strtolower($action->trimmed('nickname'));
+
+ if (!empty($nickname)) {
+ if (!$this->_checkNickname($nickname)) {
+ $msg = sprintf(_m("You may not register with nickname '%s'"),
+ $nickname);
+ throw new ClientException($msg);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Hook profile update to prevent blacklisted homepages or nicknames
+ *
+ * Throws an exception if there's a blacklisted homepage or nickname.
+ *
+ * @param Action $action Action being called (usually register)
+ *
+ * @return boolean hook value
+ */
+
+ function onStartProfileSaveForm($action)
+ {
+ $homepage = strtolower($action->trimmed('homepage'));
+
+ if (!empty($homepage)) {
+ if (!$this->_checkUrl($homepage)) {
+ $msg = sprintf(_m("You may not use homepage '%s'"),
+ $homepage);
+ throw new ClientException($msg);
+ }
+ }
+
+ $nickname = strtolower($action->trimmed('nickname'));
+
+ if (!empty($nickname)) {
+ if (!$this->_checkNickname($nickname)) {
+ $msg = sprintf(_m("You may not use nickname '%s'"),
+ $nickname);
+ throw new ClientException($msg);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Hook notice save to prevent blacklisted urls
+ *
+ * Throws an exception if there's a blacklisted url in the content.
+ *
+ * @param Notice &$notice Notice being saved
+ *
+ * @return boolean hook value
+ */
+
+ function onStartNoticeSave(&$notice)
+ {
+ common_replace_urls_callback($notice->content,
+ array($this, 'checkNoticeUrl'));
+ return true;
+ }
+
+ /**
+ * Helper callback for notice save
+ *
+ * Throws an exception if there's a blacklisted url in the content.
+ *
+ * @param string $url URL in the notice content
+ *
+ * @return boolean hook value
+ */
+
+ function checkNoticeUrl($url)
+ {
+ // It comes in special'd, so we unspecial it
+ // before comparing against patterns
+
+ $url = htmlspecialchars_decode($url);
+
+ if (!$this->_checkUrl($url)) {
+ $msg = sprintf(_m("You may not use url '%s' in notices"),
+ $url);
+ throw new ClientException($msg);
+ }
+
+ return $url;
+ }
+
+ /**
+ * Helper for checking URLs
+ *
+ * Checks an URL against our patterns for a match.
+ *
+ * @param string $url URL to check
+ *
+ * @return boolean true means it's OK, false means it's bad
+ */
+
+ private function _checkUrl($url)
+ {
+ foreach ($this->urls as $pattern) {
+ if (preg_match("/$pattern/", $url)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Helper for checking nicknames
+ *
+ * Checks a nickname against our patterns for a match.
+ *
+ * @param string $nickname nickname to check
+ *
+ * @return boolean true means it's OK, false means it's bad
+ */
+
+ private function _checkNickname($nickname)
+ {
+ foreach ($this->nicknames as $pattern) {
+ if (preg_match("/$pattern/", $nickname)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php
index 75dcca4fc..0fca5f9cf 100644
--- a/plugins/UserFlag/UserFlagPlugin.php
+++ b/plugins/UserFlag/UserFlagPlugin.php
@@ -27,7 +27,7 @@
* @link http://status.net/
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
+if (!defined('STATUSNET')) {
exit(1);
}
@@ -43,6 +43,20 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
class UserFlagPlugin extends Plugin
{
+ const REVIEWFLAGS = 'UserFlagPlugin::reviewflags';
+ const CLEARFLAGS = 'UserFlagPlugin::clearflags';
+
+ public $flagOnBlock = true;
+
+ /**
+ * Hook for ensuring our tables are created
+ *
+ * Ensures that the user_flag_profile table exists
+ * and has the right columns.
+ *
+ * @return boolean hook return
+ */
+
function onCheckSchema()
{
$schema = Schema::get();
@@ -62,37 +76,61 @@ class UserFlagPlugin extends Plugin
return true;
}
- function onInitializePlugin()
- {
- // XXX: do something here?
- return true;
- }
+ /**
+ * Add our actions to the URL router
+ *
+ * @param Net_URL_Mapper $m URL mapper for this hit
+ *
+ * @return boolean hook return
+ */
- function onRouterInitialized($m) {
+ function onRouterInitialized($m)
+ {
$m->connect('main/flag/profile', array('action' => 'flagprofile'));
+ $m->connect('main/flag/clear', array('action' => 'clearflag'));
$m->connect('admin/profile/flag', array('action' => 'adminprofileflag'));
return true;
}
- function onAutoload($cls)
+ /**
+ * Auto-load our classes if called
+ *
+ * @param string $cls Class to load
+ *
+ * @return boolean hook return
+ */
+
+ function onAutoload($cls)
{
switch ($cls)
{
case 'FlagprofileAction':
case 'AdminprofileflagAction':
- require_once(INSTALLDIR.'/plugins/UserFlag/' . strtolower(mb_substr($cls, 0, -6)) . '.php');
+ case 'ClearflagAction':
+ include_once INSTALLDIR.'/plugins/UserFlag/' .
+ strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
case 'FlagProfileForm':
- require_once(INSTALLDIR.'/plugins/UserFlag/' . strtolower($cls . '.php'));
+ case 'ClearFlagForm':
+ include_once INSTALLDIR.'/plugins/UserFlag/' . strtolower($cls . '.php');
return false;
case 'User_flag_profile':
- require_once(INSTALLDIR.'/plugins/UserFlag/'.$cls.'.php');
+ include_once INSTALLDIR.'/plugins/UserFlag/'.$cls.'.php';
return false;
default:
return true;
}
}
+ /**
+ * Add a 'flag' button to profile page
+ *
+ * @param Action &$action The action being called
+ * @param Profile $profile Profile being shown
+ *
+ * @return boolean hook result
+ */
+
function onEndProfilePageActionsElements(&$action, $profile)
{
$user = common_current_user();
@@ -105,8 +143,8 @@ class UserFlagPlugin extends Plugin
$action->element('p', 'flagged', _('Flagged'));
} else {
$form = new FlagProfileForm($action, $profile,
- array('action' => 'showstream',
- 'nickname' => $profile->nickname));
+ array('action' => 'showstream',
+ 'nickname' => $profile->nickname));
$form->show();
}
@@ -116,6 +154,14 @@ class UserFlagPlugin extends Plugin
return true;
}
+ /**
+ * Add a 'flag' button to profiles in a list
+ *
+ * @param ProfileListItem $item item being shown
+ *
+ * @return boolean hook result
+ */
+
function onEndProfileListItemActionElements($item)
{
$user = common_current_user();
@@ -136,16 +182,78 @@ class UserFlagPlugin extends Plugin
return true;
}
+ /**
+ * Add our plugin's CSS to page output
+ *
+ * @param Action $action action being shown
+ *
+ * @return boolean hook result
+ */
+
function onEndShowStatusNetStyles($action)
{
- $action->cssLink(common_path('plugins/UserFlag/userflag.css'),
+ $action->cssLink(common_path('plugins/UserFlag/userflag.css'),
null, 'screen, projection, tv');
return true;
}
+ /**
+ * Initialize any flagging buttons on the page
+ *
+ * @param Action $action action being shown
+ *
+ * @return boolean hook result
+ */
+
function onEndShowScripts($action)
{
- $action->inlineScript('if ($(".form_entity_flag").length > 0) { SN.U.FormXHR($(".form_entity_flag")); }');
+ $action->inlineScript('if ($(".form_entity_flag").length > 0) { '.
+ 'SN.U.FormXHR($(".form_entity_flag")); '.
+ '}');
+ return true;
+ }
+
+ /**
+ * Check whether a user has one of our defined rights
+ *
+ * We define extra rights; this function checks to see if a
+ * user has one of them.
+ *
+ * @param User $user User being checked
+ * @param string $right Right we're checking
+ * @param boolean &$result out, result of the check
+ *
+ * @return boolean hook result
+ */
+
+ function onUserRightsCheck($user, $right, &$result)
+ {
+ switch ($right) {
+ case self::REVIEWFLAGS:
+ case self::CLEARFLAGS:
+ $result = $user->hasRole('moderator');
+ return false; // done processing!
+ }
+
+ return true; // unchanged!
+ }
+
+ /**
+ * Optionally flag profile when a block happens
+ *
+ * We optionally add a flag when a profile has been blocked
+ *
+ * @param User $user User doing the block
+ * @param Profile $profile Profile being blocked
+ *
+ * @return boolean hook result
+ */
+
+ function onEndBlockProfile($user, $profile)
+ {
+ if ($this->flagOnBlock) {
+ User_flag_profile::create($user->id, $profile->id);
+ }
return true;
}
}
diff --git a/plugins/UserFlag/User_flag_profile.php b/plugins/UserFlag/User_flag_profile.php
index 30bd4ae68..658259452 100644
--- a/plugins/UserFlag/User_flag_profile.php
+++ b/plugins/UserFlag/User_flag_profile.php
@@ -1,5 +1,15 @@
<?php
-/*
+/**
+ * Data class for profile flags
+ *
+ * PHP version 5
+ *
+ * @category Data
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009, StatusNet, Inc.
*
@@ -23,6 +33,18 @@ if (!defined('STATUSNET')) {
require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+/**
+ * Data class for profile flags
+ *
+ * A class representing a user flagging another profile for review.
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ */
+
class User_flag_profile extends Memcached_DataObject
{
###START_AUTOCODE
@@ -40,7 +62,14 @@ class User_flag_profile extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
- function table() {
+ /**
+ * return table definition for DB_DataObject
+ *
+ * @return array array of column definitions
+ */
+
+ function table()
+ {
return array(
'profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
@@ -49,15 +78,39 @@ class User_flag_profile extends Memcached_DataObject
);
}
- function keys() {
+ /**
+ * return key definitions for DB_DataObject
+ *
+ * @return array key definitions
+ */
+
+ function keys()
+ {
return array('profile_id' => 'N', 'user_id' => 'N');
}
+ /**
+ * Get a single object with multiple keys
+ *
+ * @param array $kv Map of key-value pairs
+ *
+ * @return User_flag_profile found object or null
+ */
+
function &pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('User_flag_profile', $kv);
}
+ /**
+ * Check if a flag exists for given profile and user
+ *
+ * @param integer $profile_id Profile to check for
+ * @param integer $user_id User to check for
+ *
+ * @return boolean true if exists, else false
+ */
+
static function exists($profile_id, $user_id)
{
$ufp = User_flag_profile::pkeyGet(array('profile_id' => $profile_id,
@@ -65,4 +118,23 @@ class User_flag_profile extends Memcached_DataObject
return !empty($ufp);
}
+
+ static function create($user_id, $profile_id)
+ {
+ $ufp = new User_flag_profile();
+
+ $ufp->profile_id = $profile_id;
+ $ufp->user_id = $user_id;
+ $ufp->created = common_sql_now();
+
+ if (!$ufp->insert()) {
+ $msg = sprintf(_("Couldn't flag profile '%d' for review."),
+ $profile_id);
+ throw new ServerException($msg);
+ }
+
+ $ufp->free();
+
+ return true;
+ }
}
diff --git a/plugins/UserFlag/adminprofileflag.php b/plugins/UserFlag/adminprofileflag.php
index 20b808637..17374927b 100644
--- a/plugins/UserFlag/adminprofileflag.php
+++ b/plugins/UserFlag/adminprofileflag.php
@@ -43,6 +43,9 @@ if (!defined('STATUSNET')) {
class AdminprofileflagAction extends Action
{
+ var $page = null;
+ var $profiles = null;
+
/**
* Take arguments for running
*
@@ -55,6 +58,47 @@ class AdminprofileflagAction extends Action
{
parent::prepare($args);
+ $user = common_current_user();
+
+ // User must be logged in.
+
+ if (!common_logged_in()) {
+ $this->clientError(_('Not logged in.'));
+ return;
+ }
+
+ $user = common_current_user();
+
+ // ...because they're logged in
+
+ assert(!empty($user));
+
+ // It must be a "real" login, not saved cookie login
+
+ if (!common_is_real_login()) {
+ // Cookie theft is too easy; we require automatic
+ // logins to re-authenticate before admining the site
+ common_set_returnto($this->selfUrl());
+ if (Event::handle('RedirectToLogin', array($this, $user))) {
+ common_redirect(common_local_url('login'), 303);
+ }
+ }
+
+ // User must have the right to review flags
+
+ if (!$user->hasRight(UserFlagPlugin::REVIEWFLAGS)) {
+ $this->clientError(_('You cannot review profile flags.'));
+ return false;
+ }
+
+ $this->page = $this->trimmed('page');
+
+ if (empty($this->page)) {
+ $this->page = 1;
+ }
+
+ $this->profiles = $this->getProfiles();
+
return true;
}
@@ -73,7 +117,14 @@ class AdminprofileflagAction extends Action
$this->showPage();
}
- function title() {
+ /**
+ * Title of this page
+ *
+ * @return string Title of the page
+ */
+
+ function title()
+ {
return _('Flagged profiles');
}
@@ -85,13 +136,20 @@ class AdminprofileflagAction extends Action
function showContent()
{
- $profile = $this->getProfiles();
+ $pl = new FlaggedProfileList($this->profiles, $this);
- $pl = new FlaggedProfileList($profile, $this);
+ $cnt = $pl->show();
- $pl->show();
+ $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
+ $this->page, 'adminprofileflag');
}
+ /**
+ * Retrieve this action's profiles
+ *
+ * @return Profile $profile Profile query results
+ */
+
function getProfiles()
{
$ufp = new User_flag_profile();
@@ -103,7 +161,12 @@ class AdminprofileflagAction extends Action
$ufp->whereAdd('cleared is NULL');
$ufp->groupBy('profile_id');
- $ufp->orderBy('flag_count DESC');
+ $ufp->orderBy('flag_count DESC, profile_id DESC');
+
+ $offset = ($this->page-1) * PROFILES_PER_PAGE;
+ $limit = PROFILES_PER_PAGE + 1;
+
+ $ufp->limit($offset, $limit);
$profiles = array();
@@ -122,7 +185,27 @@ class AdminprofileflagAction extends Action
}
}
-class FlaggedProfileList extends ProfileList {
+/**
+ * Specialization of ProfileList to show flagging information
+ *
+ * Most of the hard part is done in FlaggedProfileListItem.
+ *
+ * @category Widget
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ */
+
+class FlaggedProfileList extends ProfileList
+{
+ /**
+ * Factory method for creating new list items
+ *
+ * @param Profile $profile Profile to create an item for
+ *
+ * @return ProfileListItem newly-created item
+ */
function newListItem($profile)
{
@@ -130,11 +213,29 @@ class FlaggedProfileList extends ProfileList {
}
}
+/**
+ * Specialization of ProfileListItem to show flagging information
+ *
+ * @category Widget
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ */
+
class FlaggedProfileListItem extends ProfileListItem
{
- var $user = null;
+ const MAX_FLAGGERS = 5;
+
+ var $user = null;
var $r2args = null;
+ /**
+ * Overload parent's action list with our own moderation-oriented buttons
+ *
+ * @return void
+ */
+
function showActions()
{
$this->user = common_current_user();
@@ -159,6 +260,12 @@ class FlaggedProfileListItem extends ProfileListItem
$this->endActions();
}
+ /**
+ * Show a button to sandbox the profile
+ *
+ * @return void
+ */
+
function showSandboxButton()
{
if ($this->user->hasRight(Right::SANDBOXUSER)) {
@@ -174,6 +281,12 @@ class FlaggedProfileListItem extends ProfileListItem
}
}
+ /**
+ * Show a button to silence the profile
+ *
+ * @return void
+ */
+
function showSilenceButton()
{
if ($this->user->hasRight(Right::SILENCEUSER)) {
@@ -189,6 +302,12 @@ class FlaggedProfileListItem extends ProfileListItem
}
}
+ /**
+ * Show a button to delete user and profile
+ *
+ * @return void
+ */
+
function showDeleteButton()
{
@@ -200,7 +319,92 @@ class FlaggedProfileListItem extends ProfileListItem
}
}
+ /**
+ * Show a button to clear flags
+ *
+ * @return void
+ */
+
function showClearButton()
{
+ if ($this->user->hasRight(UserFlagPlugin::CLEARFLAGS)) {
+ $this->out->elementStart('li', 'entity_clear');
+ $cf = new ClearFlagForm($this->out, $this->profile, $this->r2args);
+ $cf->show();
+ $this->out->elementEnd('li');
+ }
+ }
+
+ /**
+ * Overload parent function to add flaggers list
+ *
+ * @return void
+ */
+
+ function endProfile()
+ {
+ $this->showFlaggersList();
+ parent::endProfile();
+ }
+
+ /**
+ * Show a list of people who've flagged this profile
+ *
+ * @return void
+ */
+
+ function showFlaggersList()
+ {
+ $flaggers = array();
+
+ $ufp = new User_flag_profile();
+
+ $ufp->selectAdd();
+ $ufp->selectAdd('user_id');
+ $ufp->profile_id = $this->profile->id;
+ $ufp->orderBy('created');
+
+ if ($ufp->find()) { // XXX: this should always happen
+ while ($ufp->fetch()) {
+ $user = User::staticGet('id', $ufp->user_id);
+ if (!empty($user)) { // XXX: this would also be unusual
+ $flaggers[] = clone($user);
+ }
+ }
+ }
+
+ $cnt = count($flaggers);
+ $others = 0;
+
+ if ($cnt > self::MAX_FLAGGERS) {
+ $flaggers = array_slice($flaggers, 0, self::MAX_FLAGGERS);
+ $others = $cnt - self::MAX_FLAGGERS;
+ }
+
+ $lnks = array();
+
+ foreach ($flaggers as $flagger) {
+
+ $url = common_local_url('showstream',
+ array('nickname' => $flagger->nickname));
+
+ $lnks[] = XMLStringer::estring('a', array('href' => $url,
+ 'class' => 'flagger'),
+ $flagger->nickname);
+ }
+
+ if ($cnt > 0) {
+ $text = _('Flagged by ');
+
+ $text .= implode(', ', $lnks);
+
+ if ($others > 0) {
+ $text .= sprintf(_(' and %d others'), $others);
+ }
+
+ $this->out->elementStart('p', array('class' => 'flaggers'));
+ $this->out->raw($text);
+ $this->out->elementEnd('p');
+ }
}
}
diff --git a/plugins/UserFlag/clearflag.php b/plugins/UserFlag/clearflag.php
new file mode 100644
index 000000000..bd6732e2d
--- /dev/null
+++ b/plugins/UserFlag/clearflag.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * Clear all flags for a profile
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Action to clear flags for a profile
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ */
+
+class ClearflagAction extends ProfileFormAction
+{
+ /**
+ * Take arguments for running
+ *
+ * @param array $args $_REQUEST args
+ *
+ * @return boolean success flag
+ */
+
+ function prepare($args)
+ {
+ if (!parent::prepare($args)) {
+ return false;
+ }
+
+ $user = common_current_user();
+
+ assert(!empty($user)); // checked above
+ assert(!empty($this->profile)); // checked above
+
+ return true;
+ }
+
+ /**
+ * Handle request
+ *
+ * Overriding the base Action's handle() here to deal check
+ * for Ajax and return an HXR response if necessary
+ *
+ * @param array $args $_REQUEST args; handled in prepare()
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $this->handlePost();
+ if (!$this->boolean('ajax')) {
+ $this->returnToArgs();
+ }
+ }
+ }
+
+ /**
+ * Handle POST
+ *
+ * Executes the actions; deletes all flags
+ *
+ * @return void
+ */
+
+ function handlePost()
+ {
+ $ufp = new User_flag_profile();
+
+ $result = $ufp->query('UPDATE user_flag_profile ' .
+ 'SET cleared = now() ' .
+ 'WHERE cleared is null ' .
+ 'AND profile_id = ' . $this->profile->id);
+
+ if ($result == false) {
+ $msg = sprintf(_("Couldn't clear flags for profile '%s'."),
+ $this->profile->nickname);
+ throw new ServerException($msg);
+ }
+
+ $ufp->free();
+
+ if ($this->boolean('ajax')) {
+ $this->ajaxResults();
+ }
+ }
+
+ /**
+ * Return results in ajax form
+ *
+ * @return void
+ */
+
+ function ajaxResults()
+ {
+ header('Content-Type: text/xml;charset=utf-8');
+ $this->xw->startDocument('1.0', 'UTF-8');
+ $this->elementStart('html');
+ $this->elementStart('head');
+ $this->element('title', null, _('Flags cleared'));
+ $this->elementEnd('head');
+ $this->elementStart('body');
+ $this->element('p', 'cleared', _('Cleared'));
+ $this->elementEnd('body');
+ $this->elementEnd('html');
+ }
+}
diff --git a/plugins/UserFlag/clearflagform.php b/plugins/UserFlag/clearflagform.php
new file mode 100644
index 000000000..5ad6055d3
--- /dev/null
+++ b/plugins/UserFlag/clearflagform.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Form for clearing profile flags
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Form
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+require_once INSTALLDIR.'/lib/form.php';
+
+/**
+ * Form for clearing profile flags
+ *
+ * @category Form
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class ClearFlagForm extends ProfileActionForm
+{
+ /**
+ * class of the form
+ * Action this form provides
+ *
+ * @return string class of the form
+ */
+
+ function formClass()
+ {
+ return 'form_entity_clearflag';
+ }
+
+ /**
+ * Action this form provides
+ *
+ * @return string Name of the action, lowercased.
+ */
+
+ function target()
+ {
+ return 'clearflag';
+ }
+
+ /**
+ * Title of the form
+ *
+ * @return string Title of the form, internationalized
+ */
+
+ function title()
+ {
+ return _('Clear');
+ }
+
+ /**
+ * Description of the form
+ *
+ * @return string description of the form, internationalized
+ */
+
+ function description()
+ {
+ return _('Clear all flags');
+ }
+}
diff --git a/plugins/UserFlag/flagprofile.php b/plugins/UserFlag/flagprofile.php
index 9bce7865b..2d0f0abb9 100644
--- a/plugins/UserFlag/flagprofile.php
+++ b/plugins/UserFlag/flagprofile.php
@@ -63,8 +63,7 @@ class FlagprofileAction extends ProfileFormAction
assert(!empty($this->profile)); // checked above
if (User_flag_profile::exists($this->profile->id,
- $user->id))
- {
+ $user->id)) {
$this->clientError(_('Flag already exists.'));
return false;
}
@@ -72,7 +71,6 @@ class FlagprofileAction extends ProfileFormAction
return true;
}
-
/**
* Handle request
*
@@ -107,25 +105,23 @@ class FlagprofileAction extends ProfileFormAction
assert(!empty($user));
assert(!empty($this->profile));
- $ufp = new User_flag_profile();
-
- $ufp->profile_id = $this->profile->id;
- $ufp->user_id = $user->id;
- $ufp->created = common_sql_now();
+ // throws an exception on error
- if (!$ufp->insert()) {
- throw new ServerException(sprintf(_("Couldn't flag profile '%s' for review."),
- $this->profile->nickname));
- }
-
- $ufp->free();
+ User_flag_profile::create($user->id, $this->profile->id);
if ($this->boolean('ajax')) {
$this->ajaxResults();
}
}
- function ajaxResults() {
+ /**
+ * Return results as AJAX message
+ *
+ * @return void
+ */
+
+ function ajaxResults()
+ {
header('Content-Type: text/xml;charset=utf-8');
$this->xw->startDocument('1.0', 'UTF-8');
$this->elementStart('html');
diff --git a/scripts/setconfig.php b/scripts/setconfig.php
new file mode 100644
index 000000000..b102f99b1
--- /dev/null
+++ b/scripts/setconfig.php
@@ -0,0 +1,98 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$shortoptions = 'd';
+$longoptions = array('delete');
+
+$helptext = <<<END_OF_SETCONFIG_HELP
+setconfig.php [options] [section] [setting] <value>
+With three args, set the setting to the value.
+With two args, just show the setting.
+With -d, delete the setting.
+
+ [section] section to use (required)
+ [setting] setting to use (required)
+ <value> value to set (optional)
+
+ -d --delete delete the setting (no value)
+
+END_OF_SETCONFIG_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+if (count($args) < 2 || count($args) > 3) {
+ show_help();
+ exit(1);
+}
+
+$section = $args[0];
+$setting = $args[1];
+
+if (count($args) == 3) {
+ $value = $args[2];
+} else {
+ $value = null;
+}
+
+try {
+
+ if (have_option('d', 'delete')) { // Delete
+ if (count($args) != 2) {
+ show_help();
+ exit(1);
+ }
+
+ if (have_option('v', 'verbose')) {
+ print "Deleting setting $section/$setting...";
+ }
+
+ $setting = Config::pkeyGet(array('section' => $section,
+ 'setting' => $setting));
+
+ if (empty($setting)) {
+ print "Not found.\n";
+ } else {
+ $result = $setting->delete();
+ if ($result) {
+ print "DONE.\n";
+ } else {
+ print "ERROR.\n";
+ }
+ }
+ } else if (count($args) == 2) { // show
+ if (have_option('v', 'verbose')) {
+ print "$section/$setting = ";
+ }
+ $value = common_config($section, $setting);
+ print "$value\n";
+ } else { // set
+ if (have_option('v', 'verbose')) {
+ print "Setting $section/$setting...";
+ }
+ Config::save($section, $setting, $value);
+ print "DONE.\n";
+ }
+
+} catch (Exception $e) {
+ print $e->getMessage() . "\n";
+ exit(1);
+}