diff --git a/addon.json b/addon.json index 11dd2a8..790f223 100644 --- a/addon.json +++ b/addon.json @@ -16,7 +16,8 @@ "Groups.Group.Delete", "Groups.Moderation.Manage", "Groups.EmailInvitations.Add", - "Groups.Category.Manage" + "Groups.Category.Manage", + "Groups.Group.Archive" ], "authors": [ { diff --git a/class.groups.plugin.php b/class.groups.plugin.php index 161a671..9042833 100644 --- a/class.groups.plugin.php +++ b/class.groups.plugin.php @@ -15,8 +15,12 @@ class GroupsPlugin extends Gdn_Plugin { const GROUPS_ROUTE = '/groups'; + const ROUTE_MY_GROUPS = '/groups/mine'; + const ROUTE_CHALLENGE_GROUPS = '/groups?filter=challenge'; //'/groups/challenges'; + const ROUTE_REGULAR_GROUPS = '/groups?filter=regular'; //'/groups/regulars'; const GROUP_ROUTE = '/group/'; const GROUPS_GROUP_ADD_PERMISSION = 'Groups.Group.Add'; + const GROUPS_GROUP_ARCHIVE_PERMISSION = 'Groups.Group.Archive'; const GROUPS_GROUP_EDIT_PERMISSION = 'Groups.Group.Edit'; const GROUPS_GROUP_DELETE_PERMISSION = 'Groups.Group.Delete'; const GROUPS_CATEGORY_MANAGE_PERMISSION = 'Groups.Category.Manage'; @@ -79,7 +83,8 @@ private function initDefaultTopcoderRoles() { self::GROUPS_MODERATION_MANAGE_PERMISSION => 1, self::GROUPS_CATEGORY_MANAGE_PERMISSION => 1, self::GROUPS_GROUP_ADD_PERMISSION => 1, - self::GROUPS_EMAIL_INVITATIONS_PERMISSION => 1 + self::GROUPS_EMAIL_INVITATIONS_PERMISSION => 1, + self::GROUPS_GROUP_ARCHIVE_PERMISSION => 1 ], true); $permissionModel->save( [ @@ -88,7 +93,8 @@ private function initDefaultTopcoderRoles() { self::GROUPS_MODERATION_MANAGE_PERMISSION => 1, self::GROUPS_CATEGORY_MANAGE_PERMISSION => 1, self::GROUPS_GROUP_ADD_PERMISSION => 1, - self::GROUPS_EMAIL_INVITATIONS_PERMISSION => 1 + self::GROUPS_EMAIL_INVITATIONS_PERMISSION => 1, + self::GROUPS_GROUP_ARCHIVE_PERMISSION => 1 ], true); $permissionModel->save( [ @@ -165,11 +171,15 @@ public function onDisable() { public function discussionsController_beforeDiscussionMetaData_handler($sender, $args){ if($args['Discussion']) { $discussion = $args['Discussion']; - if ($discussion->GroupID) { - $result = '/group/' . $discussion->GroupID; + $groupModel = new GroupModel(); + $groupID = $groupModel->findGroupIDFromDiscussion($discussion); + GroupsPlugin::log('discussionsController_beforeDiscussionMetaData_handler', [ + 'GroupID' => $groupID]); + if ($groupID) { + $result = '/group/' . $groupID; $url = url($result, true); - $groupModel = new GroupModel(); - $group = $groupModel->getByGroupID($discussion->GroupID); + + $group = $groupModel->getByGroupID($groupID); echo '
'. ''. 'Challenge: '. @@ -234,11 +244,13 @@ public function settingsController_groups_create($sender) { public function discussionController_render_before($sender, $args) { $Discussion = $sender->data('Discussion'); - if($Discussion && $Discussion->GroupID != null) { + if($Discussion) { $groupModel = new GroupModel(); $currentTopcoderProjectRoles = $sender->Data['ChallengeCurrentUserProjectRoles']; $groupModel->setCurrentUserTopcoderProjectRoles($currentTopcoderProjectRoles); - $Group = $groupModel->getByGroupID($Discussion->GroupID); + $groupID = $groupModel->findGroupIDFromDiscussion($Discussion); + self::log('discussionController_render_before:GroupID='.$groupID, []); + $Group = $groupModel->getByGroupID($groupID); if (!$groupModel->canView($Group)) { throw permissionException(); } @@ -318,16 +330,8 @@ public function base_discussionOptionsDropdown_handler($sender, $args){ } } - public function discussionsController_afterDiscussionFilters_handler($sender){ - $this->addGroupLinkToMenu(); - } - - public function discussionController_afterDiscussionFilters_handler($sender){ - $this->addGroupLinkToMenu(); - } - - public function categoriesController_afterDiscussionFilters_handler($sender){ - $this->addGroupLinkToMenu(); + public function base_afterDiscussionFilters_handler($sender){ + $this->addGroupLinkToMenu($sender); } public function base_categoryOptionsDropdown_handler($sender, $args) { @@ -355,9 +359,9 @@ public function base_categoryOptionsDropdown_handler($sender, $args) { */ public function discussionController_discussionInfo_handler($sender, $args) { if($sender->Data['Discussion']) { - $groupID = $sender->Data['Discussion']->GroupID; + $groupModel = new GroupModel(); + $groupID = $groupModel->findGroupIDFromDiscussion($sender->Data['Discussion']); if($groupID) { - $groupModel = new GroupModel(); $group = $groupModel->getByGroupID($groupID); if($group->ChallengeUrl) { echo anchor($group->Name, $group->ChallengeUrl); @@ -374,10 +378,12 @@ public function postController_afterDiscussionSave_handler($sender, $args) { return; } $discussion= $args['Discussion']; + $groupModel = new GroupModel(); + $groupID = $groupModel->findGroupIDFromDiscussion($discussion); if ($sender->deliveryType() == DELIVERY_TYPE_ALL) { - redirectTo(GroupsPlugin::GROUP_ROUTE.$discussion->GroupID); + redirectTo(GroupsPlugin::GROUP_ROUTE.$groupID); } else { - $sender->setRedirectTo(GroupsPlugin::GROUP_ROUTE.$discussion->GroupID); + $sender->setRedirectTo(GroupsPlugin::GROUP_ROUTE.$groupID); } } @@ -679,12 +685,23 @@ private function getTopcoderProjectRoles($user, $resources = null, $roleResource /** * Display a groups link in the menu */ - private function addGroupLinkToMenu() { + private function addGroupLinkToMenu($sender) { if(Gdn::session()->isValid()) { - echo '
  • '. anchor('Challenges', GroupsPlugin::GROUPS_ROUTE).'
  • '; + + echo '
  • '. anchor('Challenges', GroupsPlugin::ROUTE_CHALLENGE_GROUPS).'
  • '; + echo '
  • '. anchor('Groups', GroupsPlugin::ROUTE_REGULAR_GROUPS).'
  • '; + // echo '
  • '. anchor('My Challenges & Groups', GroupsPlugin::ROUTE_MY_GROUPS).'
  • '; } } + private function getMenuItemCssClassFromRequestMethod($sender, $requestMethod){ + return $sender->ControllerName == 'groupscontroller' && $sender->RequestMethod == $requestMethod ? ' Active' : ''; + } + + private function getMenuItemCssClassFromQuery($sender, $requestMethod){ + return $sender->ControllerName == 'groupscontroller' && Gdn::request()->get('filter') == $requestMethod ? ' Active' : ''; + } + public static function log($message, $data= []) { // if (c('Debug')) { Logger::event( diff --git a/controllers/api/GroupsApiController.php b/controllers/api/GroupsApiController.php index 5f8beb2..759a283 100644 --- a/controllers/api/GroupsApiController.php +++ b/controllers/api/GroupsApiController.php @@ -57,7 +57,12 @@ public function index(array $query) { 'field' => 'ChallengeID' ], ], - + 'privacy:s?' => [ + 'description' => 'Filter by group privacy.', + 'x-filter' => [ + 'field' => 'Privacy' + ], + ], 'type:s?' => [ 'description' => 'Filter by group type.', 'x-filter' => [ @@ -194,6 +199,29 @@ public function post_members($id, array $body) { $this->groupModel->join($group->GroupID, $user->UserID); } + /** + * Archive a group. + * + * @param int $id The ID of the group. + * @throws NotFoundException if the group could not be found. + * @throws ServerException If the group could not be archived. + * @return + */ + public function put_archive($id, array $body) { + $this->idParamSchema(); + + $group = $this->groupModel->getByGroupID($id); + if(!$group) { + throw new NotFoundException('Group'); + } + + if(!$this->groupModel->canArchiveGroup($group)) { + throw new ClientException('Don\'t have permissions to archive this group.'); + } + + $this->groupModel->archiveGroup($group->GroupID); + } + /** * Remove a member from a group @@ -284,8 +312,11 @@ public function groupPostSchema() { return $this->schema(Schema::parse([ 'name:s' => 'The name of the group.', 'type:s' => [ - 'enum' => [GroupModel::TYPE_SECRET, GroupModel::TYPE_PUBLIC, GroupModel::TYPE_PRIVATE], - 'description' => 'Type of the group'], + 'enum' => [GroupModel::TYPE_CHALLENGE, GroupModel::TYPE_REGULAR], + 'description' => 'Type of the group'], + 'privacy:s' => [ + 'enum' => [GroupModel::PRIVACY_SECRET, GroupModel::PRIVACY_PUBLIC, GroupModel::PRIVACY_PRIVATE], + 'description' => 'Privacy of the group'], 'description:s' => 'Description of the group', 'challengeID:s?' => 'The challengeID of the Topcoder challenge.', 'challengeUrl:s?' => 'The challengeUrl of the Topcoder challenge.', @@ -300,7 +331,8 @@ public function groupPostSchema() { */ public function groupPatchSchema() { return $this->schema(Schema::parse([ - 'name:s' => 'The name of the group.', + 'name:s?' => 'The name of the group.', + 'archived:i?' => 'The archived status of the group.', ]), 'GroupPatch'); } @@ -313,10 +345,11 @@ protected function fullSchema() { return Schema::parse([ 'groupID:i' => 'The ID of the group.', 'type:s' => 'Type of the group', + 'privacy:s' => 'Privacy of the group', + 'archived:i' => 'Archived status of the group', 'name:s' => 'The name of the group.', 'challengeID:s?' => 'The challengeID of the Topcoder challenge.', 'challengeUrl:s?' => 'The challengeUrl of the Topcoder challenge.', ]); } - } \ No newline at end of file diff --git a/controllers/class.groupcontroller.php b/controllers/class.groupcontroller.php index 5fa3dbc..4282575 100644 --- a/controllers/class.groupcontroller.php +++ b/controllers/class.groupcontroller.php @@ -102,6 +102,7 @@ public function add() { throw permissionException(); } $this->title(t('New Challenge')); + // Use the edit form without groupID $this->View = 'Edit'; $this->edit(); @@ -155,6 +156,9 @@ public function edit($groupID = false) { $this->setData('Breadcrumbs', [['Name' => t('Challenges'), 'Url' => GroupsPlugin::GROUPS_ROUTE], ['Name' => $Group->Name ? $Group->Name: $this->title() ]]); + $typesData = [GroupModel::TYPE_REGULAR => GroupModel::TYPE_REGULAR, GroupModel::TYPE_CHALLENGE => GroupModel::TYPE_CHALLENGE]; + $this->setData('Types', $typesData); + // Set the model on the form. $this->Form->setModel($this->GroupModel); diff --git a/controllers/class.groupscontroller.php b/controllers/class.groupscontroller.php index 2cd787d..a920ca0 100644 --- a/controllers/class.groupscontroller.php +++ b/controllers/class.groupscontroller.php @@ -44,66 +44,132 @@ public function initialize() { $this->fireEvent('AfterInitialize'); } - public function index() { - // Setup head - Gdn_Theme::section('GroupList'); + public function setFilterPageData($filter) { + if($filter == 'challenge') { + $this->View = 'index'; + $this->title('Challenges'); + $this->setData('Title', 'My Challenges'); + $this->setData('AddButtonTitle', 'Challenge'); + $this->setData('AvailableGroupTitle', 'Available Challenges'); + $this->setData('MyGroupButtonTitle', 'All My Challenges'); + $this->setData('AllGroupButtonTitle', 'All Available Challenges'); + $this->SetData('MyGroupButtonLink', '/groups/mine/?filter=challenge'); + $this->setData('AllGroupButtonLink', '/groups/all/?filter=challenge'); + $this->setData('NoGroups', 'No challenges were found.'); + $this->setData('Breadcrumbs', [ + ['Name' => 'Challenges', 'Url' => GroupsPlugin::ROUTE_CHALLENGE_GROUPS]]); + } else if($filter == 'regular' ) { + $this->View = 'index'; + $this->title('Groups'); + $this->setData('Title', 'My Groups'); + $this->setData('AddButtonTitle', 'Group'); + $this->setData('MyGroupButtonTitle', 'All My Groups'); + $this->setData('AllGroupButtonTitle', 'All Available Groups'); + $this->setData('AvailableGroupTitle', 'Available Groups'); + $this->SetData('MyGroupButtonLink', '/groups/mine/?filter=regular'); + $this->setData('AllGroupButtonLink', '/groups/all/?filter=regular'); + $this->setData('NoGroups','No groups were found.'); + $this->setData('Breadcrumbs', [ + ['Name' => t('Groups'), 'Url' => GroupsPlugin::ROUTE_REGULAR_GROUPS]]); + } + } - $this->title(t('Challenges')); - $this->setData('Breadcrumbs', [['Name' => 'Challenges', 'Url' => GroupsPlugin::GROUPS_ROUTE]]); + public function index($Page=false, $filter) { + DashboardNavModule::getDashboardNav()->setHighlightRoute('groups/challenges'); + $this->Menu->highlightRoute('groups/challenges'); + Gdn_Theme::section('GroupList'); $GroupModel = new GroupModel(); + $GroupModel->setFilters(Gdn::request()->get()); + $this->setFilterPageData($filter); + $filters = $GroupModel->getFiltersFromKeys($GroupModel->getFilters()); + + // Filter wasn't found. + // TODO: redirect to a default page + if(count($filters) == 0) { + redirectTo(GroupsPlugin::ROUTE_MY_GROUPS); + } - $this->GroupData = $GroupModel->getMyGroups(false, '', false, 0); - $this->AvailableGroupData = $GroupModel->getAvailableGroups(false, '', false, 0); + $where = $filters[0]['where']; + GroupsPlugin::log('index:filter', ['filters' => $filters, 'where' =>$where]); + list($Offset, $Limit) = offsetLimit($Page, c('Vanilla.Groups.PerPage', 30), true); + $defaultSort = $GroupModel::getAllowedSorts()['new']['orderBy']; + $GroupData = $GroupModel->getMyGroups($where, $defaultSort, $Limit, $Offset); + $countOfGroups = $GroupModel->countMyGroups($where); + $AvailableGroupData = $GroupModel->getAvailableGroups($where, $defaultSort, $Limit, $Offset); $this->setData('CurrentUserGroups', $GroupModel->memberOf(Gdn::session()->UserID)); - $this->setData('Groups', $this->GroupData, true); - $this->setData('AvailableGroups', $this->AvailableGroupData, true); + $this->setData('Groups', $GroupData); + $this->setData('CountOfGroups', $countOfGroups); + $this->setData('AvailableGroups', $AvailableGroupData); $this->render(); } - public function mine($Page = false) { - // Setup head - $this->allowJSONP(true); + public function mine($Page = false,$filter ='') { + if($filter != '') { + $this->mygroups($Page, $filter); + return; + } + Gdn_Theme::section('GroupList'); - // Determine offset from $Page - list($Offset, $Limit) = offsetLimit($Page, c('Vanilla.Groups.PerPage', 30), true); - $Page = pageNumber($Offset, $Limit); + list($Offset, $Limit) = offsetLimit(0, c('Vanilla.Groups.PerPage', 30), true); - // Allow page manipulation - $this->EventArguments['Page'] = &$Page; - $this->EventArguments['Offset'] = &$Offset; - $this->EventArguments['Limit'] = &$Limit; - $this->fireEvent('AfterPageCalculation'); + $this->title(t('My Challenges & Groups')); + $this->setData('Breadcrumbs', [ + ['Name' => t('My Challenges & Groups'), 'Url' => GroupsPlugin::ROUTE_MY_GROUPS]]); - // Set canonical URL - $this->canonicalUrl(url(concatSep('/', '/groups/mine', pageNumber($Offset, $Limit, true, false)), true)); + $GroupModel = new GroupModel(); + $challengeGroupsWhere = $GroupModel::getAllowedFilters()['filter']['filters']['challenge']['where']; + $defaultSort = $GroupModel::getAllowedSorts()['new']['orderBy']; + GroupsPlugin::log('mine:filter', ['filters' => $challengeGroupsWhere]); + $challengeGroupsData = $GroupModel->getMyGroups($challengeGroupsWhere, $defaultSort, $Limit, $Offset); + $countOfChallengeGroups = $GroupModel->countMyGroups($challengeGroupsWhere); + $this->setData('CountOfChallengeGroups', $countOfChallengeGroups); + $this->setData('ChallengeGroups', $challengeGroupsData); + + $regularGroupsWhere = $GroupModel::getAllowedFilters()['filter']['filters']['regular']['where']; + $regularGroupsData = $GroupModel->getMyGroups($regularGroupsWhere, $defaultSort, $Limit, $Offset); + $countOfRegularGroups = $GroupModel->countMyGroups($regularGroupsWhere); + $this->setData('RegularGroups', $regularGroupsData); + $this->setData('CountOfRegularGroups', $countOfRegularGroups); + $this->setData('CurrentUserGroups', $GroupModel->memberOf(Gdn::session()->UserID)); + + $this->render(); + } - $this->title(t('My Challenges')); - $this->setData('Breadcrumbs', [['Name' => t('Challenges'), 'Url' => GroupsPlugin::GROUPS_ROUTE], - ['Name' => t('My Challenges'), 'Url' => GroupsPlugin::GROUPS_ROUTE.'/mine']]); + private function mygroups($Page = false, $filter = '') { + // Setup head + Gdn_Theme::section('GroupList'); + // Determine offset from $Page + list($Offset, $Limit) = offsetLimit($Page, c('Vanilla.Groups.PerPage', 30), true); + $Page = pageNumber($Offset, $Limit); $GroupModel = new GroupModel(); + $GroupModel->setFilters(Gdn::request()->get()); + $filters = $GroupModel->getFiltersFromKeys($GroupModel->getFilters()); - $where = false; - $this->GroupData = $GroupModel->getMyGroups($where, '', $Limit, $Offset); + // Filter wasn't found. + // TODO: redirect to a default page + if(count($filters) == 0) { + redirectTo(GroupsPlugin::ROUTE_MY_GROUPS); + } + $defaultSort = $GroupModel::getAllowedSorts()['new']['orderBy']; + $where = $filters[0]['where']; + $GroupData = $GroupModel->getMyGroups($where, $defaultSort, $Limit, $Offset); $CountGroups = $GroupModel->countMyGroups($where); $this->setData('CountGroups', $CountGroups); - $this->setData('Groups', $this->GroupData, true); + $this->setData('Groups', $GroupData, true); $this->setData('CurrentUserGroups', $GroupModel->memberOf(Gdn::session()->UserID)); - $this->setJson('Loading', $Offset.' to '.$Limit); // Build a pager $PagerFactory = new Gdn_PagerFactory(); - $this->EventArguments['PagerType'] = 'Pager'; - $this->fireEvent('BeforeBuildPager'); if (!$this->data('_PagerUrl')) { $this->setData('_PagerUrl', '/groups/mine/{Page}'); } - $queryString = '';// DiscussionModel::getSortFilterQueryString($DiscussionModel->getSort(), $DiscussionModel->getFilters()); + $queryString = GroupModel::getSortFilterQueryString($GroupModel->getSort(), $GroupModel->getFilters()); $this->setData('_PagerUrl', $this->data('_PagerUrl').$queryString); $this->Pager = $PagerFactory->getPager($this->EventArguments['PagerType'], $this); $this->Pager->ClientID = 'Pager'; @@ -118,61 +184,60 @@ public function mine($Page = false) { $this->setData('_Page', $Page); $this->setData('_Limit', $Limit); - $this->fireEvent('AfterBuildPager'); - $this->View = 'list'; + if($filter == 'regular') { + $title = 'My Groups'; + $noDataText = 'No groups were found.'; + $this->setData('Breadcrumbs', [ + ['Name' => 'Groups', 'Url' => '/groups/'.$queryString], + ['Name' => $title, 'Url' => '/groups/mine/'.$queryString]]); + } else if($filter == 'challenge'){ + $title = 'My Challenges'; + $noDataText = 'No challenges were found.'; + $this->setData('Breadcrumbs', [ + ['Name' => 'Challenges', 'Url' => '/groups/'.$queryString], + ['Name' => $title, 'Url' => '/groups/mine/'.$queryString]]); - // Deliver JSON data if necessary - if ($this->_DeliveryType != DELIVERY_TYPE_ALL) { - $this->setJson('LessRow', $this->Pager->toString('less')); - $this->setJson('MoreRow', $this->Pager->toString('more')); - $this->View = 'groups'; } + $this->setData('Title', $title); + $this->setData('NoDataText',$noDataText); + + $this->View = 'list'; $this->render(); } - public function all($Page = false) { + public function all($Page = false, $filter = '') { // Setup head - $this->allowJSONP(true); Gdn_Theme::section('GroupList'); // Determine offset from $Page list($Offset, $Limit) = offsetLimit($Page, c('Vanilla.Groups.PerPage', 30), true); $Page = pageNumber($Offset, $Limit); - - // Allow page manipulation - $this->EventArguments['Page'] = &$Page; - $this->EventArguments['Offset'] = &$Offset; - $this->EventArguments['Limit'] = &$Limit; - $this->fireEvent('AfterPageCalculation'); - - // Set canonical URL - $this->canonicalUrl(url(concatSep('/', '/groups/all', pageNumber($Offset, $Limit, true, false)), true)); - - $this->title(t('Available Challenges')); - $this->setData('Breadcrumbs', [['Name' => t('Challenges'), 'Url' => GroupsPlugin::GROUPS_ROUTE], - ['Name' => t('Available Challenges'), 'Url' => GroupsPlugin::GROUPS_ROUTE.'/all']]); - $GroupModel = new GroupModel(); + $GroupModel->setFilters(Gdn::request()->get()); + $filters = $GroupModel->getFiltersFromKeys($GroupModel->getFilters()); - $where = false; - $this->GroupData = $GroupModel->getAvailableGroups($where, '', $Limit, $Offset); + // Filter wasn't found. + // TODO: redirect to a default page + if(count($filters) == 0) { + redirectTo(GroupsPlugin::ROUTE_CHALLENGE_GROUPS); + } + $defaultSort = $GroupModel::getAllowedSorts()['new']['orderBy']; + $where = $filters[0]['where']; + $GroupData = $GroupModel->getAvailableGroups($where, $defaultSort, $Limit, $Offset); $CountGroups = $GroupModel->countAvailableGroups($where); $this->setData('CountGroups', $CountGroups); - $this->setData('Groups', $this->GroupData, true); + $this->setData('Groups', $GroupData, true); $this->setData('CurrentUserGroups', $GroupModel->memberOf(Gdn::session()->UserID)); - $this->setJson('Loading', $Offset.' to '.$Limit); // Build a pager $PagerFactory = new Gdn_PagerFactory(); - $this->EventArguments['PagerType'] = 'Pager'; - $this->fireEvent('BeforeBuildPager'); if (!$this->data('_PagerUrl')) { $this->setData('_PagerUrl', '/groups/all/{Page}'); } - $queryString = ''; + $queryString = GroupModel::getSortFilterQueryString($GroupModel->getSort(), $GroupModel->getFilters()); $this->setData('_PagerUrl', $this->data('_PagerUrl').$queryString); $this->Pager = $PagerFactory->getPager($this->EventArguments['PagerType'], $this); $this->Pager->ClientID = 'Pager'; @@ -187,16 +252,26 @@ public function all($Page = false) { $this->setData('_Page', $Page); $this->setData('_Limit', $Limit); - $this->fireEvent('AfterBuildPager'); - $this->View = 'list'; + if($filter == 'regular') { + $title = 'Available Groups'; + $noDataText = 'No groups were found.'; + $this->setData('Breadcrumbs', [ + ['Name' => 'Groups', 'Url' => '/groups/'.$queryString], + ['Name' => $title, 'Url' => '/groups/all/'.$queryString]]); + + } else if($filter == 'challenge'){ + $title = 'Available Challenges'; + $noDataText = 'No challenges were found.'; + $this->setData('Breadcrumbs', [ + ['Name' => 'Challenges', 'Url' => '/groups/'.$queryString], + ['Name' => $title, 'Url' => '/groups/all/'.$queryString]]); - // Deliver JSON data if necessary - if ($this->_DeliveryType != DELIVERY_TYPE_ALL) { - $this->setJson('LessRow', $this->Pager->toString('less')); - $this->setJson('MoreRow', $this->Pager->toString('more')); - $this->View = 'groups'; } + $this->setData('Title', $title); + $this->setData('NoDataText',$noDataText); + + $this->View = 'list'; $this->render(); } diff --git a/models/class.groupmodel.php b/models/class.groupmodel.php index 60dfb50..c484fe5 100644 --- a/models/class.groupmodel.php +++ b/models/class.groupmodel.php @@ -3,19 +3,94 @@ * Class GroupModel */ class GroupModel extends Gdn_Model { - /** Slug for PUBLIC type. */ - const TYPE_PUBLIC = 'public'; + use StaticInitializer; - /** Slug for PRIVATE type. */ - const TYPE_PRIVATE = 'private'; + // + // Group Privacy + // + /** Slug for PUBLIC privacy. */ + const PRIVACY_PUBLIC = 'public'; - /** Slug for SECRET type. */ - const TYPE_SECRET = 'secret'; + /** Slug for PRIVATE privacy. */ + const PRIVACY_PRIVATE = 'private'; - const ROLE_MEMBER = 'member'; + /** Slug for SECRET privacy. */ + const PRIVACY_SECRET = 'secret'; + // + // Group Roles + // + const ROLE_MEMBER = 'member'; const ROLE_LEADER = 'leader'; + // + // Group Types + // + const TYPE_CHALLENGE = 'challenge'; + const TYPE_REGULAR = 'regular'; + + /** @var string The filter key for clearing-type filters. */ + const EMPTY_FILTER_KEY = 'none'; + + /** @var string Default column to order by. */ + const DEFAULT_SORT_KEY = 'new'; + + /** + * @var array The filters that are accessible via GET. Each filter corresponds with a where clause. You can have multiple + * filter sets. Every filter must be added to a filter set. + * + * Each filter set has the following properties: + * - **key**: string - The key name of the filter set. Appears in the query string, should be url-friendly. + * - **name**: string - The display name of the filter set. Usually appears in the UI. + * - **filters**: array - The filters in the set. + * + * Each filter in the array has the following properties: + * - **key**: string - The key name of the filter. Appears in the query string, should be url-friendly. + * - **setKey**: string - The key name of the filter set. + * - **name**: string - The display name of the filter. Usually appears as an option in the UI. + * - **where**: string - The where array query to execute for the filter. Uses + */ + // [$setKey]['filters'][$key] = ['key' => $key, 'setKey' => $setKey, 'name' => $name, 'wheres' => $wheres]; + protected static $allowedFilters = [ + 'filter' => [ 'key' => 'filter', 'name' => 'All', + 'filters' => [ + 'challenge' => ['key' => 'challenge', 'name' => 'Challenges', 'where' => ['g.Type' => self::TYPE_CHALLENGE]], + 'regular' =>['key' => 'regular', 'name' => 'Groups', 'where' => ['g.Type' => self::TYPE_REGULAR]] + ] + + ], + ]; + + /** + * @var array The sorts that are accessible via GET. Each sort corresponds with an order by clause. + * + * Each sort in the array has the following properties: + * - **key**: string - The key name of the sort. Appears in the query string, should be url-friendly. + * - **name**: string - The display name of the sort. + * - **orderBy**: string - An array indicating order by fields and their directions in the format: + * `['field1' => 'direction', 'field2' => 'direction']` + */ + protected static $allowedSorts = [ + 'new' => ['key' => 'new', 'name' => 'New', 'orderBy' => ['g.DateInserted' => 'desc']], + 'old' => ['key' => 'new', 'name' => 'Old', 'orderBy' => ['g.DateInserted' => 'asc']] + ]; + + /** + * @var array The filter keys of the wheres we apply in the query. + */ + protected $filters = [ + ]; + + /** + * @var string The sort key of the order by we apply in the query. + */ + protected $sort = ''; + + /** + * @var GroupModel $instance; + */ + private static $instance; + private $currentUserTopcoderProjectRoles = []; /** @@ -26,6 +101,388 @@ public function __construct() { $this->fireEvent('Init'); } + /** + * The shared instance of this object. + * + * @return GroupModel Returns the instance. + */ + public static function instance() { + if (self::$instance === null) { + self::$instance = new GroupModel(); + } + return self::$instance; + } + + /** + * @return array The current sort array. + */ + public static function getAllowedSorts() { + self::initStatic(); + return self::$allowedSorts; + } + + /** + * Get the registered filters. + * + * This method must never be called before plugins initialisation. + * + * @return array The current filter array. + */ + public static function getAllowedFilters() { + self::initStatic(); + return self::$allowedFilters; + } + + /** + * @return array + */ + public function getFilters() { + return $this->filters; + } + + /** + * @return string + */ + public function getSort() { + return $this->sort; + } + + /** + * Set the discussion sort. + * + * This setter also accepts an array and checks if the sort key exists on the array. Will only set the sort property + * if it exists in the allowed sorts array. + * + * @param string|array $sort The prospective sort to set. + */ + public function setSort($sort) { + if (is_array($sort)) { + $safeSort = $this->getSortFromArray($sort); + $this->sort = $safeSort; + } elseif (is_string($sort)) { + $safeSort = $this->getSortFromString($sort); + $this->sort = $safeSort; + } + } + + /** + * Will only set the filters property if the passed filters exist in the allowed filters array. + * + * @param array $filters The prospective filters to set. + */ + public function setFilters($filters) { + if (is_array($filters)) { + $safeFilters = $this->getFiltersFromArray($filters); + $this->filters = $safeFilters; + } elseif (is_string($filters)) { + $safeFilters = $this->getFiltersFromArray([$filters]); + $this->filters = $safeFilters; + } + } + + /** + * @return string + */ + public static function getDefaultSort() { + // Try to find a matching sort. + foreach (self::getAllowedSorts() as $sort) { + if (val('key', $sort, []) == self::DEFAULT_SORT_KEY) { + return $sort; + } + } + + Logger::log( + Logger::DEBUG, + 'Sort: default Sort Key does not exist in the GroupModel\'s allowed sorts array.', + ['sortKey' => self::DEFAULT_SORT_KEY] + ); + + return []; + } + + /** + * Retrieves valid set key and filter keys pairs from an array, and returns the setKey => filterKey values. + * + * Works real well with unfiltered request arguments. (i.e., Gdn::request()->get()) Will only return safe + * set key and filter key pairs from the filters array or an empty array if not found. + * + * @param array $array The array to get the filters from. + * @return array The valid filters from the passed array or an empty array. + */ + protected function getFiltersFromArray($array) { + GroupsPlugin::log('getFiltersFromArray', ['param' => $array]); + $filterKeys = []; + foreach (self::getAllowedFilters() as $filterSet) { + $filterSetKey = val('key', $filterSet); + //GroupsPlugin::log('Trying to get "key" from filterSet', ['$filterSet'=> $filterSet, '$filterSetKey' => $filterSetKey = val('key', $filterSet)]); + // Check if any of our filters are in the array. Filter key value is unsafe. + if ($filterKey = val($filterSetKey, $array)) { + GroupsPlugin::log('Using "filterSetKey" ', ['$filterSetKey'=> $filterSetKey]); + GroupsPlugin::log('Using "filterKey is" ', ['$filterKey'=> $filterKey]); + GroupsPlugin::log('Using array ...', ['array'=> val('filters', $filterSet)]); + // Check that value is in filter array to ensure safety. + if (val($filterKey, val('filters', $filterSet))) { + GroupsPlugin::log('$filterKey='.$filterKey, []); + // Value is safe. + $filterKeys[$filterSetKey] = $filterKey; + } else { + Logger::log( + Logger::NOTICE, + 'Filter: {filterSetKey} => {$filterKey} does not exist in the GroupModel\'s allowed filters array.', + ['filterSetKey' => $filterSetKey, 'filterKey' => $filterKey] + ); + } + } + } + return $filterKeys; + } + + /** + * Retrieves the sort key from an array and if the value is valid, returns it. + * + * Works real well with unfiltered request arguments. (i.e., Gdn::request()->get()) Will only return a safe sort key + * from the sort array or an empty string if not found. + * + * @param array $array The array to get the sort from. + * @return string The valid sort from the passed array or an empty string. + */ + protected function getSortFromArray($array) { + $unsafeSortKey = val('sort', $array); + foreach (self::getAllowedSorts() as $sort) { + if ($unsafeSortKey == val('key', $sort)) { + // Sort key is valid. + return val('key', $sort); + } + } + if ($unsafeSortKey) { + Logger::log( + Logger::NOTICE, + 'Sort: {unsafeSortKey} does not exist in the DiscussionModel\'s allowed sorts array.', + ['unsafeSortKey' => $unsafeSortKey] + ); + } + return ''; + } + + /** + * Checks the allowed sorts array for the string and it is valid, returns it the string. + * + * If not, returns an empty string. Will only return a safe sort key from the sort array or an empty string if not + * found. + * + * @param string $string The string to get the sort from. + * @return string A valid sort key or an empty string. + */ + protected function getSortFromString($string) { + if (val($string, self::$allowedSorts)) { + // Sort key is valid. + return $string; + } else { + Logger::log( + Logger::DEBUG, + 'Sort "{sort}" does not exist in the GroupModel\'s allowed sorts array.', + ['sort' => $string] + ); + return ''; + } + } + + /** + * Takes a collection of filters and returns the corresponding filter key/value array [setKey => filterKey]. + * + * @param array $filters The filters to get the keys for. + * @return array The filter key array. + */ + protected function getKeysFromFilters($filters) { + $filterKeyValues = []; + foreach ($filters as $filter) { + if (isset($filter['setKey']) && isset($filter['key'])) { + $filterKeyValues[val('setKey', $filter)] = val('key', $filter); + } + } + return $filterKeyValues; + } + + + /** + * Takes an array of filter key/values [setKey => filterKey] and returns a collection of filters. + * + * @param array $filterKeyValues The filters key array to get the filter for. + * @return array An array of filters. + */ + public function getFiltersFromKeys($filterKeyValues) { + $filters = []; + $allFilters = self::getAllowedFilters(); + foreach ($filterKeyValues as $key => $value) { + if (isset($allFilters[$key]['filters'][$value])) { + $filters[] = $allFilters[$key]['filters'][$value]; + } + } + return $filters; + } + + /** + * @param string $sortKey + * @return array + */ + public function getSortFromKey($sortKey) { + return val($sortKey, self::getAllowedSorts(), []); + } + + /** + * Get the current sort/filter query string. + * + * You can pass no parameters or pass either a new filter key or sort key to build a new query string, leaving the + * other properties intact. + * + * @param string $selectedSort + * @param array $selectedFilters + * @param string $sortKeyToSet The key name of the sort in the sorts array. + * @param array $filterKeysToSet An array of filters, where the key is the key of the filterSet in the filters array + * and the value is the key of the filter. + * @return string The current or amended query string for sort and filter. + */ + public static function getSortFilterQueryString($selectedSort, $selectedFilters, $sortKeyToSet = '', $filterKeysToSet = []) { + $filterString = ''; + $filterKeys = array_merge($selectedFilters, $filterKeysToSet); + + // Build the sort query string + foreach ($filterKeys as $setKey => $filterKey) { + // If the preference is none, don't show it. + if ($filterKey != self::EMPTY_FILTER_KEY) { + if (!empty($filterString)) { + $filterString .= '&'; + } + $filterString .= $setKey.'='.$filterKey; + } + } + + $sortString = ''; + if (!$sortKeyToSet) { + $sort = $selectedSort; + if ($sort) { + $sortString = 'sort='.$sort; + } + } else { + $sortString = 'sort='.$sortKeyToSet; + } + + $queryString = ''; + if (!empty($sortString) && !empty($filterString)) { + $queryString = '?'.$sortString.'&'.$filterString; + } elseif (!empty($sortString)) { + $queryString = '?'.$sortString; + } elseif (!empty($filterString)) { + $queryString = '?'.$filterString; + } + + return $queryString; + } + + /** + * Add a sort to the allowed sorts array. + * + * @param string $key The key name of the sort. Appears in the query string, should be url-friendly. + * @param string $name The display name of the sort. + * @param string|array $orderBy An array indicating order by fields and their directions in the format: + * array('field1' => 'direction', 'field2' => 'direction') + */ + public static function addSort($key, $name, $orderBy) { + self::$allowedSorts[$key] = ['key' => $key, 'name' => $name, 'orderBy' => $orderBy]; + } + + /** + * Add a filter to the allowed filters array. + * + * @param string $key The key name of the filter. Appears in the query string, should be url-friendly. + * @param string $name The display name of the filter. Usually appears as an option in the UI. + * @param array $wheres The where array query to execute for the filter. Uses + * @param string $setKey The key name of the filter set. + */ + public static function addFilter($key, $name, $wheres, $setKey = 'filter') { + if (!val($setKey, self::getAllowedFilters())) { + self::addFilterSet($setKey); + } + self::$allowedFilters[$setKey]['filters'][$key] = ['key' => $key, 'setKey' => $setKey, 'name' => $name, 'wheres' => $wheres]; + } + + /** + * Adds a filter set to the allowed filters array. + * + * @param string $setKey The key name of the filter set. + * @param string $setName The name of the filter set. Appears in the UI. + * @param array $categoryIDs The IDs of the categories that this filter will work on. If empty, filter is global. + */ + public static function addFilterSet($setKey, $setName = '', $categoryIDs = []) { + if (!$setName) { + $setName = t('All Groups'); + } + self::$allowedFilters[$setKey]['key'] = $setKey; + self::$allowedFilters[$setKey]['name'] = $setName; + // self::$allowedFilters[$setKey]['categories'] = $categoryIDs; + + // Add a way to let users clear any filters they've added. + self::addClearFilter($setKey, $setName); + } + + /** + * Removes a filter set from the allowed filter array with the passed set key. + * + * @param string $setKey The key of the filter to remove. + */ + public static function removeFilterSet($setKey) { + if (val($setKey, self::$allowedFilters)) { + unset(self::$allowedFilters[$setKey]); + } + } + + + /** + * Removes a filters from the allowed filter array with the passed filter key/values. + * + * @param array $filterKeys The key/value pairs of the filters to remove. + */ + public static function removeFilter($filterKeys) { + foreach ($filterKeys as $setKey => $filterKey) { + if (isset(self::$allowedFilters[$setKey]['filters'][$filterKey])) { + unset(self::$allowedFilters[$setKey]['filters'][$filterKey]); + } + } + } + + /** + * Adds an option to a filter set filters array to clear any existing filters on the data. + * + * @param string $setKey The key name of the filter set to add the option to. + * @param string $setName The display name of the option. Usually the human-readable set name. + */ + protected static function addClearFilter($setKey, $setName = '') { + self::$allowedFilters[$setKey]['filters'][self::EMPTY_FILTER_KEY] = [ + 'key' => self::EMPTY_FILTER_KEY, + 'setKey' => $setKey, + 'name' => $setName, + 'wheres' => [] + ]; + } + + /** + * If you don't want to use any of the default sorts, use this little buddy. + */ + public static function clearSorts() { + self::$allowedSorts = []; + } + + /** + * Removes a sort from the allowed sort array with the passed key. + * + * @param string $key The key of the sort to remove. + */ + public static function removeSort($key) { + if (val($key, self::$allowedSorts)) { + unset(self::$allowedSorts[$key]); + } + } + public function setCurrentUserTopcoderProjectRoles($topcoderProjectRoles = []){ $this->currentUserTopcoderProjectRoles = $topcoderProjectRoles; } @@ -53,6 +510,15 @@ public function getDefaultLimit() { return c('Vanilla.Groups.PerPage', 30); } + public function findGroupIDFromDiscussion($discussion){ + if(is_numeric($discussion)){ + $discussionModel = new DiscussionModel(); + $discussion = $discussionModel->getID($discussion); + } + $categoryID = val('CategoryID', $discussion); + $category = CategoryModel::categories($categoryID); + return val('GroupID', $category, false); + } /** * Default Gdn_Model::get() behavior. @@ -76,7 +542,7 @@ public function getAllPublicGroupIDs(){ // Build up the base query. Self-join for optimization. $sql->select('g.GroupID') ->from('Group g') - ->where('g.Type' , [GroupModel::TYPE_PUBLIC] ); + ->where('g.Privacy' , [GroupModel::PRIVACY_PUBLIC] ); $data = $sql->get()->resultArray(); return array_column($data, 'GroupID'); @@ -179,7 +645,7 @@ public function getAncestors($categoryIDs) { } /** - * Get all available groups including private ines + * Get all available groups including private ones */ public function getAvailableGroups($where =[], $orderFields = '', $limit = false, $offset = false) { @@ -199,9 +665,9 @@ public function getAvailableGroups($where =[], $orderFields = '', $limit = false $sql = $this->SQL; - $groupTypes = [GroupModel::TYPE_PUBLIC, GroupModel::TYPE_PRIVATE]; + $groupTypes = [GroupModel::PRIVACY_PUBLIC, GroupModel::PRIVACY_PRIVATE]; if(Gdn::session()->checkPermission(GroupsPlugin::GROUPS_MODERATION_MANAGE_PERMISSION)) { - array_push($groupTypes,GroupModel::TYPE_SECRET); + array_push($groupTypes,GroupModel::PRIVACY_SECRET); } // Build up the base query. Self-join for optimization. @@ -209,12 +675,13 @@ public function getAvailableGroups($where =[], $orderFields = '', $limit = false ->from('Group g') ->leftjoin('UserGroup ug', 'ug.GroupID=g.GroupID and ug.UserID='.Gdn::session()->UserID) ->where('ug.UserID' , null) - ->where('g.Type' , $groupTypes ) + ->where('g.Privacy' , $groupTypes ) + ->where('g.Archived' , 0 ) ->where($where) ->limit($limit, $offset); foreach ($orderFields as $field => $direction) { - $sql->orderBy($this->addFieldPrefix($field), $direction); + $sql->orderBy($field, $direction); } $data = $sql->get(); @@ -232,22 +699,23 @@ public function countAvailableGroups($where =[]) { $sql = $this->SQL; - $groupTypes = [GroupModel::TYPE_PUBLIC, GroupModel::TYPE_PRIVATE]; + $groupTypes = [GroupModel::PRIVACY_PUBLIC, GroupModel::PRIVACY_PRIVATE]; if(Gdn::session()->checkPermission(GroupsPlugin::GROUPS_MODERATION_MANAGE_PERMISSION)) { - array_push($groupTypes,GroupModel::TYPE_SECRET); + array_push($groupTypes,GroupModel::PRIVACY_SECRET); } // Build up the base query. Self-join for optimization. - $sql->select('g.*') + $sql->select('count(*) Count') ->from('Group g') ->leftjoin('UserGroup ug', 'ug.GroupID=g.GroupID and ug.UserID='.Gdn::session()->UserID) ->where('ug.UserID' , null) - ->where('g.Type' , $groupTypes) + ->where('g.Privacy' , $groupTypes) + ->where('g.Archived' , 0 ) ->where($where); $data = $sql->get() ->firstRow(); - + GroupsPlugin::log('countAvailableGroups', ['data' => $data]); return $data === false ? 0 : $data->Count; } @@ -276,13 +744,16 @@ public function getMyGroups($where =[], $orderFields = '', $limit = false, $offs $sql->select('g.*, ug.Role, ug.DateInserted') ->from('Group g') ->join('UserGroup ug', 'ug.GroupID=g.GroupID and ug.UserID='.Gdn::session()->UserID) - ->limit($limit, $offset); + ->where('g.Archived' , 0 ) + ->where($where); + foreach ($orderFields as $field => $direction) { - $sql->orderBy($this->addFieldPrefix($field), $direction); + $sql->orderBy($field, $direction); } - $sql->where($where); + $sql->limit($limit, $offset); + $data = $sql->get(); return $data; @@ -303,8 +774,10 @@ public function countMyGroups($where =[]) { $sql->select('count(*) Count') ->from('Group g') ->join('UserGroup ug', 'ug.GroupID=g.GroupID and ug.UserID='.Gdn::session()->UserID) + ->where('g.Archived' , 0 ) ->where($where); + $data = $sql->get() ->firstRow(); @@ -614,7 +1087,7 @@ public function getGroupRoleFor($userID, $groupID) { * */ public function canView($group) { - if($group->Type == GroupModel::TYPE_PUBLIC){ + if($group->Privacy == GroupModel::PRIVACY_PUBLIC){ return true; } else { $result = $this->getGroupRoleFor(Gdn::session()->UserID, $group->GroupID); @@ -864,7 +1337,7 @@ public function canDelete($group){ * */ public function canJoin($group) { - return $group->Type == GroupModel::TYPE_PUBLIC; + return $group->Privacy == GroupModel::PRIVACY_PUBLIC; } /** @@ -974,7 +1447,7 @@ public function canAddAnnouncement($group) { * */ public function canViewDiscussion($discussion) { - $groupID= $discussion->GroupID; + $groupID = $this->findGroupIDFromDiscussion($discussion); if(!$groupID) { return true; } @@ -992,7 +1465,7 @@ public function canViewDiscussion($discussion) { */ public function canEditDiscussion($discussion) { $canEditDiscussion = DiscussionModel::canEdit($discussion) ; - $groupID= $discussion->GroupID; + $groupID= $this->findGroupIDFromDiscussion($discussion); if(!$groupID) { return $canEditDiscussion; } @@ -1016,7 +1489,7 @@ public function canDismissDiscussion($discussion) { && !$discussion->Dismissed && Gdn::session()->isValid(); - $groupID= $discussion->GroupID; + $groupID= $this->findGroupIDFromDiscussion($discussion); if(!$groupID ) { return $canDismissDiscussion; } @@ -1042,7 +1515,8 @@ public function canDismissDiscussion($discussion) { */ public function canAnnounceDiscussion($discussion) { $canAnnounceDiscussion = CategoryModel::checkPermission($discussion->CategoryID, 'Vanilla.Discussions.Announce', true); - $groupID = $discussion->GroupID; + $groupID = $this->findGroupIDFromDiscussion($discussion); + if(!$groupID ) { return $canAnnounceDiscussion; } @@ -1093,7 +1567,7 @@ public function canAddComment($categoryID, $groupID) { */ public function canSinkDiscussion($discussion) { $canSinkDiscussion = CategoryModel::checkPermission($discussion->CategoryID, 'Vanilla.Discussions.Sink', true); - $groupID = $discussion->GroupID; + $groupID = $this->findGroupIDFromDiscussion($discussion); if(!$groupID ) { return $canSinkDiscussion; } @@ -1120,7 +1594,7 @@ public function canSinkDiscussion($discussion) { */ public function canCloseDiscussion($discussion) { $canCloseDiscussion = CategoryModel::checkPermission($discussion->CategoryID, 'Vanilla.Discussions.Close', true); - $groupID = $discussion->GroupID; + $groupID = $this->findGroupIDFromDiscussion($discussion); if(!$groupID ) { return $canCloseDiscussion; } @@ -1157,7 +1631,7 @@ public function canRefetchDiscussion($discussion) { public function canDeleteDiscussion($discussion) { $canDeleteDiscussion = CategoryModel::checkPermission($discussion->CategoryID, 'Vanilla.Discussions.Delete', true); - $groupID= $discussion->GroupID; + $groupID = $this->findGroupIDFromDiscussion($discussion); if(!$groupID) { return $canDeleteDiscussion; } @@ -1237,4 +1711,40 @@ public function notifyJoinGroup($groupID, $userID) { ]; $activityModel->save($data); } + + + public function canArchiveGroup($group){ + return Gdn::session()->UserID == $group->OwnerID || Gdn::session()->checkPermission(GroupsPlugin::GROUPS_GROUP_ARCHIVE_PERMISSION); + } + + /** + * Archive a group and its categories + * + * @param $group + */ + public function archiveGroup($group){ + if(is_numeric($group) && $group > 0) { + $group = $this->getByGroupID($group); + } + + if($group->ChallengeID) { + $categoryModel = new CategoryModel(); + $groupCategory = $categoryModel->getByCode($group->ChallengeID); + if($groupCategory->DisplayAs !== 'Discussions') { + $categories = CategoryModel::getSubtree($groupCategory->CategoryID, true); + $categoryIDs = array_column($categories, 'CategoryID'); + } else { + $categoryIDs = [$groupCategory->CategoryID]; + } + + foreach($categoryIDs as $categoryID) { + $category = $categoryModel->getID($categoryID, DATASET_TYPE_ARRAY); + $category['Archived'] = 1; + $categoryModel->save($category); + } + } + + $group->Archived = 1; + $this->save($group); + } } \ No newline at end of file diff --git a/openapi/groups.yml b/openapi/groups.yml index 3576132..f81c34e 100644 --- a/openapi/groups.yml +++ b/openapi/groups.yml @@ -148,6 +148,23 @@ paths: $ref: '#/components/schemas/GroupMemberPost' required: true summary: Add member to a group. + '/groups/{id}/archive': + put: + parameters: + - description: The group ID. + in: path + name: id + required: true + schema: + type: integer + responses: + '204': + description: Success + tags: + - Groups + requestBody: + required: false + summary: Archive a group. components: requestBodies: GroupPost: @@ -180,15 +197,22 @@ components: description: The name of the group. minLength: 1 type: string + privacy: + description: The privacy of this group + minLength: 1 + enum: + - public + - private + - secret type: description: The type of this group minLength: 1 enum: - - public - - private - - secret + - challenge + - regular required: - groupID + - privacy - type - name - challengeID @@ -200,14 +224,20 @@ components: description: The name of the group. minLength: 1 type: string - type: + privacy: type: string - description: The type of the group + description: The privacy of the group minLength: 1 enum: - public - private - secret + type: + description: The type of this group + minLength: 1 + enum: + - challenge + - regular description: description: The description of the group. minLength: 1 @@ -221,6 +251,7 @@ components: required: - name - type + - privacy - description - challengeID - challengeUrl diff --git a/structure.php b/structure.php index ff047b8..a154de9 100644 --- a/structure.php +++ b/structure.php @@ -30,7 +30,7 @@ ->primaryKey('GroupID') ->column('Name', 'varchar(100)') ->column('Description', 'varchar(500)', true) - ->column('Type', [GroupModel::TYPE_PUBLIC, GroupModel::TYPE_PRIVATE, GroupModel::TYPE_SECRET], true) + ->column('Type', [GroupModel::PRIVACY_PUBLIC, GroupModel::PRIVACY_PRIVATE, GroupModel::PRIVACY_SECRET], true) ->column('Deletable', 'tinyint(1)', '1') ->column('Closed', 'tinyint(1)', '0') ->column('DateInserted', 'datetime', false, 'index') @@ -132,3 +132,50 @@ $SQL->insert('ActivityType', ['AllowComments' => '0', 'Name' => 'InviteToGroup', 'FullHeadline' => '%%1$s invited %4$s to %8s.', 'ProfileHeadline' => '%1$s invited %4$s to %8s.', 'RouteCode' => 'group', 'Public' => '0','Notify' => '1']); } +// +// Update Vanilla tables and data +// + +//Add extra columns in Category : https://github.com/topcoder-platform/forums/issues/178 +if(!Gdn::structure()->table('Category')->columnExists('GroupID')) { + Gdn::structure()->table('Category') + ->column('GroupID', 'int', true, 'key') + ->set(false, false); + + // Update data after adding GroupID column: https://github.com/topcoder-platform/forums/issues/178 + Gdn::sql()->query("UPDATE GDN_Category c + INNER JOIN (SELECT c.CategoryID, g.GroupID FROM GDN_Category c , GDN_Group g WHERE c.UrlCode LIKE concat(g.ChallengeID,'%')) AS src + ON src.CategoryID = c.CategoryID + SET c.GroupID = src.GroupID + WHERE c.GroupID IS NULL"); +} + + + +// Add the column Type in Group : https://github.com/topcoder-platform/forums/issues/133 +if(! Gdn::structure()->table('Group')->columnExists('Privacy')) { + if(Gdn::structure()->table('Group')->renameColumn('Type', 'Privacy')) { + + // Reset the internal state of this object so that it can be reused. + Gdn::structure()->reset(); + + Gdn::structure()->table('Group') + ->column('Type', ['challenge', 'regular'], true) + ->set(false, false); + + // Update existing data, all groups with ChallengeID will have the type 'challenge' + Gdn::sql()->query("UPDATE GDN_Group g + SET g.Type = CASE WHEN g.ChallengeID IS NOT NULL THEN 'challenge' + ELSE 'regular' END"); + + Gdn::structure()->table('Group') + ->column('Type', ['challenge', 'regular'], false) + ->set(false, false); + } +} + +// Add the column Archived in Group : https://github.com/topcoder-platform/forums/issues/136 +if(!Gdn::structure()->table('Group')->columnExists('Archived')) { + Gdn::structure()->table('Group') + ->column('Archived', 'tinyint(1)', '0'); +} diff --git a/views/group/edit.php b/views/group/edit.php index 719f66d..a05d164 100644 --- a/views/group/edit.php +++ b/views/group/edit.php @@ -25,6 +25,16 @@ echo '
    '.$this->Form->textBox('Description', ['MultiLine' => true]).'
    '; echo '
    '; + echo '
    '; + echo $this->Form->label('Type', 'Type'); + echo $this->Form->dropDown('Type', $this->data('Types'), ['IncludeNull' => t('All Types')]); + echo '
    '; + + echo '
    '; + echo $this->Form->label('Archived', 'Archived'); + echo $this->Form->checkbox('Archived','Is Archived?', ['value' => '1']); + echo '
    '; + echo '
    '; echo $this->Form->label('Icon', 'Icon'); echo $this->Form->imageUploadPreview('Icon'); @@ -38,9 +48,9 @@ echo '
    '; echo '
    Privacy
    '; - echo $this->Form->radioList('Type',[GroupModel::TYPE_PUBLIC => 'Public. Anyone can see the group and its content. Anyone can join.', - GroupModel::TYPE_PRIVATE => 'Private. Anyone can see the group, but only members can see its content. People must apply or be invited to join.', - GroupModel::TYPE_SECRET => 'Secret. Only members can see the group and view its content. People must be invited to join.'], ['Default' => GroupModel::TYPE_PUBLIC]); + echo $this->Form->radioList('Privacy',[GroupModel::PRIVACY_PUBLIC => 'Public. Anyone can see the group and its content. Anyone can join.', + GroupModel::PRIVACY_PRIVATE => 'Private. Anyone can see the group, but only members can see its content. People must apply or be invited to join.', + GroupModel::PRIVACY_SECRET => 'Secret. Only members can see the group and view its content. People must be invited to join.'], ['Default' => GroupModel::PRIVACY_PUBLIC]); echo '
    '; echo '
    '; diff --git a/views/groups/groups.php b/views/groups/groups.php index da52630..1cbf00d 100644 --- a/views/groups/groups.php +++ b/views/groups/groups.php @@ -5,6 +5,4 @@ include($this->fetchViewLocation('helper_functions', 'groups', 'vanilla')); } -foreach ($this->GroupData->result() as $Group) { - writeGroup($Group, $this, $Session); -} +echo writeGroups($this->data('Groups'), $this); diff --git a/views/groups/helper_functions.php b/views/groups/helper_functions.php index ad2fefc..c3a1343 100644 --- a/views/groups/helper_functions.php +++ b/views/groups/helper_functions.php @@ -58,7 +58,7 @@ function writeGroups($Groups, $sender){ } } -if (!function_exists('writeGroup')) : +if (!function_exists('writeGroup')) { /** * @@ -84,7 +84,7 @@ function writeGroup($group, $sender, $session) {
    Type == GroupModel::TYPE_PUBLIC && hasJoinedGroup($group->GroupID) == null) { + if($group->Privacy == GroupModel::PRIVACY_PUBLIC && hasJoinedGroup($group->GroupID) == null) { echo anchor('Join', '/group/join/' . $group->GroupID, 'Button Popup', ''); } @@ -109,4 +109,82 @@ function writeGroup($group, $sender, $session) { '; + echo '
    '; + echo '

    '.$sectionTitle.'

    '; + echo '
    '; + + if ($Groups->numRows() > 0 ) { + ?> +
      + +
    +
    + +
    + '; + } +} + +if(!function_exists('buildGroupPagerOptions')) { + function buildGroupPagerOptions($Groups, $Pager){ + $pagerOptions = ['Wrapper' => ' 
    %2$s
    ', 'RecordCount' => $Pager->TotalRecords, + 'CurrentRecords' => $Groups->numRows()]; + + return $pagerOptions; + } +} + +if(!function_exists('writeGroupSection')) { + + function writeGroupSection($Groups, $Pager = null, $sectionTitle, $noDataText, $moreDataText, $moreDataLink, $sender){ + echo '
    '; + echo '
    '; + echo '

    '.$sectionTitle.'

    '; + echo '
    '; + + if($Pager) { + $PagerOptions = buildGroupPagerOptions($Groups, $Pager); + PagerModule::current($Pager); + } + + if ($Groups->numRows() > 0 ) { ?> + '; + PagerModule::write($PagerOptions); + echo '
    '; + } + ?> +
      + +
    + '; + PagerModule::write($PagerOptions); + echo '
    '; + } else { ?> +
    + + +
    + '; + } +} \ No newline at end of file diff --git a/views/groups/index.php b/views/groups/index.php index 5cf54f0..b71d3ae 100644 --- a/views/groups/index.php +++ b/views/groups/index.php @@ -9,46 +9,9 @@ include_once $this->fetchViewLocation('helper_functions'); if($canAddGroup === true) { - echo ''; + echo ''; } +echo writeGroupSection($this->data('Groups'), $this->data('GroupsPager') , $this->data('Title'), $this->data('NoGroups'),$this->data('MyGroupButtonTitle'), $this->data('MyGroupButtonLink'),$this); -echo '
    '; - echo '
    '; - echo '

    My Challenges

    '; - echo '
    '; - - if ($this->GroupData->numRows() > 0 ) { - ?> -

    -
      - GroupData, $this); ?> -
    -
    - -
    - '; - -echo '
    '; -echo '
    '; -echo '

    Available Challenges

    '; -echo '
    '; - -if ($this->AvailableGroupData->numRows() > 0 ) { - ?> -

    -
      - AvailableGroupData, $this); ?> -
    -
    - -
    - '; \ No newline at end of file +echo writeGroupSection($this->data('AvailableGroups'), null, $this->data('AvailableGroupTitle'), $this->data('NoGroups'),$this->data('AllGroupButtonTitle'), $this->data('AllGroupButtonLink'),$this); diff --git a/views/groups/list.php b/views/groups/list.php index ab8f625..3f14efa 100644 --- a/views/groups/list.php +++ b/views/groups/list.php @@ -17,9 +17,8 @@ PagerModule::write($PagerOptions); echo '
    '; - if ($this->GroupData->numRows() > 0 ) { + if ($this->data('Groups')->numRows() > 0 ) { ?> -

      fetchViewLocation('groups')); ?>
    @@ -31,7 +30,7 @@ } else { ?> -
    +
    data('NoDataText'); ?>
    '; \ No newline at end of file diff --git a/views/groups/mine.php b/views/groups/mine.php new file mode 100644 index 0000000..dc47879 --- /dev/null +++ b/views/groups/mine.php @@ -0,0 +1,59 @@ +fetchViewLocation('helper_functions'); + + +echo '
    '; +echo '
    '; +echo '

    My Challenges

    '; +echo '
    '; + +if ($this->data('ChallengeGroups')) { + ?> +
      + data('ChallengeGroups'), $this); + ?> +
    + data('CountOfChallengeGroups') > 0) { + ?> +
    data('CountOfChallengeGroups').')', '/groups/mine?filter=challenge', 'MoreWrap');?>
    + +
    + +'; + +echo '
    '; +echo '
    '; +echo '

    My Groups

    '; +echo '
    '; + +if ($this->data('RegularGroups')) { + ?> +
      + data('RegularGroups'), $this); + ?> +
    + data('CountOfRegularGroups') > 0) { + ?> +
    data('CountOfRegularGroups').')', '/groups/mine/?filter=regular', 'MoreWrap');?>
    + +
    + +'; \ No newline at end of file