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/apistatusesupdate.php20
-rw-r--r--actions/block.php8
-rw-r--r--actions/newnotice.php33
-rw-r--r--actions/profilesettings.php38
-rw-r--r--actions/unblock.php13
-rw-r--r--classes/Notice.php59
-rw-r--r--classes/User.php30
-rw-r--r--classes/User_location_prefs.php48
-rw-r--r--classes/statusnet.ini13
-rw-r--r--db/08to09.sql10
-rw-r--r--db/statusnet.sql9
-rw-r--r--lib/default.php3
-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--[-rwxr-xr-x]scripts/setconfig.php0
23 files changed, 1121 insertions, 97 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/apistatusesupdate.php b/actions/apistatusesupdate.php
index dabbea92f..f594bbf39 100644
--- a/actions/apistatusesupdate.php
+++ b/actions/apistatusesupdate.php
@@ -203,12 +203,6 @@ class ApiStatusesUpdateAction extends ApiAuthAction
}
}
- $location = null;
-
- if (!empty($this->lat) && !empty($this->lon)) {
- $location = Location::fromLatLon($this->lat, $this->lon);
- }
-
$upload = null;
try {
@@ -235,11 +229,15 @@ class ApiStatusesUpdateAction extends ApiAuthAction
$options = array('reply_to' => $reply_to);
- if (!empty($location)) {
- $options['lat'] = $location->lat;
- $options['lon'] = $location->lon;
- $options['location_id'] = $location->location_id;
- $options['location_ns'] = $location->location_ns;
+ if ($this->user->shareLocation()) {
+
+ $locOptions = Notice::locationOptions($this->lat,
+ $this->lon,
+ null,
+ null,
+ $this->user->getProfile());
+
+ $options = array_merge($options, $locOptions);
}
$this->notice =
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/newnotice.php b/actions/newnotice.php
index c014f1781..2d9f0ff79 100644
--- a/actions/newnotice.php
+++ b/actions/newnotice.php
@@ -164,19 +164,6 @@ class NewnoticeAction extends Action
$replyto = 'false';
}
- $lat = $this->trimmed('lat');
- $lon = $this->trimmed('lon');
- $location_id = $this->trimmed('location_id');
- $location_ns = $this->trimmed('location_ns');
-
- if (!empty($lat) && !empty($lon) && empty($location_id)) {
- $location = Location::fromLatLon($lat, $lon);
- if (!empty($location)) {
- $location_id = $location->location_id;
- $location_ns = $location->location_ns;
- }
- }
-
$upload = null;
$upload = MediaFile::fromUpload('attach');
@@ -195,12 +182,20 @@ class NewnoticeAction extends Action
}
}
- $notice = Notice::saveNew($user->id, $content_shortened, 'web',
- array('reply_to' => ($replyto == 'false') ? null : $replyto,
- 'lat' => $lat,
- 'lon' => $lon,
- 'location_id' => $location_id,
- 'location_ns' => $location_ns));
+ $options = array('reply_to' => ($replyto == 'false') ? null : $replyto);
+
+ if ($user->shareLocation()) {
+
+ $locOptions = Notice::locationOptions($this->trimmed('lat'),
+ $this->trimmed('lon'),
+ $this->trimmed('location_id'),
+ $this->trimmed('location_ns'),
+ $user->getProfile());
+
+ $options = array_merge($options, $locOptions);
+ }
+
+ $notice = Notice::saveNew($user->id, $content_shortened, 'web', $options);
if (isset($upload)) {
$upload->attachToNotice($notice);
diff --git a/actions/profilesettings.php b/actions/profilesettings.php
index acfcbcd00..ee236fe62 100644
--- a/actions/profilesettings.php
+++ b/actions/profilesettings.php
@@ -133,6 +133,13 @@ class ProfilesettingsAction extends AccountSettingsAction
($this->arg('location')) ? $this->arg('location') : $profile->location,
_('Where you are, like "City, State (or Region), Country"'));
$this->elementEnd('li');
+ if (common_config('location', 'share') == 'user') {
+ $this->elementStart('li');
+ $this->checkbox('sharelocation', _('Share my current location when posting notices'),
+ ($this->arg('sharelocation')) ?
+ $this->arg('sharelocation') : $user->shareLocation());
+ $this->elementEnd('li');
+ }
Event::handle('EndProfileFormData', array($this));
$this->elementStart('li');
$this->input('tags', _('Tags'),
@@ -318,6 +325,37 @@ class ProfilesettingsAction extends AccountSettingsAction
$profile->profileurl = common_profile_url($nickname);
+ if (common_config('location', 'share') == 'user') {
+
+ $exists = false;
+
+ $prefs = User_location_prefs::staticGet('user_id', $user->id);
+
+ if (empty($prefs)) {
+ $prefs = new User_location_prefs();
+
+ $prefs->user_id = $user->id;
+ $prefs->created = common_sql_now();
+ } else {
+ $exists = true;
+ $orig = clone($prefs);
+ }
+
+ $prefs->share_location = $this->boolean('sharelocation');
+
+ if ($exists) {
+ $result = $prefs->update($orig);
+ } else {
+ $result = $prefs->insert();
+ }
+
+ if ($result === false) {
+ common_log_db_error($prefs, ($exists) ? 'UPDATE' : 'INSERT', __FILE__);
+ $this->serverError(_('Couldn\'t save location prefs.'));
+ return;
+ }
+ }
+
common_debug('Old profile: ' . common_log_objstring($orig_profile), __FILE__);
common_debug('New profile: ' . common_log_objstring($profile), __FILE__);
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/Notice.php b/classes/Notice.php
index 7651d8bd5..9f68c5255 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -289,21 +289,11 @@ class Notice extends Memcached_DataObject
if (!empty($lat) && !empty($lon)) {
$notice->lat = $lat;
$notice->lon = $lon;
+ }
+
+ if (!empty($location_ns) && !empty($location_id)) {
$notice->location_id = $location_id;
$notice->location_ns = $location_ns;
- } else if (!empty($location_ns) && !empty($location_id)) {
- $location = Location::fromId($location_id, $location_ns);
- if (!empty($location)) {
- $notice->lat = $location->lat;
- $notice->lon = $location->lon;
- $notice->location_id = $location_id;
- $notice->location_ns = $location_ns;
- }
- } else {
- $notice->lat = $profile->lat;
- $notice->lon = $profile->lon;
- $notice->location_id = $profile->location_id;
- $notice->location_ns = $profile->location_ns;
}
if (Event::handle('StartNoticeSave', array(&$notice))) {
@@ -1429,4 +1419,47 @@ class Notice extends Memcached_DataObject
return $ids;
}
+
+ function locationOptions($lat, $lon, $location_id, $location_ns, $profile = null)
+ {
+ $options = array();
+
+ if (!empty($location_id) && !empty($location_ns)) {
+
+ $options['location_id'] = $location_id;
+ $options['location_ns'] = $location_ns;
+
+ $location = Location::fromId($location_id, $location_ns);
+
+ if (!empty($location)) {
+ $options['lat'] = $location->lat;
+ $options['lon'] = $location->lon;
+ }
+
+ } else if (!empty($lat) && !empty($lon)) {
+
+ $options['lat'] = $lat;
+ $options['lon'] = $lon;
+
+ $location = Location::fromLatLon($lat, $lon);
+
+ if (!empty($location)) {
+ $options['location_id'] = $location->location_id;
+ $options['location_ns'] = $location->location_ns;
+ }
+ } else if (!empty($profile)) {
+
+ if (isset($profile->lat) && isset($profile->lon)) {
+ $options['lat'] = $profile->lat;
+ $options['lon'] = $profile->lon;
+ }
+
+ if (isset($profile->location_id) && isset($profile->location_ns)) {
+ $options['location_id'] = $profile->location_id;
+ $options['location_ns'] = $profile->location_ns;
+ }
+ }
+
+ return $options;
+ }
}
diff --git a/classes/User.php b/classes/User.php
index 484dc8c82..34151778c 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');
@@ -992,4 +996,28 @@ class User extends Memcached_DataObject
return $ids;
}
+
+ function shareLocation()
+ {
+ $cfg = common_config('location', 'share');
+
+ if ($cfg == 'always') {
+ return true;
+ } else if ($cfg == 'never') {
+ return false;
+ } else { // user
+ $share = true;
+
+ $prefs = User_location_prefs::staticGet('user_id', $this->id);
+
+ if (empty($prefs)) {
+ $share = common_config('location', 'sharedefault');
+ } else {
+ $share = $prefs->share_location;
+ $prefs->free();
+ }
+
+ return $share;
+ }
+ }
}
diff --git a/classes/User_location_prefs.php b/classes/User_location_prefs.php
new file mode 100644
index 000000000..52cb254ba
--- /dev/null
+++ b/classes/User_location_prefs.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Data class for user location preferences
+ *
+ * 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 Data
+ * @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/
+ */
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class User_location_prefs extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'user_location_prefs'; // table name
+ public $user_id; // int(4) primary_key not_null
+ public $share_location; // tinyint(1) default_1
+ public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00
+ public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_location_prefs',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/statusnet.ini b/classes/statusnet.ini
index 2cc37dbfe..35a000aac 100644
--- a/classes/statusnet.ini
+++ b/classes/statusnet.ini
@@ -1,4 +1,3 @@
-
[avatar]
profile_id = 129
original = 17
@@ -564,4 +563,14 @@ modified = 384
[user_openid__keys]
trustroot = K
-user_id = K \ No newline at end of file
+user_id = K
+
+[user_location_prefs]
+user_id = 129
+share_location = 17
+created = 142
+modified = 384
+
+[user_location_prefs__keys]
+user_id = N
+
diff --git a/db/08to09.sql b/db/08to09.sql
index 28ec3ec16..d9c25bc72 100644
--- a/db/08to09.sql
+++ b/db/08to09.sql
@@ -84,3 +84,13 @@ create table login_token (
constraint primary key (user_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table user_location_prefs (
+ user_id integer not null comment 'user who has the preference' references user (id),
+ share_location tinyint default 1 comment 'Whether to share location data',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ constraint primary key (user_id)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
diff --git a/db/statusnet.sql b/db/statusnet.sql
index 6b3c2ca06..94b03df63 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -587,3 +587,12 @@ create table login_token (
constraint primary key (user_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+create table user_location_prefs (
+ user_id integer not null comment 'user who has the preference' references user (id),
+ share_location tinyint default 1 comment 'Whether to share location data',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ constraint primary key (user_id)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
diff --git a/lib/default.php b/lib/default.php
index 42d4623b1..8a70ed3fa 100644
--- a/lib/default.php
+++ b/lib/default.php
@@ -226,7 +226,8 @@ $default =
'message' =>
array('contentlimit' => null),
'location' =>
- array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth
+ array('share' => 'user', // whether to share location; 'always', 'user', 'never'
+ 'sharedefault' => true),
'omb' =>
array('timeout' => 5), // HTTP request timeout in seconds when contacting remote hosts for OMB updates
'logincommand' =>
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
index b102f99b1..b102f99b1 100755..100644
--- a/scripts/setconfig.php
+++ b/scripts/setconfig.php