diff --git a/EVENTS.txt b/EVENTS.txt index 96250f64c7..64e345b692 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 e79dec32d0..c26485f591 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 328f18ab0d..666f308f4c 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 71a34e0879..5fae45dffc 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 c60458cd3c..0f63e1dae0 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 484dc8c82b..6708d95b6a 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 21deb18852..87e3c99a16 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 0000000000..655b0926b7 --- /dev/null +++ b/plugins/Blacklist/BlacklistPlugin.php @@ -0,0 +1,203 @@ +. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @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 + * @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 75dcca4fcb..0fca5f9cf9 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 30bd4ae68d..6582594524 100644 --- a/plugins/UserFlag/User_flag_profile.php +++ b/plugins/UserFlag/User_flag_profile.php @@ -1,5 +1,15 @@ + * @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 + * @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 20b8086377..17374927b3 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 + * @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 + * @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 0000000000..bd6732e2da --- /dev/null +++ b/plugins/UserFlag/clearflag.php @@ -0,0 +1,138 @@ + + * @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 . + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Action to clear flags for a profile + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @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 0000000000..5ad6055d33 --- /dev/null +++ b/plugins/UserFlag/clearflagform.php @@ -0,0 +1,92 @@ +. + * + * @category Form + * @package StatusNet + * @author Evan Prodromou + * @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 + * @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 9bce7865b8..2d0f0abb90 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(); + // throws an exception on error - $ufp->profile_id = $this->profile->id; - $ufp->user_id = $user->id; - $ufp->created = common_sql_now(); - - 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 0000000000..b102f99b1f --- /dev/null +++ b/scripts/setconfig.php @@ -0,0 +1,98 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'd'; +$longoptions = array('delete'); + +$helptext = << +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 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); +}