From 972610019c7f0da5a5b0171db11373f05fa0e0d2 Mon Sep 17 00:00:00 2001 From: obog Date: Wed, 2 Sep 2020 12:36:40 +0300 Subject: [PATCH 1/4] Added topcoder profile link --- Topcoder/class.topcoder.plugin.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Topcoder/class.topcoder.plugin.php b/Topcoder/class.topcoder.plugin.php index 85b8b5a..b2611f0 100644 --- a/Topcoder/class.topcoder.plugin.php +++ b/Topcoder/class.topcoder.plugin.php @@ -509,6 +509,22 @@ function userPhotoUrl($user) { } } +if (!function_exists('topcoderUserUrl')) { + /** + * Return the URL for a topcoder user. + * + * @param array|object $user The user to get the url for. + * @param string $px The prefix to apply before fieldnames. + * @return string The url suitable to be passed into the url() function. + * @since 2.1 + */ + function topcoderUserUrl($user, $px = '') { + $userName = val($px.'Name', $user); + return TopcoderPlugin::getTopcoderProfileUrl(rawurlencode($userName)); + } +} + + if (!function_exists('userAnchor')) { /** * Take a user object, and writes out an anchor of the user's name to the user's profile. @@ -543,7 +559,8 @@ function userAnchor($user, $cssClass = null, $options = null) { $attributes['title'] = $options['title']; } - $userUrl = userUrl($user, $px); + // Go to Topcoder user profile link instead of Vanilla profile link + $userUrl = topcoderUserUrl($user, $px); $topcoderRating = TopcoderPlugin::getTopcoderRating($name); if($topcoderRating != null) { $coderStyles = TopcoderPlugin::getRatingCssClass($topcoderRating); From a47e64c01d4620c9adaa88e309dee6767b0a6b58 Mon Sep 17 00:00:00 2001 From: obog Date: Wed, 2 Sep 2020 18:52:03 +0300 Subject: [PATCH 2/4] issues-24:colouring admin handles in orange --- Topcoder/class.topcoder.plugin.php | 133 +++++++++++++++++++++++++++++ Topcoder/design/topcoder.css | 30 ++++--- 2 files changed, 151 insertions(+), 12 deletions(-) diff --git a/Topcoder/class.topcoder.plugin.php b/Topcoder/class.topcoder.plugin.php index b2611f0..e1d466d 100644 --- a/Topcoder/class.topcoder.plugin.php +++ b/Topcoder/class.topcoder.plugin.php @@ -30,7 +30,9 @@ public function settingsController_topcoder_create($sender) { $cf->initialize([ 'Plugins.Topcoder.BaseApiURL' => ['Control' => 'TextBox', 'Default' => 'https://api.topcoder-dev.com', 'Description' => 'TopCoder Base API URL'], 'Plugins.Topcoder.MemberApiURI' => ['Control' => 'TextBox', 'Default' => '/v3/members', 'Description' => 'Topcoder Member API URI'], + 'Plugins.Topcoder.RolesApiURI' => ['Control' => 'TextBox', 'Default' => '/v3/roles', 'Description' => 'Topcoder Role API URI'], 'Plugins.Topcoder.MemberProfileURL' => ['Control' => 'TextBox', 'Default' => 'https://www.topcoder.com/members', 'Description' => 'Topcoder Member Profile URL'], + ]); $sender->setData('Title', sprintf(t('%s Settings'), 'Topcoder')); @@ -274,6 +276,132 @@ public static function getTopcoderProfile($name) { return null; } + /** + * Get a Topcoder Member Id by Topcoder handle + * @param $name vanilla user name + * @return null|int + */ + public static function getTopcoderId($name) { + $topcoderMembersApiUrl = c('Plugins.Topcoder.BaseApiURL').c('Plugins.Topcoder.MemberApiURI'); + $memberData = @file_get_contents($topcoderMembersApiUrl.'/'.$name); + if($memberData === false) { + // Handle errors (e.g. 404 and others) + return null; + } + $memberResponse = json_decode($memberData); + //Use a photo of Topcoder member if the member with the given user name exists and photoUrl is not null + if($memberResponse->result->status === 200 && $memberResponse->result->content !== null) { + return $memberResponse->result->content->userId; + } + return null; + } + + /** + * Generate machine to machine token from Auth0 + * @return null|String m2m token + */ + public static function getM2MToken() + { + $TOPCODER_AUTH0_CLIENT_ID = getenv('TOPCODER_AUTH0_CLIENT_ID'); + $TOPCODER_AUTH0_CLIENT_SECRET = getenv('TOPCODER_AUTH0_CLIENT_SECRET'); + $TOPCODER_AUTH0_AUDIENCE = getenv('TOPCODER_AUTH0_AUDIENCE'); + $TOPCODER_AUTH0_URL = getenv('TOPCODER_AUTH0_URL'); + $TOPCODER_AUTH0_PROXY_SERVER_URL = getenv('TOPCODER_AUTH0_PROXY_SERVER_URL'); + + $issetM2MParams = (isset($TOPCODER_AUTH0_CLIENT_ID) && + isset($TOPCODER_AUTH0_CLIENT_SECRET) && + isset($TOPCODER_AUTH0_AUDIENCE) && + isset($TOPCODER_AUTH0_URL) && + isset($TOPCODER_AUTH0_PROXY_SERVER_URL)); + if(!$issetM2MParams) { + logMessage(__FILE__,__LINE__,'TopcoderPlugin','getM2MToken()',"M2M Token parameters weren't set"); + throw new InvalidArgumentException("M2M Token parameters weren't set"); + } + + $data = array('grant_type' => 'client_credentials', + 'client_id' => $TOPCODER_AUTH0_CLIENT_ID, + 'client_secret' => $TOPCODER_AUTH0_CLIENT_SECRET, + 'audience' => $TOPCODER_AUTH0_AUDIENCE, + 'auth0_url' => $TOPCODER_AUTH0_URL); + + $m2mOptions = array('http' => array( + 'method' => 'POST', + 'header' => 'Content-type: application/json', + 'content' => json_encode($data) + )); + + $m2mContext = stream_context_create($m2mOptions); + try { + $m2mTokenData = file_get_contents($TOPCODER_AUTH0_PROXY_SERVER_URL, false, $m2mContext); + $m2mTokenResponse = json_decode($m2mTokenData); + return $m2mTokenResponse->access_token; + } catch (Exception $e) { + logMessage(__FILE__,__LINE__,'TopcoderPlugin','getM2MToken',"M2M token wasn't generated:" .$e.message); + return null; + } + + } + + /** + * Get a Topcoder Roles + * + * @param $name Topcoder Handle + * @return null|string array of role objects. Example of role object: + * { + * "id":"3", + * "modifiedBy":null, + * "modifiedAt":null, + * "createdBy":null, + * "createdAt":null, + * "roleName":"Connect Support" + * } + */ + public static function getTopcoderRoles($name) { + $topcoderId = TopcoderPlugin::getTopcoderId($name); + if ($topcoderId) { + $token = TopcoderPlugin::getM2MToken(); + if ($token) { + $topcoderRolesApiUrl = c('Plugins.Topcoder.BaseApiURL') . c('Plugins.Topcoder.RolesApiURI'); + $options = array('http' => array( + 'method' => 'GET', + 'header' => 'Authorization: Bearer ' .$token + )); + $context = stream_context_create($options); + $rolesData = file_get_contents($topcoderRolesApiUrl . '?filter=subjectID%3D' . $topcoderId, false, $context); + if ($rolesData === false) { + // Handle errors (e.g. 404 and others) + logMessage(__FILE__, __LINE__, 'TopcoderPlugin', 'getTopcoderRoles', "Couldn't get Topcoder roles".json_encode($http_response_header)); + return null; + } + + $rolesResponse = json_decode($rolesData); + if ($rolesResponse->result->status === 200 && $rolesResponse->result->content !== null) { + return $rolesResponse->result->content; + } + } + } + return null; + } + + /** + * Check if User has a Topcoder admin role + * + * @param $name username + * @return boolean true if User has Topcoder admin role + */ + public static function hasTopcoderAdminRole($name) { + $roles = TopcoderPlugin::getTopcoderRoles($name); + if($roles) { + $adminRoleNames = array("admin", "administrator"); + foreach ($roles as $role) { + if (in_array(strtolower($role->roleName), $adminRoleNames)) { + return true; + } + } + } + return false; + } + /** * Get a photo url from Topcoder Member Profile * @param $name vanilla user name @@ -561,12 +689,17 @@ function userAnchor($user, $cssClass = null, $options = null) { // Go to Topcoder user profile link instead of Vanilla profile link $userUrl = topcoderUserUrl($user, $px); + $topcoderRating = TopcoderPlugin::getTopcoderRating($name); if($topcoderRating != null) { $coderStyles = TopcoderPlugin::getRatingCssClass($topcoderRating); $attributes['class'] = $attributes['class'].' '.$coderStyles ; } + $isTopcoderAdmin = TopcoderPlugin::hasTopcoderAdminRole($name); + if($isTopcoderAdmin) { + $attributes['class'] = $attributes['class'].' '. 'topcoderAdmin' ; + } return ''.$text.''; } } diff --git a/Topcoder/design/topcoder.css b/Topcoder/design/topcoder.css index 4699411..1b2fa1d 100644 --- a/Topcoder/design/topcoder.css +++ b/Topcoder/design/topcoder.css @@ -1,22 +1,22 @@ -.coderTextRed, .coderTextYellow, .coderTextBlue, .coderTextGreen, .coderTextGray, .coderTextOrange { +.coderTextRed, .coderTextYellow, .coderTextBlue, .coderTextGreen, .coderTextGray, .coderTextOrange, .topcoderAdmin { font-weight: bold; background-color: transparent; } -.coderTextRed:link, .coderTextYellow:link, .coderTextBlue:link, .coderTextGreen:link, .coderTextGray:link, .coderTextOrange:link, .coderTextWhite:link, .coderTextBlack:link { +.coderTextRed:link, .coderTextYellow:link, .coderTextBlue:link, .coderTextGreen:link, .coderTextGray:link, .coderTextOrange:link, .coderTextWhite:link, .coderTextBlack:link , .topcoderAdmin:link { text-decoration: none; } -.coderTextRed:visited, .coderTextYellow:visited, .coderTextBlue:visited, .coderTextGreen:visited, .coderTextGray:visited, .coderTextOrange:visited, .coderTextWhite:visited, .coderTextBlack:visited { +.coderTextRed:visited, .coderTextYellow:visited, .coderTextBlue:visited, .coderTextGreen:visited, .coderTextGray:visited, .coderTextOrange:visited, .coderTextWhite:visited, .coderTextBlack:visited, .topcoderAdmin:visited { text-decoration: none; } -a.coderTextRed:hover, a.coderTextYellow:hover, a.coderTextBlue:hover, a.coderTextGreen:hover, a.coderTextGray:hover, a.coderTextOrange:hover, a.coderTextWhite:hover, a.coderTextBlack:hover { +a.coderTextRed:hover, a.coderTextYellow:hover, a.coderTextBlue:hover, a.coderTextGreen:hover, a.coderTextGray:hover, a.coderTextOrange:hover, a.coderTextWhite:hover, a.coderTextBlack:hover, a.topcoderAdmin:hover { text-decoration: underline; } -.coderTextRed:active, .coderTextYellow:active, .coderTextBlue:active, .coderTextGreen:active, .coderTextGray:active, .coderTextOrange:active, .coderTextWhite:active, .coderTextBlack:active { +.coderTextRed:active, .coderTextYellow:active, .coderTextBlue:active, .coderTextGreen:active, .coderTextGray:active, .coderTextOrange:active, .coderTextWhite:active, .coderTextBlack:active, a.topcoderAdmin:active { text-decoration: underline; } /* Orange */ -.coderTextOrange, .coderTextOrange:link, .coderTextOrange:visited, .coderTextOrange:hover, .coderTextOrange:active { color: #FF9900; } +.coderTextOrange, .coderTextOrange:link, .coderTextOrange:visited, .coderTextOrange:hover, .coderTextOrange:active { color: #ff9900; } /* Red */ .coderTextRed, .coderTextRed:link, .coderTextRed:visited, .coderTextRed:hover, .coderTextRed:active { color: #EE0000; } /* Yellow */ @@ -31,20 +31,23 @@ a.coderTextRed:hover, a.coderTextYellow:hover, a.coderTextBlue:hover, a.coderTex .coderTextWhite, .coderTextWhite:link, .coderTextWhite:visited, .coderTextWhite:hover, .coderTextWhite:active { color: #FFFFFF; } /* Black */ .coderTextBlack, .coderTextBlack:link, .coderTextBlack:visited, .coderTextBlack:hover, .coderTextBlack:active { color: #000000; } -.coderTextRed, .coderTextYellow, .coderTextBlue, .coderTextGreen, .coderTextGray, .coderTextOrange { +/* Topcoder Admin */ +.topcoderAdmin, .topcoderAdmin:link, .topcoderAdmin:visited, .topcoderAdmin:hover, .topcoderAdmin:active { color: #ff5100; } + +.coderTextRed, .coderTextYellow, .coderTextBlue, .coderTextGreen, .coderTextGray, .coderTextOrange, .topcoderAdmin { font-weight: bold !important; background-color: transparent !important; } -.coderTextRed:link, .coderTextYellow:link, .coderTextBlue:link, .coderTextGreen:link, .coderTextGray:link, .coderTextOrange:link, .coderTextWhite:link, .coderTextBlack:link { +.coderTextRed:link, .coderTextYellow:link, .coderTextBlue:link, .coderTextGreen:link, .coderTextGray:link, .coderTextOrange:link, .coderTextWhite:link, .coderTextBlack:link, .topcoderAdmin:link { text-decoration: none !important; } -.coderTextRed:visited, .coderTextYellow:visited, .coderTextBlue:visited, .coderTextGreen:visited, .coderTextGray:visited, .coderTextOrange:visited, .coderTextWhite:visited, .coderTextBlack:visited { +.coderTextRed:visited, .coderTextYellow:visited, .coderTextBlue:visited, .coderTextGreen:visited, .coderTextGray:visited, .coderTextOrange:visited, .coderTextWhite:visited, .coderTextBlack:visited, .topcoderAdmin:visited { text-decoration: none !important; } -.coderTextRed:hover, .coderTextYellow:hover, .coderTextBlue:hover, .coderTextGreen:hover, .coderTextGray:hover, .coderTextOrange:hover, .coderTextWhite:hover, .coderTextBlack:hover { +.coderTextRed:hover, .coderTextYellow:hover, .coderTextBlue:hover, .coderTextGreen:hover, .coderTextGray:hover, .coderTextOrange:hover, .coderTextWhite:hover, .coderTextBlack:hover, .topcoderAdmin:hover { text-decoration: underline !important; } -.coderTextRed:active, .coderTextYellow:active, .coderTextBlue:active, .coderTextGreen:active, .coderTextGray:active, .coderTextOrange:active, .coderTextWhite:active, .coderTextBlack:active { +.coderTextRed:active, .coderTextYellow:active, .coderTextBlue:active, .coderTextGreen:active, .coderTextGray:active, .coderTextOrange:active, .coderTextWhite:active, .coderTextBlack:active, .topcoderAdmin:active { text-decoration: underline !important; } @@ -63,4 +66,7 @@ a.coderTextRed:hover, a.coderTextYellow:hover, a.coderTextBlue:hover, a.coderTex /* White */ .coderTextWhite, .coderTextWhite:link, .coderTextWhite:visited, .coderTextWhite:hover, .coderTextWhite:active { color: #FFFFFF !important; } /* Black */ -.coderTextBlack, .coderTextBlack:link, .coderTextBlack:visited, .coderTextBlack:hover, .coderTextBlack:active { color: #000000 !important; } \ No newline at end of file +.coderTextBlack, .coderTextBlack:link, .coderTextBlack:visited, .coderTextBlack:hover, .coderTextBlack:active { color: #000000 !important; } + +/* Topcoder Admin */ +.topcoderAdmin, .topcoderAdmin:link, .topcoderAdmin:visited, .topcoderAdmin:hover, .topcoderAdmin:active { color: #ff5100 !important; } From ad2545bf487c2dc35aa341e5a85304f0fe51dddc Mon Sep 17 00:00:00 2001 From: obog Date: Wed, 2 Sep 2020 19:22:59 +0300 Subject: [PATCH 3/4] sync plugin names --- Topcoder/class.topcoder.plugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Topcoder/class.topcoder.plugin.php b/Topcoder/class.topcoder.plugin.php index e1d466d..441e116 100644 --- a/Topcoder/class.topcoder.plugin.php +++ b/Topcoder/class.topcoder.plugin.php @@ -30,7 +30,7 @@ public function settingsController_topcoder_create($sender) { $cf->initialize([ 'Plugins.Topcoder.BaseApiURL' => ['Control' => 'TextBox', 'Default' => 'https://api.topcoder-dev.com', 'Description' => 'TopCoder Base API URL'], 'Plugins.Topcoder.MemberApiURI' => ['Control' => 'TextBox', 'Default' => '/v3/members', 'Description' => 'Topcoder Member API URI'], - 'Plugins.Topcoder.RolesApiURI' => ['Control' => 'TextBox', 'Default' => '/v3/roles', 'Description' => 'Topcoder Role API URI'], + 'Plugins.Topcoder.RoleApiURI' => ['Control' => 'TextBox', 'Default' => '/v3/roles', 'Description' => 'Topcoder Role API URI'], 'Plugins.Topcoder.MemberProfileURL' => ['Control' => 'TextBox', 'Default' => 'https://www.topcoder.com/members', 'Description' => 'Topcoder Member Profile URL'], ]); From 5c916156df72d1a48235c810e3c7b86208cb1c9e Mon Sep 17 00:00:00 2001 From: obog Date: Wed, 2 Sep 2020 19:23:42 +0300 Subject: [PATCH 4/4] sync plugin names --- Topcoder/class.topcoder.plugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Topcoder/class.topcoder.plugin.php b/Topcoder/class.topcoder.plugin.php index 441e116..a7dbcb3 100644 --- a/Topcoder/class.topcoder.plugin.php +++ b/Topcoder/class.topcoder.plugin.php @@ -361,7 +361,7 @@ public static function getTopcoderRoles($name) { if ($topcoderId) { $token = TopcoderPlugin::getM2MToken(); if ($token) { - $topcoderRolesApiUrl = c('Plugins.Topcoder.BaseApiURL') . c('Plugins.Topcoder.RolesApiURI'); + $topcoderRolesApiUrl = c('Plugins.Topcoder.BaseApiURL') . c('Plugins.Topcoder.RoleApiURI'); $options = array('http' => array( 'method' => 'GET', 'header' => 'Authorization: Bearer ' .$token