diff --git a/DebugPlugin/controllers/api/PermissionApiController.php b/DebugPlugin/controllers/api/PermissionApiController.php index dd47f1d..7090794 100644 --- a/DebugPlugin/controllers/api/PermissionApiController.php +++ b/DebugPlugin/controllers/api/PermissionApiController.php @@ -28,6 +28,7 @@ public function index($userID) { $userPermissions = Gdn::userModel()->getPermissions($userID); $data = [ 'userPermissions' => $userPermissions, + 'userPermissionArray' => $userPermissions->getPermissions() ]; return $data; } diff --git a/Topcoder/class.topcoder.plugin.php b/Topcoder/class.topcoder.plugin.php index c658e27..af258f1 100644 --- a/Topcoder/class.topcoder.plugin.php +++ b/Topcoder/class.topcoder.plugin.php @@ -292,26 +292,40 @@ public function gdn_auth_startAuthenticator_handler() { } self::log('TopcoderPlugin: gdn_auth_startAuthenticator_handler', ['Path' => Gdn::request()->path()]); - - // Ignore EntryController endpoints and ApiController endpoints. + // Ignore EntryController endpoints // AccessToken for /api will be checked in class.hooks.php - if (stringBeginsWith(Gdn::request()->getPath(), '/api/') || stringBeginsWith(Gdn::request()->getPath(), '/entry/')) { + if (stringBeginsWith(Gdn::request()->getPath(), '/entry/')) { return; } $cookieName = c('Plugins.Topcoder.SSO.CookieName'); - self::log('Cookie Name', ['value' => $cookieName]); + self::log('Cookie Name', ['value' => $cookieName]); $cookiesToken = isset($_COOKIE[$cookieName]) ? $_COOKIE[$cookieName] : null; - $headersToken = $this->getBearerToken(); - $accessToken = $headersToken ? $headersToken : $cookiesToken; if ($cookiesToken) { - self::log('Token from Cookies', ['value' => $cookiesToken]); + self::log('Token from Cookies', ['value' => $cookiesToken]); } if ($headersToken) { - self::log('Token from Headers', ['value' => '' . $headersToken]); + self::log('Token from Headers', ['value' => '' . $headersToken]); + } + + $accessToken = null; + + if(stringBeginsWith(Gdn::request()->getPath(), '/api/')) { + if(stringBeginsWith(Gdn::request()->getPath(), '/api/v2/users/me-preferences') || + stringBeginsWith(Gdn::request()->getPath(), '/api/v2/discussions/bookmarked') || + (stringBeginsWith(Gdn::request()->getPath(), '/api/v2/discussions/') + && stringEndsWith(Gdn::request()->getPath(), '/bookmark'))) { + $accessToken = $headersToken; + } else { + // Ignore other ApiController endpoints. + // AccessToken for /api will be checked in class.hooks.php + return; + } + } else { + $accessToken = $cookiesToken; } if ($accessToken) { @@ -2122,6 +2136,163 @@ public function discussionController_announce_create($sender, $discussionID = ' $sender->render('Blank', 'Utility', 'Dashboard'); } + + /** + * Edit user's preferences (mostly notification settings). + * + * @param mixed $userReference Unique identifier, possibly username or ID. + * @param string $username . + * @param int $userID Unique identifier. + */ + public function profileController_preferences_create($sender, $userReference = '', $username = '', $userID = '') { + $sender->addJsFile('profile.js'); + $session = Gdn::session(); + $sender->permission('Garden.SignIn.Allow'); + + // Get user data + $sender->getUserInfo($userReference, $username, $userID, true); + $userPrefs = dbdecode($sender->User->Preferences); + if ($sender->User->UserID != $session->UserID) { + $sender->permission(['Garden.Users.Edit', 'Moderation.Profiles.Edit'], false); + } + + if (!is_array($userPrefs)) { + $userPrefs = []; + } + + $metaPrefs = [];// UserModel::getMeta($this->User->UserID, 'Preferences.%', 'Preferences.'); + + // Define the preferences to be managed + $notifications = []; + + if (c('Garden.Profile.ShowActivities', true)) { + $notifications = [ + 'Email.WallComment' => t('Notify me when people write on my wall.'), + 'Email.ActivityComment' => t('Notify me when people reply to my wall comments.'), + 'Popup.WallComment' => t('Notify me when people write on my wall.'), + 'Popup.ActivityComment' => t('Notify me when people reply to my wall comments.') + ]; + } + + $sender->Preferences = ['Notifications' => $notifications]; + + // Allow email notification of applicants (if they have permission & are using approval registration) + if (checkPermission('Garden.Users.Approve') && c('Garden.Registration.Method') == 'Approval') { + $sender->Preferences['Notifications']['Email.Applicant'] = [t('NotifyApplicant', 'Notify me when anyone applies for membership.'), 'Meta']; + } + + $sender->fireEvent('AfterPreferencesDefined'); + + // Loop through the preferences looking for duplicates, and merge into a single row + $sender->PreferenceGroups = []; + $sender->PreferenceTypes = []; + foreach ($sender->Preferences as $preferenceGroup => $preferences) { + $sender->PreferenceGroups[$preferenceGroup] = []; + $sender->PreferenceTypes[$preferenceGroup] = []; + foreach ($preferences as $name => $description) { + $location = 'Prefs'; + if (is_array($description)) { + list($description, $location) = $description; + } + + $nameParts = explode('.', $name); + $prefType = val('0', $nameParts); + $subName = val('1', $nameParts); + if ($subName != false) { + // Save an array of all the different types for this group + if (!in_array($prefType, $sender->PreferenceTypes[$preferenceGroup])) { + $sender->PreferenceTypes[$preferenceGroup][] = $prefType; + } + + // Store all the different subnames for the group + if (!array_key_exists($subName, $sender->PreferenceGroups[$preferenceGroup])) { + $sender->PreferenceGroups[$preferenceGroup][$subName] = [$name]; + } else { + $sender->PreferenceGroups[$preferenceGroup][$subName][] = $name; + } + } else { + $sender->PreferenceGroups[$preferenceGroup][$name] = [$name]; + } + } + } + + // Loop the preferences, setting defaults from the configuration. + $currentPrefs = []; + foreach ($sender->Preferences as $prefGroup => $prefs) { + foreach ($prefs as $pref => $desc) { + $location = 'Prefs'; + if (is_array($desc)) { + list($desc, $location) = $desc; + } + + if ($location == 'Meta') { + $currentPrefs[$pref] = val($pref, $metaPrefs, false); + } else { + $currentPrefs[$pref] = val($pref, $userPrefs, c('Preferences.'.$pref, '0')); + } + + unset($metaPrefs[$pref]); + } + } + $currentPrefs = array_merge($currentPrefs, $metaPrefs); + $currentPrefs = array_map('intval', $currentPrefs); + $sender->setData('Preferences', $currentPrefs); + + if (UserModel::noEmail()) { + $sender->PreferenceGroups = self::_removeEmailPreferences($sender->PreferenceGroups); + $sender->PreferenceTypes = self::_removeEmailPreferences($sender->PreferenceTypes); + $sender->setData('NoEmail', true); + } + + $sender->setData('PreferenceGroups', $sender->PreferenceGroups); + $sender->setData('PreferenceTypes', $sender->PreferenceTypes); + $sender->setData('PreferenceList', $sender->Preferences); + + if ($sender->Form->authenticatedPostBack()) { + // Get, assign, and save the preferences. + $newMetaPrefs = []; + foreach ($sender->Preferences as $prefGroup => $prefs) { + foreach ($prefs as $pref => $desc) { + $location = 'Prefs'; + if (is_array($desc)) { + list($desc, $location) = $desc; + } + + $value = $sender->Form->getValue($pref, null); + if (is_null($value)) { + continue; + } + + if ($location == 'Meta') { + // $newMetaPrefs[$pref] = $value ? $value : null; + // if ($value) { + // $userPrefs[$pref] = $value; // dup for notifications code. + // } + } else { + if (!$currentPrefs[$pref] && !$value) { + unset($userPrefs[$pref]); // save some space + } else { + $userPrefs[$pref] = $value; + } + } + } + } + + $sender->UserModel->savePreference($sender->User->UserID, $userPrefs); + // UserModel::setMeta($this->User->UserID, $newMetaPrefs, 'Preferences.'); + $sender->setData('Preferences', array_merge($sender->data('Preferences', []), $userPrefs, $newMetaPrefs)); + + if (count($sender->Form->errors() == 0)) { + $sender->informMessage(sprite('Check', 'InformSprite').t('Your preferences have been saved.'), 'Dismissable AutoDismiss HasSprite'); + } + } else { + $sender->Form->setData($currentPrefs); + } + + $sender->title(t('Notification Preferences')); + $sender->_setBreadcrumbs($sender->data('Title'), $sender->canonicalUrl()); + $sender->render(); + } } if(!function_exists('topcoderRatingCssClass')) { diff --git a/Topcoder/controllers/api/UsersApiController.php b/Topcoder/controllers/api/UsersApiController.php new file mode 100644 index 0000000..4afb181 --- /dev/null +++ b/Topcoder/controllers/api/UsersApiController.php @@ -0,0 +1,233 @@ +userMetaModel = $userMetaModel; + $this->categoryModel = $categoryModel; + } + + + public function get_mePreferences() { + $this->permission('Garden.SignIn.Allow'); + $user = Gdn::userModel()->getID(Gdn::session()->UserID, DATASET_TYPE_ARRAY); + $preferences = $this->userMetaModel->getUserMeta($user['UserID'], 'Preferences.%', 'Preferences.'); + + $metaKeyCategories = []; + foreach ($preferences as $metaKey =>$value) { + $items = explode('.', $metaKey); + $key = $items[1].'.'.$items[2]; + $categoryID = (int) $items[3]; + $metaKeyCategories[$categoryID][$key] = (int)$value; + } + + $categories = []; + foreach ($metaKeyCategories as $categoryID =>$value) { + array_push($categories , array_merge(['CategoryID' => $categoryID], $value)); + + } + // Default Vanilla Settings + $userPreferences = [ + 'GeneralPreferences' => [ + 'Email.DiscussionComment' => (int) val('Email.DiscussionComment', $user['Preferences'], c('Preferences.Email.DiscussionComment')), + 'Email.BookmarkComment' => (int) val('Email.BookmarkComment', $user['Preferences'], c('Preferences.Email.BookmarkComment')), + 'Email.Mention' => (int) val('Email.Mention', $user['Preferences'], c('Preferences.Email.Mention')), + 'Email.ParticipateComment' => (int) val('Email.ParticipateComment', $user['Preferences'], c('Preferences.Email.ParticipateComment')), + 'Popup.DiscussionComment' => (int) val('Popup.DiscussionComment', $user['Preferences'], c('Preferences.Popup.DiscussionComment')), + 'Popup.BookmarkComment' => (int) val('Popup.BookmarkComment', $user['Preferences'], c('Preferences.Popup.BookmarkComment')), + 'Popup.Mention' => (int) val('Popup.Mention', $user['Preferences'], c('Preferences.Popup.Mention')), + 'Popup.ParticipateComment' => (int) val('Popup.ParticipateComment', $user['Preferences'], c('Preferences.Popup.ParticipateComment'))], + 'CategoryPreferences' => $categories + ]; + + return $userPreferences; + } + + public function patch_mePreferences(array $body) { + + $this->permission('Garden.SignIn.Allow'); + $in = $this->schema($this->notificationPreferencesPatchSchema(), 'in') + ->setDescription('Update User Notification Preferences.'); + $body = $in->validate($body); + $generalPreferences = $body['GeneralPreferences']; + + $preferences = []; + if(isset($generalPreferences['Email.DiscussionComment'])){ + $preferences['Email.DiscussionComment'] = (int)$generalPreferences['Email.DiscussionComment']; + } + if(isset($generalPreferences['Email.BookmarkComment'])){ + $preferences['Email.BookmarkComment'] = (int)$generalPreferences['Email.BookmarkComment']; + } + + if(isset($generalPreferences['Email.Mention'])){ + $preferences['Email.Mention'] = (int)$generalPreferences['Email.Mention']; + } + + if(isset($generalPreferences['Email.ParticipateComment'])){ + $preferences['Email.ParticipateComment'] = (int)$generalPreferences['Email.ParticipateComment']; + } + + if(isset($generalPreferences['Popup.DiscussionComment'])){ + $preferences['Popup.DiscussionComment'] = (int) $generalPreferences['Popup.DiscussionComment']; + } + if(isset($generalPreferences['Popup.BookmarkComment'])){ + $preferences['Popup.BookmarkComment'] = (int) $generalPreferences['Popup.BookmarkComment']; + } + + if(isset($generalPreferences['Popup.Mention'])){ + $preferences['Popup.Mention'] = (int)$generalPreferences['Popup.Mention']; + } + + if(isset($generalPreferences['Popup.ParticipateComment'])){ + $preferences['Popup.ParticipateComment'] = (int)$generalPreferences['Popup.ParticipateComment']; + } + + if(count($preferences) > 0 ) { + Gdn::userModel()->savePreference(Gdn::session()->UserID, $generalPreferences); + } + + if(isset($body['CategoryPreferences'])){ + $metaData = []; + foreach ($body['CategoryPreferences'] as $entity) { + $categoryID= val('CategoryID', $entity); + $category = CategoryModel::categories($categoryID); + if (empty($category)) { + throw new NotFoundException('Category'); + } + + if (!CategoryModel::checkPermission($categoryID,'Vanilla.Discussions.View', true)) { + $this->permission('Vanilla.Discussions.View', true, 'Category', $categoryID); + } + if(isset($entity['Email.NewDiscussion'])) { + $metaKey = 'Preferences.Email.NewDiscussion.'.$categoryID; + $metaData[$metaKey] =(int)$entity['Email.NewDiscussion']; + } + + if(isset($entity['Email.NewComment'])) { + $metaKey = 'Preferences.Email.NewComment.'.$categoryID; + $metaData[$metaKey] = (int)$entity['Email.NewComment']; + } + + if(isset($entity['Popup.NewDiscussion'])) { + $metaKey = 'Preferences.Popup.NewDiscussion.'.$categoryID; + $metaData[$metaKey] = (int)$entity['Popup.NewDiscussion']; + } + + if(isset($entity['Popup.NewComment'])) { + $metaKey = 'Preferences.Popup.NewComment.'.$categoryID; + $metaData[$metaKey] =(int)$entity['Popup.NewComment']; + } + } + + foreach ( $metaData as $key =>$value) { + $this->userMetaModel->setUserMeta(Gdn::session()->UserID, $key, $value > 0?$value: null); + } + } + + return $this->get_mePreferences(); + } + + protected function notificationPreferencesPatchSchema() { + $schema = [ + 'GeneralPreferences?'=> [ + 'type' => 'object', + 'properties' => [ + 'Email.DiscussionComment:i' => [ + 'minimum' => 0, + 'maximum' => 1, + 'description' => 'Notify me when people comment on my discussions', + ], + 'Email.BookmarkComment:i' => [ + 'minimum' => 0, + 'maximum' => 1, + 'description' => 'Notify me when people comment on my bookmarked discussions' + ], + 'Email.Mention:i' => [ + 'minimum' => 0, + 'maximum' => 1, + 'description' => 'Notify me when people mention me' + ], + 'Email.ParticipateComment:i' => [ + 'minimum' => 0, + 'maximum' => 1, + 'description' => 'Notify me when people comment on discussions I\'ve participated in' + ], + 'Popup.DiscussionComment:i' => [ + 'minimum' => 0, + 'maximum' => 1, + 'description' => 'Notify me when people comment on my discussions', + ], + 'Popup.BookmarkComment:i' => [ + 'minimum' => 0, + 'maximum' => 1, + 'description' => 'Notify me when people comment on my bookmarked discussions' + ], + 'Popup.Mention:i' => [ + 'minimum' => 0, + 'maximum' => 1, + 'description' => 'Notify me when people mention me' + ], + 'Popup.ParticipateComment:i' => [ + 'minimum' => 0, + 'maximum' => 1, + 'description' => 'Notify me when people comment on discussions I\'ve participated in' + ], + ] + ], + 'CategoryPreferences?' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'CategoryID' => [ + 'type' => 'integer', + 'minimum' => 1, + ], + 'Email.NewDiscussion' => [ + 'type' => 'integer', + 'minimum' => 0, + 'maximum' => 2, + ], + 'Email.NewComment' => [ + 'type' => 'integer', + 'minimum' => 0, + 'maximum' => 2, + ], + 'Popup.NewDiscussion' => [ + 'type' => 'integer', + 'minimum' => 0, + 'maximum' => 2, + ], + 'Popup.NewComment' => [ + 'type' => 'integer', + 'minimum' => 0, + 'maximum' => 2, + ] + ] + ] + ] + ]; + return $schema; + } + +} \ No newline at end of file diff --git a/Topcoder/design/topcoder.css b/Topcoder/design/topcoder.css index 55d950b..83cced0 100644 --- a/Topcoder/design/topcoder.css +++ b/Topcoder/design/topcoder.css @@ -59,7 +59,7 @@ a.coderRatingRed:hover, a.coderRatingYellow:hover, a.coderRatingBlue:hover, a.co /* Gray */ .coderRatingGrey, .coderRatingGrey:link, .coderRatingGrey:visited, .coderRatingGrey:hover, .coderRatingGrey:active { color: #999999 !important; } /* Purple, no rating */ -.coderRatingNone, .coderRatingNone:link, .coderRatingNone:visited, .coderRatingNone:hover, .coderRatingNone:active { color: #000000; !important; } +.coderRatingNone, .coderRatingNone:link, .coderRatingNone:visited, .coderRatingNone:hover, .coderRatingNone:active { color: #000000 !important; } /* Topcoder Admin */ .topcoderAdmin, .topcoderAdmin:link, .topcoderAdmin:visited, .topcoderAdmin:hover, .topcoderAdmin:active { color: #ff9900 !important; } diff --git a/Topcoder/openapi/users.yml b/Topcoder/openapi/users.yml new file mode 100644 index 0000000..d065ecf --- /dev/null +++ b/Topcoder/openapi/users.yml @@ -0,0 +1,140 @@ +openapi: 3.0.2 +info: + title: User Preferences API + description: Topcoder API + version: 1.0.0 +paths: + '/users/me-preferences': + get: + responses: + '200': + content: + 'application/json': + schema: + $ref: '#/components/schemas/UserPreferences' + description: Success + tags: + - Users + summary: Get notification preferences for current user. + patch: + responses: + '200': + content: + 'application/json': + schema: + $ref: '#/components/schemas/UserPreferences' + description: Success + tags: + - Users + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserPreferencesPatch' + required: true + summary: Update notification preferences for current user. +components: + schemas: + GeneralPreferences: + type: object + required: + - Email.DiscussionComment + - Email.BookmarkComment + - Email.Mention + - Email.ParticipateComment + - Popup.DiscussionComment + - Popup.BookmarkComment + - Popup.Mention + - Popup.ParticipateComment + properties: + Email.DiscussionComment: + description: Notify me when people comment on my discussions + type: integer + minimum: 0 + maximum: 1 + Email.BookmarkComment: + description: Notify me when people comment on my bookmarked + type: integer + minimum: 0 + maximum: 1 + Email.Mention: + description: Notify me when people mention me + type: integer + minimum: 0 + maximum: 1 + Email.ParticipateComment: + description: Notify me when people comment on discussions I\'ve participated in + type: integer + minimum: 0 + maximum: 1 + Popup.DiscussionComment: + description: Notify me when people comment on my discussions + type: integer + minimum: 0 + maximum: 1 + Popup.BookmarkComment: + description: Notify me when people comment on my bookmarked discussions + type: integer + minimum: 0 + maximum: 1 + Popup.Mention: + description: Notify me when people mention me + type: integer + minimum: 0 + maximum: 1 + Popup.ParticipateComment: + description: Notify me when people comment on discussions I\'ve participated in + type: integer + minimum: 0 + maximum: 1 + CategoryPreferences: + type: object + required: + - CategoryID + - Email.NewDiscussion + - Email.NewComment + - Popup.NewDiscussion + - Popup.NewComment + properties: + CategoryID: + type: integer + Email.NewDiscussion: + type: integer + minimum: 0 + maximum: 2 + Email.NewComment: + type: integer + minimum: 0 + maximum: 2 + Popup.NewDiscussion: + type: integer + minimum: 0 + maximum: 2 + Popup.NewComment: + type: integer + minimum: 0 + maximum: 2 + UserPreferences: + type: object + properties: + GeneralPreferences: + $ref: '#/components/schemas/GeneralPreferences' + CategoryPreferences: + type: array + items: + $ref: '#/components/schemas/CategoryPreferences' + required: + - GeneralPreferences + - CategoryPreferences + UserPreferencesPatch: + type: object + properties: + GeneralPreferences: + $ref: '#/components/schemas/GeneralPreferences' + CategoryPreferences: + type: array + items: + $ref: '#/components/schemas/CategoryPreferences' + required: + - GeneralPreferences + - CategoryPreferences \ No newline at end of file