From b40e39fdc77b9c5912693795c7b6d5eb8d18915e Mon Sep 17 00:00:00 2001 From: Bogdanova Olga Date: Tue, 2 Mar 2021 00:03:16 +0300 Subject: [PATCH] Issues-449 --- controllers/class.groupcontroller.php | 66 +++++++- models/class.groupinvitationmodel.php | 211 ++++++++++++++++++++++++++ models/class.groupmodel.php | 59 +------ structure.php | 16 ++ views/group/accept.php | 8 + views/group/invitation_sent.php | 11 +- 6 files changed, 304 insertions(+), 67 deletions(-) create mode 100644 models/class.groupinvitationmodel.php create mode 100644 views/group/accept.php diff --git a/controllers/class.groupcontroller.php b/controllers/class.groupcontroller.php index 095395f..d37894d 100644 --- a/controllers/class.groupcontroller.php +++ b/controllers/class.groupcontroller.php @@ -3,9 +3,13 @@ * Group controller */ +use Garden\Schema\Validation; +use Garden\Schema\ValidationException; +use Garden\Web\Exception\ClientException; use Vanilla\Message; use Cocur\Slugify\Slugify; + /** * Handles accessing & displaying a single group via /group endpoint. */ @@ -405,9 +409,27 @@ public function invite($GroupID) { if(GroupModel::isMemberOfGroup($user->UserID, $GroupID)) { $this->Form->addError('User is a member of "'.$Group->Name.'".'); } else { - $this->GroupModel->invite($GroupID, $user->UserID); - $this->informMessage('Invitation was sent.'); - $this->render('invitation_sent'); + $groupInvitation['GroupID'] = $GroupID; + $groupInvitation['InviteeUserID'] = $user->UserID; + $groupInvitationModel = new GroupInvitationModel(); + $result = $groupInvitationModel->save($groupInvitation); + if($result) { + $this->informMessage('Invitation was sent.'); + } else { + $validationErrors = $groupInvitationModel->validationResults(); + $validation = new Validation(); + foreach ($validationErrors as $field => $errors) { + foreach ($errors as $error) { + $validation->addError( + $field, + $error + ); + } + } + $this->Form->addError($validation->getMessage()); + $this->render(); + } + } } catch (\Exception $e) { $this->Form->addError('Error' . $e->getMessage()); @@ -535,11 +557,41 @@ public function unwatch($GroupID) { * @param $UserID * @throws Gdn_UserException */ - public function accept($GroupID, $UserID) { - if(!GroupModel::isMemberOfGroup($UserID, $GroupID) ) { - $this->GroupModel->accept($GroupID, $UserID); + public function accept($token ='') { + + if (!Gdn::session()->isValid()) { + redirectTo(signInUrl()); } - redirectTo(GroupsPlugin::GROUP_ROUTE.$GroupID); + + $groupInvitationModel = new GroupInvitationModel(); + + $result = $groupInvitationModel->validateToken($token); + $validationErrors = $groupInvitationModel->Validation->results(); + if (count($validationErrors) > 0) { + $validation = new Validation(); + foreach ($validationErrors as $field => $errors) { + foreach ($errors as $error) { + $validation->addError( + $field, + $error + ); + } + } + if ($validation->getErrorCount() > 0) { + $this->setData('ErrorMessage', $validation->getMessage()); + $this->render(); + } + } else { + if(!GroupModel::isMemberOfGroup($result['InviteeUserID'], $result['GroupID']) ) { + $GroupModel = new GroupModel(); + $GroupModel->join($result['GroupID'],$result['InviteeUserID']); + } + $result['Status'] = 'accepted'; + $result['DateAccepted'] = Gdn_Format::toDateTime(); + $groupInvitationModel->save($result); + redirectTo(GroupsPlugin::GROUP_ROUTE.$result['GroupID']); + } + } diff --git a/models/class.groupinvitationmodel.php b/models/class.groupinvitationmodel.php new file mode 100644 index 0000000..4535df5 --- /dev/null +++ b/models/class.groupinvitationmodel.php @@ -0,0 +1,211 @@ +SQL->from('GroupInvitation gi') + ->join('Group g', 'gi.GroupID = g.GroupID') + ->join('User ibyu', 'gi.InvitedByUserID = ibyu.UserID') + ->join('User iu', 'gi.InviteeUserID = iu.UserID', 'left') + ->select('gi.*') + ->select('g.Name', '','GroupName') + ->select('iu.UserID', '', 'InviteeUserID') + ->select('iu.Email', '', 'InviteeEmail') + ->select('iu.Name', '', 'InviteeName') + ->select('ibyu.UserID', '', 'InvitedByUserID') + ->select('ibyu.Email', '', 'InvitedByEmail') + ->select('ibyu.Name', '', 'InvitedByName') + ->where('gi.GroupInvitationID', $groupInvitationID) + ->get(); + return $dataSet->firstRow(); + } + + + private function generateToken(){ + $strongResult = true; + // Returns the generated string of bytes on success, or false on failure + $randomString = openssl_random_pseudo_bytes(16, $strongResult); + if($randomString === false) { + throw new Exception('Couldn\'t generate a random string'); + } + + return bin2hex($randomString); + } + + /** + * + * + * @param array $formPostValues + * @param array|bool $settings + * @throws Exception + * @return bool|array + */ + public function save($formPostValues, $settings = false) { + $sendEmail = val('SendEmail', $settings, true); + $returnRow = val('ReturnRow', $settings, false); + $insert = $formPostValues['GroupInvitationID'] > 0? false: true; + + // Define the primary key in this model's table. + $this->defineSchema(); + + if($insert) { + $formPostValues['InvitedByUserID'] = Gdn::session()->UserID; + $formPostValues['Token'] = self::generateToken(); + + $expires = strtotime(c('Plugins.Groups.InviteExpiration', '+1 day')); + $formPostValues['DateExpires'] = Gdn_Format::toDateTime($expires); + $formPostValues['Status'] = 'pending'; + // Make sure required db fields are present. + $this->addInsertFields($formPostValues); + } else { + $this->addUpdateFields($formPostValues); + } + + if(!$insert) { + $invitationID = $this->update(['Status' => $formPostValues['Status'], 'DateAccepted' => $formPostValues['DateAccepted']], ['GroupInvitationID' => $formPostValues['GroupInvitationID']] ); + return $invitationID; + } + + // Validate the form posted values + if ($this->validate($formPostValues, $insert) === true) { + $groupModel = new GroupModel(); + // Make sure this user has permissions + $hasPermission = $groupModel->canInviteNewMember($formPostValues['GroupID']); + if (!$hasPermission) { + $this->Validation->addValidationResult('GroupID', 'You do not have permissions to invite new members.'); + return false; + } + + $now = Gdn_Format::toDateTime(); + $testData = $this->getWhere(['InviteeUserID' => $formPostValues['InviteeUserID'], 'GroupID' => $formPostValues['GroupID'], + 'Status' => 'pending', 'DateExpires >=' => $now])->result(DATASET_TYPE_ARRAY); + if (count($testData)> 0) { + // Check status + $this->Validation->addValidationResult('InviteeUserID', 'An invitation has already been sent to this user.'); + return false; + } + + // Call the base model for saving + $invitationID = parent::save($formPostValues); + + // And send the invitation email + if ($sendEmail) { + try { + $this->send($invitationID); + } catch (Exception $ex) { + $this->Validation->addValidationResult('Email', sprintf(t('Although the group invitation was created successfully, the email failed to send. The server reported the following error: %s'), strip_tags($ex->getMessage()))); + return false; + } + } + + if ($returnRow) { + return (array)$this->getByGroupInvitationID($invitationID); + } else { + return true; + } + } + return false; + } + + + + /** + * Send Group Invitation by Email + * + * @param $groupInvitationID + * @throws Exception + */ + public function send($groupInvitationID) { + $invitation = $this->getByGroupInvitationID($groupInvitationID); + $email = new Gdn_Email(); + $email->subject($invitation->InvitedByName.' invited you to '.$invitation->GroupName); + $email->to($invitation->InviteeEmail); + $greeting = 'Hello!'; + $message = $greeting.'
'. + 'You can accept or decline this invitation.'; + + $emailTemplate = $email->getEmailTemplate() + ->setTitle($invitation->InvitedByName.' invited you to '.$invitation->GroupName) + ->setMessage($message) + ->setButton(externalUrl('/group/accept/'.$invitation->Token), 'Accept' ); + $email->setEmailTemplate($emailTemplate); + + try { + $email->send(); + } catch (Exception $e) { + if (debug()) { + throw $e; + } + } + } + + /** + * Validate token + * @param $token + * @param bool $returnData + * @return array|bool|stdClass + */ + public function validateToken($token, $returnData = true){ + if(empty($token)){ + $this->Validation->addValidationResult('Token', 'Invalid token'); + return false; + } + $userID = Gdn::session()->UserID; + // One row only, token is unique + $testData = $this->getWhere(['Token' => $token])->firstRow(DATASET_TYPE_ARRAY); + if ($testData) { + if($testData['InviteeUserID'] != $userID) { + $this->Validation->addValidationResult('Token', 'Invalid token'); + return false; + } + $now = Gdn_Format::toDateTime(); + if($now > $testData['DateExpires']) { + $this->Validation->addValidationResult('Token', 'Your token has expired.'); + return false; + } + + if($returnData) { + return $testData; + } else { + return true; + } + + } else { + $this->Validation->addValidationResult('Token', 'Invalid token.'); + } + return false; + } + + /** + * {@inheritdoc} + */ + public function delete($where = [], $options = []) { + throw new Exception("Not supported"); + } + + /** + * {@inheritdoc} + */ + public function deleteID($id, $options = []) { + throw new Exception("Not supported"); + } +} \ No newline at end of file diff --git a/models/class.groupmodel.php b/models/class.groupmodel.php index dc0ef42..ca9e6c8 100644 --- a/models/class.groupmodel.php +++ b/models/class.groupmodel.php @@ -861,27 +861,6 @@ public function join($GroupID, $UserID, $watched = true, $followed = true ){ $discussionModel = new DiscussionModel(); $discussionModel->updateUserDiscussionCount($UserID, false); } - - /** - * Invite a new member - * @param $GroupID - * @param $UserID - * @return bool|Gdn_DataSet|object|string - */ - public function invite($GroupID, $UserID){ - $this->sendInviteEmail($GroupID, $UserID); - } - - /** - * Accept an invitation - * @param $GroupID - * @param $UserID - * @return bool|Gdn_DataSet|object|string - */ - public function accept($groupID, $userID){ - $this->join($groupID, $userID); - } - /** * Return true if user is a member of the group * @param $userID @@ -1503,6 +1482,10 @@ public function canManageMembers($group) { * */ public function canInviteNewMember($group) { + if(is_numeric($group) && $group > 0) { + $group = $this->getByGroupID($group); + } + if((int)$group->Archived === 1) { return false; } @@ -1753,40 +1736,6 @@ public function canDeleteDiscussion($discussion) { return false; } - /** - * Send invite email. - * - * @param int $userID - * @param string $password - */ - public function sendInviteEmail($GroupID, $userID) { - $Group = $this->getByGroupID($GroupID); - $session = Gdn::session(); - $sender = Gdn::userModel()->getID($session->UserID); - $user = Gdn::userModel()->getID($userID); - $email = new Gdn_Email(); - $email->subject($sender->Name.' invited you to '.$Group->Name); - $email->to($user->Email); - $greeting = 'Hello!'; - $message = $greeting.'
'. - 'You can accept or decline this invitation.'; - - $emailTemplate = $email->getEmailTemplate() - ->setTitle($sender->Name.' invited you to '.$Group->Name) - ->setMessage($message) - ->setButton(externalUrl('/group/accept/'.$Group->GroupID.'/'.$userID), 'Accept' ); - - $email->setEmailTemplate($emailTemplate); - - try { - $email->send(); - } catch (Exception $e) { - if (debug()) { - throw $e; - } - } - } - public function notifyNewGroup($groupID, $groupName) { $activityModel = Gdn::getContainer()->get(ActivityModel::class); $data = [ diff --git a/structure.php b/structure.php index a154de9..1dd4e1d 100644 --- a/structure.php +++ b/structure.php @@ -179,3 +179,19 @@ Gdn::structure()->table('Group') ->column('Archived', 'tinyint(1)', '0'); } + +// FIX: https://github.com/topcoder-platform/forums/issues/449 +if(!Gdn::structure()->tableExists('GroupInvitation')) { + // Group Invitation Table + Gdn::structure()->table('GroupInvitation') + ->primaryKey('GroupInvitationID') + ->column('GroupID', 'int', false, 'index') + ->column('Token', 'varchar(32)', false, 'unique') + ->column('InvitedByUserID', 'int', false, 'index') + ->column('InviteeUserID', 'int', false, 'index') + ->column('DateInserted', 'datetime', false, 'index') + ->column('Status', ['pending', 'accepted', 'declined', 'deleted']) + ->column('DateAccepted', 'datetime', true) + ->column('DateExpires', 'datetime') + ->set(false, false); +} \ No newline at end of file diff --git a/views/group/accept.php b/views/group/accept.php new file mode 100644 index 0000000..6c953fa --- /dev/null +++ b/views/group/accept.php @@ -0,0 +1,8 @@ + +

+data('ErrorMessage'); +?> + '; + ?> diff --git a/views/group/invitation_sent.php b/views/group/invitation_sent.php index af86296..c85884c 100644 --- a/views/group/invitation_sent.php +++ b/views/group/invitation_sent.php @@ -1,7 +1,8 @@

-
- Invitation was sent.
'; - ?> - \ No newline at end of file +data('ErrorMessage'); +?> +'; +?>