]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch 'master' into 0.9.x
authorEvan Prodromou <evan@status.net>
Mon, 28 Dec 2009 23:49:14 +0000 (15:49 -0800)
committerEvan Prodromou <evan@status.net>
Mon, 28 Dec 2009 23:49:14 +0000 (15:49 -0800)
15 files changed:
EVENTS.txt
actions/apiblockcreate.php
actions/apiblockdestroy.php
actions/block.php
actions/unblock.php
classes/User.php
js/geometa.js
plugins/Blacklist/BlacklistPlugin.php [new file with mode: 0644]
plugins/UserFlag/UserFlagPlugin.php
plugins/UserFlag/User_flag_profile.php
plugins/UserFlag/adminprofileflag.php
plugins/UserFlag/clearflag.php [new file with mode: 0644]
plugins/UserFlag/clearflagform.php [new file with mode: 0644]
plugins/UserFlag/flagprofile.php
scripts/setconfig.php [new file with mode: 0644]

index 96250f64c784dc665d78d1a0fdadf8653c9fe556..64e345b6926b1800f12933d17453805a2de7dfcc 100644 (file)
@@ -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
index e79dec32d0521ced06d3da89c6a4226ac05e89d0..c26485f591b23196917eb07f3fcc8b65e6ab72d3 100644 (file)
@@ -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);
index 328f18ab0dcf1765d688f807160b314ac4ba3dd2..666f308f4c5a3819ce1c83ff35e9fb9620c4b5d2 100644 (file)
@@ -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);
index 71a34e08793d1bdd0796748fd9c3a4bb5a3528b3..5fae45dffcd46b0140d00e1cb79488abc707a9c1 100644 (file)
@@ -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
         }
     }
 }
-
index c60458cd3ca810486714a1ea184e30d06cd51f45..0f63e1dae05802c4308f52e7f644d63fb81cbec1 100644 (file)
@@ -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;
index 484dc8c82b30a09cd19ebf92366d4a460898a60a..6708d95b6a52fea1dd6ba52abe79783ff5f7e545 100644 (file)
@@ -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');
 
index 21deb18852a2102c225af7091bd2d534d56cf201..87e3c99a16dd0c5734356570df7b1dc63394f893 100644 (file)
@@ -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 (file)
index 0000000..655b092
--- /dev/null
@@ -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;
+    }
+}
index 75dcca4fcb3eaba46b953cca453a39f448c542a3..0fca5f9cf90c22111773e667fe5e1654bb5f63c4 100644 (file)
@@ -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;
     }
 }
index 30bd4ae68de75aee12a7b5271ba653a909a54a08..658259452436aae780d56956ffe9841c7bf79391 100644 (file)
@@ -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;
+    }
 }
index 20b8086377de218535221f933c27d338c440f461..17374927b37ad970d733770083ed13e8cf718fdd 100644 (file)
@@ -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 (file)
index 0000000..bd6732e
--- /dev/null
@@ -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 (file)
index 0000000..5ad6055
--- /dev/null
@@ -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');
+    }
+}
index 9bce7865b8c973114d42b12dacb804473183c25f..2d0f0abb90f80ced2063b2a461e46c6022fdda27 100644 (file)
@@ -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 (file)
index 0000000..b102f99
--- /dev/null
@@ -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);
+}