diff --git a/EVENTS.txt b/EVENTS.txt index 0c08a46478..49940e467f 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -1450,3 +1450,9 @@ EndNoticeListPrefill: After pre-filling a list of notices with extra data - &$profiles: Profiles that were pre-filled - $avatarSize: The avatar size for the list +OtherAccountProfiles: Hook to add account profiles to a user account profile block +- $profile: the Profile being shown +- &$others: Modifiable array of profile info arrays. Each one has the following fields: + href: link to the profile + text: text for the profile + image: mini image for the profile diff --git a/lib/accountprofileblock.php b/lib/accountprofileblock.php index 4eca000c9e..f49c73aeab 100644 --- a/lib/accountprofileblock.php +++ b/lib/accountprofileblock.php @@ -94,6 +94,15 @@ class AccountProfileBlock extends ProfileBlock return $this->profile->bio; } + function otherProfiles() + { + $others = array(); + + Event::handle('OtherAccountProfiles', array($this->profile, &$others)); + + return $others; + } + function showTags() { $cur = common_current_user(); diff --git a/lib/defaultprofileblock.php b/lib/defaultprofileblock.php index b8af14ac21..78c7c4a118 100644 --- a/lib/defaultprofileblock.php +++ b/lib/defaultprofileblock.php @@ -86,4 +86,9 @@ class DefaultProfileBlock extends AccountProfileBlock { return null; } + + function otherProfiles() + { + return array(); + } } \ No newline at end of file diff --git a/lib/groupprofileblock.php b/lib/groupprofileblock.php index 58e553a4c2..87ec174dc6 100644 --- a/lib/groupprofileblock.php +++ b/lib/groupprofileblock.php @@ -85,6 +85,11 @@ class GroupProfileBlock extends ProfileBlock return $this->group->description; } + function otherProfiles() + { + return array(); + } + function showActions() { $cur = common_current_user(); diff --git a/lib/profileblock.php b/lib/profileblock.php index eb19a1a9aa..8edc5d9ba1 100644 --- a/lib/profileblock.php +++ b/lib/profileblock.php @@ -61,6 +61,7 @@ abstract class ProfileBlock extends Widget $this->showName(); $this->showLocation(); $this->showHomepage(); + $this->showOtherProfiles(); $this->showDescription(); $this->showTags(); } @@ -133,6 +134,33 @@ abstract class ProfileBlock extends Widget } } + function showOtherProfiles() + { + $otherProfiles = $this->otherProfiles(); + + if (!empty($otherProfiles)) { + + $this->out->elementStart('ul', + array('class' => 'profile_block_otherprofile_list')); + + foreach ($otherProfiles as $otherProfile) { + $this->out->elementStart('li'); + $this->out->elementStart('a', + array('href' => $otherProfile['href'], + 'rel' => 'me', + 'class' => 'profile_block_otherprofile', + 'title' => $otherProfile['text'])); + $this->out->element('img', + array('src' => $otherProfile['image'], + 'class' => 'profile_block_otherprofile_icon')); + $this->out->elementEnd('a'); + $this->out->elementEnd('li'); + } + + $this->out->elementEnd('ul'); + } + } + function avatarSize() { return AVATAR_PROFILE_SIZE; diff --git a/plugins/FacebookBridge/FacebookBridgePlugin.php b/plugins/FacebookBridge/FacebookBridgePlugin.php index bf16da337d..07a149785c 100644 --- a/plugins/FacebookBridge/FacebookBridgePlugin.php +++ b/plugins/FacebookBridge/FacebookBridgePlugin.php @@ -559,6 +559,69 @@ ENDOFSCRIPT; return true; } + /** + * Add links in the user's profile block to their Facebook profile URL. + * + * @param Profile $profile The profile being shown + * @param Array &$links Writeable array of arrays (href, text, image). + * + * @return boolean hook value (true) + */ + + function onOtherAccountProfiles($profile, &$links) + { + $fuser = null; + + $flink = Foreign_link::getByUserID($profile->id, FACEBOOK_SERVICE); + + if (!empty($flink)) { + + $fuser = $this->getFacebookUser($flink->foreign_id); + + if (!empty($fuser)) { + $links[] = array("href" => $fuser->link, + "text" => sprintf(_("%s on Facebook"), $fuser->name), + "image" => $this->path("images/f_logo.png")); + } + } + + return true; + } + + function getFacebookUser($id) { + + $key = Cache::key(sprintf("FacebookBridgePlugin:userdata:%s", $id)); + + $c = Cache::instance(); + + if ($c) { + $obj = $c->get($key); + if ($obj) { + return $obj; + } + } + + $url = sprintf("https://graph.facebook.com/%s", $id); + $client = new HTTPClient(); + $resp = $client->get($url); + + if (!$resp->isOK()) { + return null; + } + + $user = json_decode($resp->getBody()); + + if ($user->error) { + return null; + } + + if ($c) { + $c->set($key, $user); + } + + return $user; + } + /* * Add version info for this plugin * diff --git a/plugins/FacebookBridge/images/f_logo.png b/plugins/FacebookBridge/images/f_logo.png new file mode 100644 index 0000000000..b54e21cadf Binary files /dev/null and b/plugins/FacebookBridge/images/f_logo.png differ diff --git a/plugins/ModLog/ModLog.php b/plugins/ModLog/ModLog.php new file mode 100644 index 0000000000..b2c6546838 --- /dev/null +++ b/plugins/ModLog/ModLog.php @@ -0,0 +1,123 @@ +. + * + * @category Moderation + * @package StatusNet + * @author Evan Prodromou + * @copyright 2012 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Class comment here + * + * @category Category here + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * @see DB_DataObject + */ + +class ModLog extends Managed_DataObject +{ + public $__table = 'mod_log'; // table name + + public $id; // UUID + public $profile_id; // profile id + public $moderator_id; // profile id + public $role; // the role + public $grant; // 1 = grant, 0 = revoke + public $created; // datetime + + /** + * Get an instance by key + * + * @param string $k Key to use to lookup (usually 'user_id' for this class) + * @param mixed $v Value to lookup + * + * @return TagSub object found, or null for no hits + * + */ + function staticGet($k, $v=null) + { + return Managed_DataObject::staticGet('ModLog', $k, $v); + } + + /** + * Get an instance by compound key + * + * @param array $kv array of key-value mappings + * + * @return TagSub object found, or null for no hits + * + */ + function pkeyGet($kv) + { + return Managed_DataObject::pkeyGet('ModLog', $kv); + } + + /** + * The One True Thingy that must be defined and declared. + */ + public static function schemaDef() + { + return array('description' => 'Log of moderation events', + 'fields' => array( + 'id' => array('type' => 'varchar', + 'length' => 36, + 'not null' => true, + 'description' => 'unique event ID'), + 'profile_id' => array('type' => 'int', + 'not null' => true, + 'description' => 'profile getting the role'), + 'moderator_id' => array('type' => 'int', + 'description' => 'profile granting or revoking the role'), + 'role' => array('type' => 'varchar', + 'length' => 32, + 'not null' => true, + 'description' => 'role granted or revoked'), + 'is_grant' => array('type' => 'int', + 'size' => 'tiny', + 'default' => 1, + 'description' => 'Was this a grant or revocation of a role'), + 'created' => array('type' => 'datetime', + 'not null' => true, + 'description' => 'date this record was created') + ), + 'primary key' => array('id'), + 'foreign keys' => array( + 'mod_log_profile_id_fkey' => array('profile', array('profile_id' => 'id')), + 'mod_log_moderator_id_fkey' => array('user', array('user_id' => 'id')) + ), + 'indexes' => array( + 'mod_log_profile_id_created_idx' => array('profile_id', 'created'), + ), + ); + } +} diff --git a/plugins/ModLog/ModLogPlugin.php b/plugins/ModLog/ModLogPlugin.php new file mode 100644 index 0000000000..459df63e82 --- /dev/null +++ b/plugins/ModLog/ModLogPlugin.php @@ -0,0 +1,218 @@ +. + * + * @category Moderation + * @package StatusNet + * @author Evan Prodromou + * @copyright 2012 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Moderation logging + * + * Shows a history of moderation for this user in the sidebar + * + * @category Moderation + * @package StatusNet + * @author Evan Prodromou + * @copyright 2012 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class ModLogPlugin extends Plugin +{ + const VIEWMODLOG = 'ModLogPlugin::VIEWMODLOG'; + + /** + * Database schema setup + * + * We keep a moderation log table + * + * @see Schema + * @see ColumnDef + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onCheckSchema() + { + $schema = Schema::get(); + + $schema->ensureTable('mod_log', ModLog::schemaDef()); + + return true; + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'ModLog': + include_once $dir . '/'.$cls.'.php'; + return false; + default: + return true; + } + } + + function onEndGrantRole($profile, $role) + { + $modlog = new ModLog(); + + $modlog->id = UUID::gen(); + $modlog->profile_id = $profile->id; + + $cur = common_current_user(); + + if (!empty($cur)) { + $modlog->moderator_id = $cur->id; + } + + $modlog->role = $role; + $modlog->is_grant = 1; + $modlog->created = common_sql_now(); + + $modlog->insert(); + + return true; + } + + function onEndRevokeRole($profile, $role) + { + $modlog = new ModLog(); + + $modlog->id = UUID::gen(); + + $modlog->profile_id = $profile->id; + + $cur = common_current_user(); + + if (!empty($cur)) { + $modlog->moderator_id = $cur->id; + } + + $modlog->role = $role; + $modlog->is_grant = 0; + $modlog->created = common_sql_now(); + + $modlog->insert(); + + return true; + } + + function onEndShowSections($action) + { + if ($action->arg('action') != 'showstream') { + return true; + } + + $cur = common_current_user(); + + if (empty($cur) || !$cur->hasRight(self::VIEWMODLOG)) { + return true; + } + + $profile = $action->profile; + + $ml = new ModLog(); + + $ml->profile_id = $profile->id; + $ml->orderBy("created"); + + $cnt = $ml->find(); + + if ($cnt > 0) { + + $action->elementStart('div', array('id' => 'entity_mod_log', + 'class' => 'section')); + + $action->element('h2', null, _('Moderation')); + + $action->elementStart('table'); + + while ($ml->fetch()) { + $action->elementStart('tr'); + $action->element('td', null, strftime('%y-%m-%d', strtotime($ml->created))); + $action->element('td', null, sprintf(($ml->is_grant) ? _('+%s') : _('-%s'), $ml->role)); + $action->elementStart('td'); + if ($ml->moderator_id) { + $mod = Profile::staticGet('id', $ml->moderator_id); + if (empty($mod)) { + $action->text(_('[unknown]')); + } else { + $action->element('a', array('href' => $mod->profileurl, + 'title' => $mod->fullname), + $mod->nickname); + } + } else { + $action->text(_('[unknown]')); + } + $action->elementEnd('td'); + $action->elementEnd('tr'); + } + + $action->elementEnd('table'); + + $action->elementEnd('div'); + } + } + + function onUserRightsCheck($profile, $right, &$result) { + switch ($right) { + case self::VIEWMODLOG: + $result = ($profile->hasRole(Profile_role::MODERATOR) || $profile->hasRole('modhelper')); + return false; + default: + return true; + } + } + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'ModLog', + 'version' => STATUSNET_VERSION, + 'author' => 'Evan Prodromou', + 'homepage' => 'http://status.net/wiki/Plugin:ModLog', + 'description' => + _m('Show the moderation history for a profile in the sidebar')); + return true; + } +} diff --git a/plugins/OStatus/actions/ostatusgroup.php b/plugins/OStatus/actions/ostatusgroup.php index 2c8e318c1f..41e933498b 100644 --- a/plugins/OStatus/actions/ostatusgroup.php +++ b/plugins/OStatus/actions/ostatusgroup.php @@ -146,6 +146,8 @@ class OStatusGroupAction extends OStatusSubAction try { $user->joinGroup($group); } catch (Exception $e) { + common_log(LOG_ERR, "Exception on remote group join: " . $e->getMessage()); + common_log(LOG_ERR, $e->getTraceAsString()); // TRANS: OStatus remote group subscription dialog error. $this->showForm(_m('Remote group join failed!')); return; diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index ed6d6534c0..3d8dab8213 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -20,7 +20,7 @@ * @category Plugin * @package StatusNet * @author Evan Prodromou - * @author Craig Andrews + * @author Craig Andrews * @copyright 2009-2010 StatusNet, Inc. * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 @@ -362,10 +362,9 @@ class OpenIDPlugin extends Plugin require_once dirname(__FILE__) . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; case 'User_openid': - require_once dirname(__FILE__) . '/User_openid.php'; - return false; + case 'User_openid_prefs': case 'User_openid_trustroot': - require_once dirname(__FILE__) . '/User_openid_trustroot.php'; + require_once dirname(__FILE__) . '/' . $cls . '.php'; return false; case 'Auth_OpenID_TeamsExtension': case 'Auth_OpenID_TeamsRequest': @@ -574,6 +573,8 @@ class OpenIDPlugin extends Plugin null, false), new ColumnDef('modified', 'timestamp'))); + $schema->ensureTable('user_openid_prefs', User_openid_prefs::schemaDef()); + /* These are used by JanRain OpenID library */ $schema->ensureTable('oid_associations', @@ -814,4 +815,35 @@ class OpenIDPlugin extends Plugin return true; } + + /** + * Add links in the user's profile block to their OpenID URLs. + * + * @param Profile $profile The profile being shown + * @param Array &$links Writeable array of arrays (href, text, image). + * + * @return boolean hook value (true) + */ + + function onOtherAccountProfiles($profile, &$links) + { + $prefs = User_openid_prefs::staticGet('user_id', $profile->id); + + if (empty($prefs) || !$prefs->hide_profile_link) { + + $oid = new User_openid(); + + $oid->user_id = $profile->id; + + if ($oid->find()) { + while ($oid->fetch()) { + $links[] = array('href' => $oid->display, + 'text' => _('OpenID'), + 'image' => $this->path("icons/openid-16x16.gif")); + } + } + } + + return true; + } } diff --git a/plugins/OpenID/User_openid_prefs.php b/plugins/OpenID/User_openid_prefs.php new file mode 100644 index 0000000000..74a21f685e --- /dev/null +++ b/plugins/OpenID/User_openid_prefs.php @@ -0,0 +1,100 @@ +. + * + * @category OpenID + * @package StatusNet + * @author Evan Prodromou + * @copyright 2012 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Store preferences for OpenID use in StatusNet + * + * @category OpenID + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * @see DB_DataObject + */ + +class User_openid_prefs extends Managed_DataObject +{ + public $__table = 'user_openid_prefs'; // table name + + public $user_id; // The User with the prefs + public $hide_profile_link; // Hide the link on the profile block? + public $created; // datetime + public $modified; // datetime + + /** + * Get an instance by key + * + * This is a utility method to get a single instance with a given key value. + * + * @param string $k Key to use to lookup (usually 'user_id' for this class) + * @param mixed $v Value to lookup + * + * @return TagSub object found, or null for no hits + * + */ + function staticGet($k, $v=null) + { + return Managed_DataObject::staticGet('User_openid_prefs', $k, $v); + } + + /** + * The One True Thingy that must be defined and declared. + */ + + public static function schemaDef() + { + return array( + 'description' => 'Per-user preferences for OpenID display', + 'fields' => array('user_id' => array('type' => 'integer', + 'not null' => true, + 'description' => 'User whose prefs we are saving'), + 'hide_profile_link' => array('type' => 'int', + 'not null' => true, + 'default' => 0, + 'description' => 'Whether to hide profile links from profile block'), + 'created' => array('type' => 'datetime', + 'not null' => true, + 'description' => 'date this record was created'), + 'modified' => array('type' => 'datetime', + 'not null' => true, + 'description' => 'date this record was modified'), + ), + 'primary key' => array('user_id'), + 'foreign keys' => array('user_openid_prefs_user_id_fkey' => array('user', array('user_id' => 'id')), + ), + 'indexes' => array(), + ); + } +} diff --git a/plugins/OpenID/icons/openid-16x16.gif b/plugins/OpenID/icons/openid-16x16.gif new file mode 100644 index 0000000000..e2d8377db0 Binary files /dev/null and b/plugins/OpenID/icons/openid-16x16.gif differ diff --git a/plugins/OpenID/openidsettings.php b/plugins/OpenID/openidsettings.php index f1a62384b4..0dc4930ce1 100644 --- a/plugins/OpenID/openidsettings.php +++ b/plugins/OpenID/openidsettings.php @@ -222,6 +222,22 @@ class OpenidsettingsAction extends SettingsAction // TRANS: Button text to remove an OpenID trustroot. 'value' => _m('BUTTON','Remove'))); $this->elementEnd('fieldset'); + + $prefs = User_openid_prefs::staticGet('user_id', $user->id); + + $this->elementStart('fieldset'); + $this->element('legend', null, _m('LEGEND','Preferences')); + $this->elementStart('ul', 'form_data'); + $this->checkBox('hide_profile_link', "Hide OpenID links from my profile", !empty($prefs) && $prefs->hide_profile_link); + $this->element('input', array('type' => 'submit', + 'id' => 'settings_openid_prefs_save', + 'name' => 'save_prefs', + 'class' => 'submit', + // TRANS: Button text to save OpenID prefs + 'value' => _m('BUTTON','Save'))); + $this->elementEnd('ul'); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); } @@ -258,6 +274,8 @@ class OpenidsettingsAction extends SettingsAction $this->removeOpenid(); } else if($this->arg('remove_trustroots')) { $this->removeTrustroots(); + } else if($this->arg('save_prefs')) { + $this->savePrefs(); } else { // TRANS: Unexpected form validation error. $this->showForm(_m('Something weird happened.')); @@ -326,4 +344,43 @@ class OpenidsettingsAction extends SettingsAction $this->showForm(_m('OpenID removed.'), true); return; } + + /** + * Handles a request to save preferences + * + * Validates input and, if everything is OK, deletes the OpenID. + * Reloads the form with a success or error notification. + * + * @return void + */ + function savePrefs() + { + $cur = common_current_user(); + + if (empty($cur)) { + throw new ClientException(_("Not logged in.")); + } + + $orig = null; + $prefs = User_openid_prefs::staticGet('user_id', $cur->id); + + if (empty($prefs)) { + $prefs = new User_openid_prefs(); + $prefs->user_id = $cur->id; + $prefs->created = common_sql_now(); + } else { + $orig = clone($prefs); + } + + $prefs->hide_profile_link = $this->boolean('hide_profile_link'); + + if (empty($orig)) { + $prefs->insert(); + } else { + $prefs->update($orig); + } + + $this->showForm(_m('OpenID preferences saved.'), true); + return; + } } diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index d733f71c56..25d8a423ca 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -557,4 +557,32 @@ class TwitterBridgePlugin extends Plugin } return true; } + + /** + * Add links in the user's profile block to their Twitter profile URL. + * + * @param Profile $profile The profile being shown + * @param Array &$links Writeable array of arrays (href, text, image). + * + * @return boolean hook value (true) + */ + + function onOtherAccountProfiles($profile, &$links) + { + $fuser = null; + + $flink = Foreign_link::getByUserID($profile->id, TWITTER_SERVICE); + + if (!empty($flink)) { + $fuser = $flink->getForeignUser(); + + if (!empty($fuser)) { + $links[] = array("href" => $fuser->uri, + "text" => sprintf(_("@%s on Twitter"), $fuser->nickname), + "image" => $this->path("icons/twitter-bird-white-on-blue.png")); + } + } + + return true; + } } diff --git a/plugins/TwitterBridge/icons/twitter-bird-white-on-blue.png b/plugins/TwitterBridge/icons/twitter-bird-white-on-blue.png new file mode 100644 index 0000000000..2c42b0823e Binary files /dev/null and b/plugins/TwitterBridge/icons/twitter-bird-white-on-blue.png differ diff --git a/theme/base/css/display.css b/theme/base/css/display.css index d2d07c4cec..483aa15ff7 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -2549,6 +2549,11 @@ display:none; display:none; } +.profile_block_otherprofile_list li { + display: inline; + list-style-type: none; +} + /*end of @media screen, projection, tv*/