RSVP endpoints

POST api/v1/summits/{id}/members/me/schedule/{event_id}/rsvp

Payload

anwers: array of answer DTO

answer DTO

question_id: int
value: string or string array ( depending on type of question multivalue or not)

Required Scopes

REALM_BASE_URL/summits/write

PUT api/v1/summits/{id}/members/me/schedule/{event_id}/rsvp

Payload

anwers: array of answer DTO

answer DTO

question_id: int
value: string or string array ( depending on type of question multivalue or not)

Required Scopes

REALM_BASE_URL/summits/write

DELETE api/v1/summits/{id}/members/me/schedule/{event_id}/rsvp

Required Scopes

REALM_BASE_URL/summits/write

Change-Id: I9ea4388effd44617e5122e1b1a23c9c74473d2e6
This commit is contained in:
smarcet 2020-02-10 18:39:20 -03:00
parent fa09cc4dd8
commit e33cf7ee44
45 changed files with 1764 additions and 235 deletions

41
app/Events/RSVPAction.php Normal file
View File

@ -0,0 +1,41 @@
<?php namespace App\Events;
/**
* Copyright 2020 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use models\main\Member;
use models\summit\RSVP;
/**
* Class RSVPAction
* @package App\Events
*/
class RSVPAction extends SummitEventAction
{
/**
* @var int
*/
protected $rsvp_id;
public function __construct(RSVP $rsvp){
$this->rsvp_id = $rsvp->getId();
parent::__construct($rsvp->getEventId());
}
/**
* @return int
*/
public function getRsvpId(): int
{
return $this->rsvp_id;
}
}

View File

@ -0,0 +1,22 @@
<?php namespace App\Events;
/**
* Copyright 2020 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
/**
* Class RSVPAdd
* @package App\Events
*/
class RSVPCreated extends RSVPAction
{
}

View File

@ -0,0 +1,22 @@
<?php namespace App\Events;
/**
* Copyright 2020 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
/**
* Class RSVPUpdated
* @package App\Events
*/
class RSVPUpdated extends RSVPAction
{
}

View File

@ -11,7 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Http\Exceptions\HTTP403ForbiddenException;
use App\Http\Utils\CurrentAffiliationsCellFormatter;
use App\Http\Utils\EpochCellFormatter;
use App\Http\Utils\PagingConstants;
@ -33,6 +33,7 @@ use utils\OrderParser;
use utils\PagingInfo;
use utils\PagingResponse;
use Illuminate\Support\Facades\Input;
use Exception;
/**
* Class OAuth2SummitMembersApiController
* @package App\Http\Controllers
@ -372,54 +373,6 @@ final class OAuth2SummitMembersApiController extends OAuth2ProtectedController
}
}
/**
* @param $summit_id
* @param $member_id
* @param $event_id
* @return mixed
*/
public function deleteEventRSVP($summit_id, $member_id, $event_id){
try {
$summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->resource_server_context)->find($summit_id);
if (is_null($summit)) return $this->error404();
$current_member = $this->resource_server_context->getCurrentUser();
if (is_null($current_member)) return $this->error403();
$event = $summit->getScheduleEvent(intval($event_id));
if (is_null($event)) {
return $this->error404();
}
$this->summit_service->unRSVPEvent($summit, $current_member, $event_id);
return $this->deleted();
}
catch (ValidationException $ex1)
{
Log::warning($ex1);
return $this->error412(array( $ex1->getMessage()));
}
catch (EntityNotFoundException $ex2)
{
Log::warning($ex2);
return $this->error404(array('message' => $ex2->getMessage()));
}
catch(\HTTP401UnauthorizedException $ex3)
{
Log::warning($ex3);
return $this->error401();
}
catch (Exception $ex) {
Log::error($ex);
return $this->error500($ex);
}
}
/**
* @param $summit_id
* @return \Illuminate\Http\JsonResponse|mixed
@ -711,4 +664,222 @@ final class OAuth2SummitMembersApiController extends OAuth2ProtectedController
}
}
/**
* @param array $payload
* @return array
*/
private function validateRSVPEventUri(array $payload){
if(!isset($payload['event_uri']) || empty($payload['event_uri'])){
Log::debug("validateRSVPEventUri: event uri not set , trying to get from Referer");
$payload['event_uri'] = Request::instance()->header('Referer', null);
}
if(isset($payload['event_uri']) && !empty($payload['event_uri'])){
$allowed_return_uris = $this->resource_server_context->getAllowedReturnUris();
if(!empty($allowed_return_uris)){
Log::debug(sprintf("validateRSVPEventUri: event_uri %s allowed_return_uris %s", $payload['event_uri'], $allowed_return_uris));
// validate the event_uri against the allowed returned uris of the current client
// check using host name
$test_host = parse_url($payload['event_uri'], PHP_URL_HOST);
$valid_event_uri = false;
foreach(explode(",", $allowed_return_uris) as $allowed_uri){
if($test_host == parse_url($allowed_uri, PHP_URL_HOST)){
$valid_event_uri = true;
Log::debug(sprintf("validateRSVPEventUri: valid host %s", $test_host));
break;
}
}
if(!$valid_event_uri){
unset($payload['event_uri'] );
}
}
}
return $payload;
}
/**
* @param $summit_id
* @param $member_id
* @param $event_id
* @return \Illuminate\Http\JsonResponse|mixed
*/
public function addEventRSVP($summit_id, $member_id, $event_id){
try {
if(!Request::isJson()) return $this->error400();
$data = Input::json();
$payload = $data->all();
$summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->resource_server_context)->find($summit_id);
if (is_null($summit)) return $this->error404();
// Creates a Validator instance and validates the data.
$validation = Validator::make($payload, [
'answers' => 'sometimes|rsvp_answer_dto_array',
'event_uri' => 'sometimes|url',
]);
if ($validation->fails()) {
$messages = $validation->messages()->toArray();
return $this->error412
(
$messages
);
}
$current_member = $this->resource_server_context->getCurrentUser();
if (is_null($current_member)) return $this->error403();
$event = $summit->getScheduleEvent(intval($event_id));
if (is_null($event)) {
return $this->error404();
}
$rsvp = $this->summit_service->addRSVP($summit, $current_member, $event_id, $this->validateRSVPEventUri($payload));
return $this->created(SerializerRegistry::getInstance()->getSerializer($rsvp)->serialize
(
Request::input('expand', '')
));
}
catch (ValidationException $ex) {
Log::warning($ex);
return $this->error412(array($ex->getMessage()));
}
catch(EntityNotFoundException $ex)
{
Log::warning($ex);
return $this->error404(array('message'=> $ex->getMessage()));
}
catch (\HTTP401UnauthorizedException $ex) {
Log::warning($ex);
return $this->error401();
}
catch (HTTP403ForbiddenException $ex) {
Log::warning($ex);
return $this->error403();
}
catch (Exception $ex) {
Log::error($ex);
return $this->error500($ex);
}
}
/**
* @param $summit_id
* @param $member_id
* @param $event_id
* @return \Illuminate\Http\JsonResponse|mixed
*/
public function updateEventRSVP($summit_id, $member_id, $event_id){
try {
if(!Request::isJson()) return $this->error400();
$origin = Request::instance()->headers->get('Origin', null);
$data = Input::json();
$payload = $data->all();
$summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->resource_server_context)->find($summit_id);
if (is_null($summit)) return $this->error404();
// Creates a Validator instance and validates the data.
$validation = Validator::make($payload, [
'answers' => 'sometimes|rsvp_answer_dto_array',
'event_uri' => 'sometimes|url',
]);
if ($validation->fails()) {
$messages = $validation->messages()->toArray();
return $this->error412
(
$messages
);
}
$current_member = $this->resource_server_context->getCurrentUser();
if (is_null($current_member)) return $this->error403();
$event = $summit->getScheduleEvent(intval($event_id));
if (is_null($event)) {
return $this->error404();
}
$rsvp = $this->summit_service->updateRSVP($summit, $current_member, $event_id, $this->validateRSVPEventUri($payload));
return $this->updated(SerializerRegistry::getInstance()->getSerializer($rsvp)->serialize
(
Request::input('expand', '')
));
}
catch (ValidationException $ex) {
Log::warning($ex);
return $this->error412(array($ex->getMessage()));
}
catch(EntityNotFoundException $ex)
{
Log::warning($ex);
return $this->error404(array('message'=> $ex->getMessage()));
}
catch (\HTTP401UnauthorizedException $ex) {
Log::warning($ex);
return $this->error401();
}
catch (HTTP403ForbiddenException $ex) {
Log::warning($ex);
return $this->error403();
}
catch (Exception $ex) {
Log::error($ex);
return $this->error500($ex);
}
}
/**
* @param $summit_id
* @param $member_id
* @param $event_id
* @return \Illuminate\Http\JsonResponse|mixed
*/
public function deleteEventRSVP($summit_id, $member_id, $event_id){
try {
$summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->resource_server_context)->find($summit_id);
if (is_null($summit)) return $this->error404();
$current_member = $this->resource_server_context->getCurrentUser();
if (is_null($current_member)) return $this->error403();
$event = $summit->getScheduleEvent(intval($event_id));
if (is_null($event)) {
return $this->error404();
}
$this->summit_service->unRSVPEvent($summit, $current_member, $event_id);
return $this->deleted();
}
catch (ValidationException $ex1)
{
Log::warning($ex1);
return $this->error412(array( $ex1->getMessage()));
}
catch (EntityNotFoundException $ex2)
{
Log::warning($ex2);
return $this->error404(array('message' => $ex2->getMessage()));
}
catch(\HTTP401UnauthorizedException $ex3)
{
Log::warning($ex3);
return $this->error401();
}
catch (Exception $ex) {
Log::error($ex);
return $this->error500($ex);
}
}
}

View File

@ -186,11 +186,13 @@ class OAuth2BearerAccessTokenRequestValidator
Log::debug('setting resource server context ...');
//set context for api and continue processing
$context = [
'access_token' => $access_token_value,
'expires_in' => $token_info->getLifetime(),
'client_id' => $token_info->getClientId(),
'scope' => $token_info->getScope(),
'application_type' => $token_info->getApplicationType()
'access_token' => $access_token_value,
'expires_in' => $token_info->getLifetime(),
'client_id' => $token_info->getClientId(),
'scope' => $token_info->getScope(),
'application_type' => $token_info->getApplicationType(),
'allowed_origins' => $token_info->getAllowedOrigins(),
'allowed_return_uris' => $token_info->getAllowedReturnUris()
];
if (!is_null($token_info->getUserId()))

View File

@ -587,7 +587,13 @@ Route::group([
Route::get('', 'OAuth2SummitMembersApiController@getMemberScheduleSummitEvents')->where('member_id', 'me');
Route::group(array('prefix' => '{event_id}'), function (){
Route::delete('/rsvp', 'OAuth2SummitMembersApiController@deleteEventRSVP')->where('member_id', 'me');
Route::group(array('prefix' => 'rsvp'), function (){
Route::post('', 'OAuth2SummitMembersApiController@addEventRSVP')->where('member_id', 'me');
Route::put('', 'OAuth2SummitMembersApiController@updateEventRSVP')->where('member_id', 'me');
Route::delete('', 'OAuth2SummitMembersApiController@deleteEventRSVP')->where('member_id', 'me');
});
Route::post('', 'OAuth2SummitMembersApiController@addEventToMemberSchedule')->where('member_id', 'me');
Route::delete('', 'OAuth2SummitMembersApiController@removeEventFromMemberSchedule')->where('member_id', 'me');
});

View File

@ -0,0 +1,82 @@
<?php namespace App\Mail\Schedule;
/**
* Copyright 2020 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use models\summit\RSVP;
/**
* Class RSVPMail
* @package App\Mail\Schedule
*/
class RSVPMail extends Mailable
{
use Queueable, SerializesModels;
/**
* @var string
*/
public $owner_fullname;
/**
* @var string
*/
public $event_title;
/**
* @var string
*/
public $event_date;
/**
* @var string
*/
public $event_uri;
/**
* @var string
*/
public $confirmation_number;
/**
* @var string
*/
public $owner_email;
/**
* @var string
*/
public $summit_name;
/**
* RSVPMail constructor.
* @param RSVP $rsvp
*/
public function __construct(RSVP $rsvp)
{
$event = $rsvp->getEvent();
$summit = $event->getSummit();
$owner = $rsvp->getOwner();
$this->owner_fullname = $owner->getFullName();
$this->owner_email = $owner->getEmail();
$this->event_title = $event->getTitle();
$this->event_date = $event->getDateNice();
$this->confirmation_number = $rsvp->getConfirmationNumber();
$this->summit_name = $summit->getName();
$event_uri = $rsvp->getEventUri();
if(!empty($event_uri)){
// we got a valid origin
$this->event_uri = $event_uri;
}
}
}

View File

@ -0,0 +1,40 @@
<?php namespace App\Mail\Schedule;
use Illuminate\Support\Facades\Config;
/**
* Copyright 2020 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
/**
* Class RSVPRegularSeatMail
* @package App\Mail\Schedule
*/
class RSVPRegularSeatMail extends RSVPMail
{
/**
* Build the message.
*
* @return $this
*/
public function build()
{
$subject = sprintf("[%s] Thank you for your RSVP", $this->summit_name);
$from = Config::get("mail.from");
if(empty($from)){
throw new \InvalidArgumentException("mail.from is not set");
}
return $this->from($from)
->to($this->owner_email)
->subject($subject)
->view('emails.schedule.rsvp_regular_seat');
}
}

View File

@ -0,0 +1,40 @@
<?php namespace App\Mail\Schedule;
use Illuminate\Support\Facades\Config;
/**
* Copyright 2020 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
/**
* Class RSVPWaitListSeatMail
* @package App\Mail\Schedule
*/
class RSVPWaitListSeatMail extends RSVPMail
{
/**
* Build the message.
*
* @return $this
*/
public function build()
{
$subject = sprintf("[%s] Waitlist for Event", $this->summit_name);
$from = Config::get("mail.from");
if(empty($from)){
throw new \InvalidArgumentException("mail.from is not set");
}
return $this->from($from)
->to($this->owner_email)
->subject($subject)
->view('emails.schedule.rsvp_wait_seat');
}
}

View File

@ -11,8 +11,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Libs\ModelSerializers\AbstractSerializer;
use models\main\Member;
/**
* Class OwnMemberSerializer
* @package ModelSerializers
@ -120,9 +120,10 @@ final class OwnMemberSerializer extends AbstractMemberSerializer
}
if (!empty($expand)) {
$exp_expand = explode(',', $expand);
foreach ($exp_expand as $relation) {
switch (trim($relation)) {
foreach (explode(',', $expand) as $relation) {
$relation = trim($relation);
switch ($relation) {
case 'attendee': {
if (!is_null($attendee))
@ -180,7 +181,7 @@ final class OwnMemberSerializer extends AbstractMemberSerializer
foreach ($member->getRsvpBySummit($summit) as $rsvp){
$rsvps[] = SerializerRegistry::getInstance()
->getSerializer($rsvp)
->serialize($expand);
->serialize(AbstractSerializer::filterExpandByPrefix($relation, $expand));
}
$values['rsvp'] = $rsvps;
}

View File

@ -167,6 +167,7 @@ final class SerializerRegistry
// RSVP
$this->registry['RSVP'] = RSVPSerializer::class;
$this->registry['RSVPAnswer'] = RSVPAnswerSerializer::class;
$this->registry['RSVPTemplate'] = RSVPTemplateSerializer::class;
$this->registry['RSVPQuestionValueTemplate'] = RSVPQuestionValueTemplateSerializer::class;

View File

@ -0,0 +1,63 @@
<?php namespace ModelSerializers;
/**
* Copyright 2020 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Libs\ModelSerializers\AbstractSerializer;
use models\summit\RSVPAnswer;
/**
* Class RSVPAnswerSerializer
* @package ModelSerializers
*/
class RSVPAnswerSerializer extends SilverStripeSerializer
{
protected static $array_mappings = [
'Value' => 'value:json_string',
'RSVPId' => 'rsvp_id:json_int',
'QuestionId' => 'question_id:json_string',
'Created' => 'created:datetime_epoch',
];
/**
* @param null $expand
* @param array $fields
* @param array $relations
* @param array $params
* @return array
*/
public function serialize($expand = null, array $fields = [], array $relations = [], array $params = [])
{
$answer = $this->object;
if(! $answer instanceof RSVPAnswer) return [];
$values = parent::serialize($expand, $fields, $relations, $params);
if (!empty($expand)) {
foreach (explode(',', $expand) as $relation) {
$relation = trim($relation);
switch ($relation) {
case 'rsvp': {
unset($values['rsvp_id']);
$values['rsvp_id'] = SerializerRegistry::getInstance()->getSerializer($answer->getRsvp())->serialize(AbstractSerializer::filterExpandByPrefix($relation, $expand));
}
break;
case 'question': {
unset($values['question_id']);
$values['question'] = SerializerRegistry::getInstance()->getSerializer($answer->getQuestion())->serialize(AbstractSerializer::filterExpandByPrefix($relation, $expand));
}
break;
}
}
}
return $values;
}
}

View File

@ -11,6 +11,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Libs\ModelSerializers\AbstractSerializer;
use models\summit\RSVP;
/**
* Class RSVPSerializer
* @package ModelSerializers
@ -18,10 +20,64 @@
final class RSVPSerializer extends SilverStripeSerializer
{
protected static $array_mappings = [
'OwnerId' => 'owner_id:json_int',
'EventId' => 'event_id:json_int',
'SeatType' => 'seat_type:json_string',
'Created' => 'created:datetime_epoch',
'OwnerId' => 'owner_id:json_int',
'EventId' => 'event_id:json_int',
'SeatType' => 'seat_type:json_string',
'Created' => 'created:datetime_epoch',
'ConfirmationNumber' => 'confirmation_number:json_string',
'EventUri' => 'event_uri:json_string',
];
/**
* @param null $expand
* @param array $fields
* @param array $relations
* @param array $params
* @return array
*/
public function serialize($expand = null, array $fields = [], array $relations = [], array $params = [])
{
$rsvp = $this->object;
if(! $rsvp instanceof RSVP) return [];
$values = parent::serialize($expand, $fields, $relations, $params);
$answers = [];
foreach ($rsvp->getAnswers() as $answer){
$answers[] = $answer->getId();
}
$values['answers'] = $answers;
if (!empty($expand)) {
foreach (explode(',', $expand) as $relation) {
$relation = trim($relation);
switch ($relation) {
case 'owner': {
if(!$rsvp->hasOwner()) continue;
unset($values['owner_id']);
$values['owner'] = SerializerRegistry::getInstance()->getSerializer($rsvp->getOwner())->serialize(AbstractSerializer::filterExpandByPrefix($relation, $expand));
}
break;
case 'event': {
if(!$rsvp->hasEvent()) continue;
unset($values['event_id']);
$values['event'] = SerializerRegistry::getInstance()->getSerializer($rsvp->getEvent())->serialize(AbstractSerializer::filterExpandByPrefix($relation, $expand));
}
break;
case 'answers':{
$answers = [];
foreach ($rsvp->getAnswers() as $answer){
$answers[] = SerializerRegistry::getInstance()->getSerializer($answer)->serialize(AbstractSerializer::filterExpandByPrefix($relation, $expand));
}
$values['answers'] = $answers;
}
break;
}
}
}
return $values;
}
}

View File

@ -11,9 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Libs\ModelSerializers\AbstractSerializer;
use libs\utils\JsonUtils;
use models\summit\SummitEvent;
/**
* Class SummitEventSerializer
@ -39,13 +37,14 @@ class SummitEventSerializer extends SilverStripeSerializer
'RSVPTemplateId' => 'rsvp_template_id:json_int',
'RSVPMaxUserNumber' => 'rsvp_max_user_number:json_int',
'RSVPMaxUserWaitListNumber' => 'rsvp_max_user_wait_list_number:json_int',
'RSVPRegularCount' => 'rsvp_regular_count:json_int',
'RSVPWaitCount' => 'rsvp_wait_count:json_int',
'ExternalRSVP' => 'rsvp_external:json_boolean',
'CategoryId' => 'track_id:json_int',
'Occupancy' => 'occupancy:json_string'
];
protected static $allowed_fields = [
'id',
'title',
'description',
@ -67,6 +66,8 @@ class SummitEventSerializer extends SilverStripeSerializer
'rsvp_max_user_number',
'rsvp_max_user_wait_list_number',
'occupancy',
'rsvp_regular_count',
'rsvp_wait_count',
];
protected static $allowed_relations = [
@ -107,7 +108,7 @@ class SummitEventSerializer extends SilverStripeSerializer
$relation = trim($relation);
switch ($relation) {
case 'feedback': {
$feedback = array();
$feedback = [];
foreach ($event->getFeedback() as $f) {
$feedback[] = SerializerRegistry::getInstance()->getSerializer($f)->serialize(AbstractSerializer::filterExpandByPrefix($expand, $relation));
}
@ -121,8 +122,15 @@ class SummitEventSerializer extends SilverStripeSerializer
}
}
break;
case 'rsvp_template': {
if($event->hasRSVPTemplate()){
unset($values['rsvp_template_id']);
$values['rsvp_template'] = SerializerRegistry::getInstance()->getSerializer($event->getRSVPTemplate())->serialize(AbstractSerializer::filterExpandByPrefix($expand, $relation));
}
}
break;
case 'sponsors': {
$sponsors = array();
$sponsors = [];
foreach ($event->getSponsors() as $s) {
$sponsors[] = SerializerRegistry::getInstance()->getSerializer($s)->serialize(AbstractSerializer::filterExpandByPrefix($expand, $relation));
}

View File

@ -11,10 +11,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Doctrine\Common\Collections\Criteria;
use App\Models\Foundation\Summit\Events\RSVP\RSVPQuestionTemplate;
use models\exceptions\ValidationException;
use models\main\Member;
use models\utils\SilverstripeBaseModel;
use Doctrine\ORM\Mapping AS ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping AS ORM;
/**
* @ORM\Entity
* @ORM\Table(name="RSVP")
@ -24,37 +27,69 @@ use Doctrine\Common\Collections\ArrayCollection;
*/
class RSVP extends SilverstripeBaseModel
{
public function __construct()
{
parent::__construct();
$this->answers = new ArrayCollection();
}
const SeatTypeRegular = 'Regular';
const SeatTypeWaitList = 'WaitList';
const ValidSeatTypes = [self::SeatTypeRegular, self::SeatTypeWaitList];
/**
* @ORM\ManyToOne(targetEntity="models\main\Member", inversedBy="rsvp", fetch="LAZY")
* @ORM\Column(name="SeatType", type="string")
* @var string
*/
protected $seat_type;
/**
* @ORM\Column(name="EventUri", type="string")
* @var string
*/
protected $event_uri;
/**
* @ORM\Column(name="BeenEmailed", type="boolean")
* @var bool
*/
protected $been_emailed;
/**
* @ORM\ManyToOne(targetEntity="models\main\Member", inversedBy="rsvp")
* @ORM\JoinColumn(name="SubmittedByID", referencedColumnName="ID", onDelete="CASCADE")
* @var Member
*/
private $owner;
/**
* @ORM\ManyToOne(targetEntity="models\summit\SummitEvent", inversedBy="rsvp", fetch="LAZY")
* @ORM\ManyToOne(targetEntity="models\summit\SummitEvent", inversedBy="rsvp")
* @ORM\JoinColumn(name="EventID", referencedColumnName="ID", onDelete="CASCADE")
* @var SummitEvent
*/
private $event;
/**
* @ORM\Column(name="SeatType", type="string")
*/
protected $seat_type;
/**
* @ORM\OneToMany(targetEntity="models\summit\RSVPAnswer", mappedBy="rsvp", cascade={"persist", "remove"})
* @ORM\OneToMany(targetEntity="models\summit\RSVPAnswer", mappedBy="rsvp", cascade={"persist", "remove"}, orphanRemoval=true)
* @var RSVPAnswer[]
*/
protected $answers;
/**
* RSVP constructor.
*/
public function __construct()
{
parent::__construct();
$this->seat_type = null;
$this->answers = new ArrayCollection();
$this->been_emailed = false;
$this->event_uri = null;
}
/**
* @return bool
*/
public function hasSeatTypeSet():bool{
return !empty($this->seat_type);
}
/**
* @return ArrayCollection
*/
@ -86,7 +121,6 @@ class RSVP extends SilverstripeBaseModel
$this->owner = $owner;
}
/**
* @return SummitEvent
*/
@ -107,6 +141,13 @@ class RSVP extends SilverstripeBaseModel
}
}
/**
* @return bool
*/
public function hasEvent(){
return $this->getEventId() > 0;
}
/**
* @return bool
*/
@ -134,19 +175,102 @@ class RSVP extends SilverstripeBaseModel
}
/**
* @return mixed
* @return string
*/
public function getSeatType()
public function getSeatType():?string
{
return $this->seat_type;
}
/**
* @param mixed $seat_type
* @param string $seat_type
* @throws ValidationException
*/
public function setSeatType($seat_type)
public function setSeatType(string $seat_type)
{
if(!in_array($seat_type, self::ValidSeatTypes))
throw new ValidationException(sprintf("Seat type %s is not valid."), $seat_type);
$this->seat_type = $seat_type;
}
/**
* @return bool
*/
public function isBeenEmailed(): bool
{
return $this->been_emailed;
}
/**
* @param bool $been_emailed
*/
public function setBeenEmailed(bool $been_emailed): void
{
$this->been_emailed = $been_emailed;
}
public function clearEvent(){
$this->event = null;
}
public function clearOwner(){
$this->owner = null;
}
/**
* @param RSVPQuestionTemplate $question
* @return RSVPAnswer|null
*/
public function findAnswerByQuestion(RSVPQuestionTemplate $question):?RSVPAnswer{
$criteria = Criteria::create();
$criteria = $criteria->where(Criteria::expr()->eq('question', $question));
$answer = $this->answers->matching($criteria)->first();
return !$answer ? null:$answer;
}
public function addAnswer(RSVPAnswer $answer){
if($this->answers->contains($answer)) return;
$this->answers->add($answer);
$answer->setRsvp($this);
}
public function removeAnswer(RSVPAnswer $answer){
if(!$this->answers->contains($answer)) return;
$this->answers->removeElement($answer);
$answer->clearRSVP();
}
public function clearAnswers(){
$this->answers->clear();
}
/**
* @return string|null
*/
public function getConfirmationNumber():?string{
if(!$this->hasEvent()) return null;
if(!$this->getEvent()->hasSummit()) return null;
$summit = $this->event->getSummit();
$summit_title = substr($summit->getName(),0,3);
$summit_year = $summit->getLocalBeginDate()->format('y');
return strtoupper($summit_title).$summit_year.$this->id;
}
/**
* @return string
*/
public function getEventUri(): ?string
{
return $this->event_uri;
}
/**
* @param string $event_uri
*/
public function setEventUri(string $event_uri): void
{
$this->event_uri = $event_uri;
}
}

View File

@ -11,11 +11,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Models\Foundation\Summit\Events\RSVP\RSVPMultiValueQuestionTemplate;
use App\Models\Foundation\Summit\Events\RSVP\RSVPQuestionTemplate;
use models\utils\SilverstripeBaseModel;
use Doctrine\ORM\Mapping AS ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @ORM\Entity
* @ORM\Table(name="RSVPAnswer")
@ -24,13 +23,6 @@ use Doctrine\Common\Collections\ArrayCollection;
*/
class RSVPAnswer extends SilverstripeBaseModel
{
/**
* @ORM\ManyToOne(targetEntity="models\summit\RSVP", inversedBy="answers", fetch="LAZY")
* @ORM\JoinColumn(name="RSVPID", referencedColumnName="ID", onDelete="CASCADE")
* @var SummitAttendee
*/
private $rsvp;
/**
* @ORM\Column(name="Value", type="string")
* @var string
@ -38,34 +30,35 @@ class RSVPAnswer extends SilverstripeBaseModel
private $value;
/**
* @return SummitAttendee
* @ORM\ManyToOne(targetEntity="models\summit\RSVP", inversedBy="answers")
* @ORM\JoinColumn(name="RSVPID", referencedColumnName="ID", onDelete="CASCADE")
* @var RSVP
*/
public function getRsvp()
{
return $this->rsvp;
}
private $rsvp;
/**
* @param SummitAttendee $rsvp
* @ORM\ManyToOne(targetEntity="App\Models\Foundation\Summit\Events\RSVP\RSVPQuestionTemplate")
* @ORM\JoinColumn(name="QuestionID", referencedColumnName="ID")
* @var RSVPQuestionTemplate
*/
public function setRsvp($rsvp)
{
$this->rsvp = $rsvp;
}
private $question;
/**
* @return string
* @return string|null
*/
public function getValue()
public function getValue():?string
{
return $this->value;
}
/**
* @param string $value
* @param array|string $value
*/
public function setValue($value)
{
if (is_array($value)) {
$value = implode(',', $value);
}
$this->value = $value;
}
@ -74,20 +67,65 @@ class RSVPAnswer extends SilverstripeBaseModel
*/
public function getQuestionId()
{
return $this->question_id;
try{
return $this->question->getId();
}
catch(\Exception $ex){
return 0;
}
}
/**
* @param int $question_id
* @return int
*/
public function setQuestionId($question_id)
public function getRSVPId()
{
$this->question_id = $question_id;
try{
return $this->rsvp->getId();
}
catch(\Exception $ex){
return 0;
}
}
/**
* @ORM\Column(name="QuestionID", type="integer")
* @var int
* @return RSVP
*/
private $question_id;
public function getRsvp(): RSVP
{
return $this->rsvp;
}
/**
* @param RSVP $rsvp
*/
public function setRsvp(RSVP $rsvp): void
{
$this->rsvp = $rsvp;
}
/**
* @return RSVPQuestionTemplate
*/
public function getQuestion(): RSVPQuestionTemplate
{
return $this->question;
}
/**
* @param RSVPQuestionTemplate $question
*/
public function setQuestion(RSVPQuestionTemplate $question): void
{
$this->question = $question;
}
public function clearRSVP(){
$this->rsvp = null;
}
public function clearQuestion(){
$this->question = null;
}
}

View File

@ -38,4 +38,18 @@ class RSVPCheckBoxListQuestionTemplate extends RSVPMultiValueQuestionTemplate
public static function getMetadata(){
return array_merge(RSVPMultiValueQuestionTemplate::getMetadata(), self::$metadata);
}
/**
* @param array|string $value
* @return bool
*/
public function isValidValue($value): bool
{
if(!is_array($value)) return false;
foreach($value as $valId){
$val = $this->getValueById(intval($valId));
if(is_null($val)) return false;
}
return true;
}
}

View File

@ -117,4 +117,25 @@ class RSVPDropDownQuestionTemplate extends RSVPMultiValueQuestionTemplate
public static function getMetadata(){
return array_merge(RSVPMultiValueQuestionTemplate::getMetadata(), self::$metadata);
}
/**
* @param array|string $value
* @return bool
*/
public function isValidValue($value): bool
{
if(!$this->is_multiselect){
if(!is_string($value)) return false;
$valId = intval($value);
$val = $this->getValueById($valId);
if(is_null($val)) return false;
return true;
}
if(!is_array($value)) return false;
foreach($value as $valId){
$val = $this->getValueById(intval($valId));
if(is_null($val)) return false;
}
return true;
}
}

View File

@ -38,4 +38,17 @@ class RSVPMemberEmailQuestionTemplate extends RSVPTextBoxQuestionTemplate
public static function getMetadata(){
return array_merge(RSVPTextBoxQuestionTemplate::getMetadata(), self::$metadata);
}
/**
* @param array|string $value
* @return bool
*/
public function isValidValue($value): bool
{
if(!is_string($value)) return false;
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
return false;
}
return true;
}
}

View File

@ -38,4 +38,14 @@ class RSVPMemberFirstNameQuestionTemplate extends RSVPTextBoxQuestionTemplate
public static function getMetadata(){
return array_merge(RSVPTextBoxQuestionTemplate::getMetadata(), self::$metadata);
}
/**
* @param array|string $value
* @return bool
*/
public function isValidValue($value): bool
{
if(!is_string($value)) return false;
return true;
}
}

View File

@ -38,4 +38,14 @@ class RSVPMemberLastNameQuestionTemplate extends RSVPTextBoxQuestionTemplate
public static function getMetadata(){
return array_merge(RSVPTextBoxQuestionTemplate::getMetadata(), self::$metadata);
}
/**
* @param array|string $value
* @return bool
*/
public function isValidValue($value): bool
{
if(!is_string($value)) return false;
return true;
}
}

View File

@ -29,20 +29,20 @@ class RSVPMultiValueQuestionTemplate extends RSVPQuestionTemplate
* @ORM\Column(name="EmptyString", type="string")
* @var string
*/
private $empty_string;
protected $empty_string;
/**
* @ORM\OneToMany(targetEntity="RSVPQuestionValueTemplate", mappedBy="owner", cascade={"persist", "remove"}, orphanRemoval=true)
* @var RSVPQuestionValueTemplate[]
*/
private $values;
protected $values;
/**
* @ORM\ManyToOne(targetEntity="RSVPQuestionValueTemplate", fetch="EXTRA_LAZY")
* @ORM\JoinColumn(name="DefaultValueID", referencedColumnName="ID")
* @var RSVPQuestionValueTemplate
*/
private $default_value;
protected $default_value;
/**
* @return string

View File

@ -213,4 +213,12 @@ class RSVPQuestionTemplate extends SilverstripeBaseModel implements IOrderable
return self::$metadata;
}
/**
* @param array|string|null $value
* @return bool
*/
public function isValidValue($value):bool {
return true;
}
}

View File

@ -38,4 +38,17 @@ class RSVPRadioButtonListQuestionTemplate extends RSVPMultiValueQuestionTemplate
public static function getMetadata(){
return array_merge(RSVPMultiValueQuestionTemplate::getMetadata(), self::$metadata);
}
/**
* @param array|string $value
* @return bool
*/
public function isValidValue($value): bool
{
if(!is_string($value)) return false;
$valId = intval($value);
$val = $this->getValueById($valId);
if(is_null($val)) return false;
return true;
}
}

View File

@ -62,4 +62,15 @@ class RSVPSingleValueTemplateQuestion extends RSVPQuestionTemplate
public static function getMetadata(){
return array_merge(RSVPQuestionTemplate::getMetadata(), self::$metadata);
}
/**
* @param array|string $value
* @return bool
*/
public function isValidValue($value): bool
{
if(empty($value) && !$this->is_mandatory) return true;
if(!is_string($value)) return false;
return true;
}
}

View File

@ -134,7 +134,7 @@ class RSVPTemplate extends SilverstripeBaseModel
}
/**
* @return RSVPQuestionTemplate[]
* @return ArrayCollection|\Doctrine\Common\Collections\Collection
*/
public function getQuestions()
{

View File

@ -38,4 +38,14 @@ class RSVPTextAreaQuestionTemplate extends RSVPSingleValueTemplateQuestion
public static function getMetadata(){
return array_merge(RSVPSingleValueTemplateQuestion::getMetadata(), self::$metadata);
}
/**
* @param array|string $value
* @return bool
*/
public function isValidValue($value): bool
{
if(!is_string($value)) return false;
return true;
}
}

View File

@ -38,4 +38,5 @@ class RSVPTextBoxQuestionTemplate extends RSVPSingleValueTemplateQuestion
public static function getMetadata(){
return array_merge(RSVPSingleValueTemplateQuestion::getMetadata(), self::$metadata);
}
}

View File

@ -957,4 +957,120 @@ class SummitEvent extends SilverstripeBaseModel
$this->external_id = $external_id;
}
/**
* @return string
* @throws ValidationException
*/
public function getCurrentRSVPSubmissionSeatType():string{
if(!$this->hasRSVPTemplate())
throw new ValidationException(sprintf("Event %s has not RSVP configured.", $this->id));
if(!$this->getRSVPTemplate()->isEnabled()){
throw new ValidationException(sprintf("Event %s has not RSVP configured.", $this->id));
}
$count_regular = $this->getRSVPSeatTypeCount(RSVP::SeatTypeRegular);
if($count_regular < intval($this->rsvp_max_user_number)) return RSVP::SeatTypeRegular;
$count_wait = $this->getRSVPSeatTypeCount(RSVP::SeatTypeWaitList);
if($count_wait < intval($this->rsvp_max_user_wait_list_number)) return RSVP::SeatTypeWaitList;
throw new ValidationException(sprintf("Event %s is Full.", $this->id));
}
/**
* @param string $seat_type
* @return int
*/
public function getRSVPSeatTypeCount(string $seat_type):int{
$criteria = Criteria::create();
$criteria = $criteria->where(Criteria::expr()->eq('seat_type', $seat_type));
return $this->rsvp->matching($criteria)->count();
}
/**
* @param string $seat_type
* @return bool
*/
public function couldAddSeatType(string $seat_type):bool{
switch($seat_type){
case RSVP::SeatTypeRegular: {
$count_regular = $this->getRSVPSeatTypeCount(RSVP::SeatTypeRegular);
return $count_regular < intval($this->rsvp_max_user_number);
}
case RSVP::SeatTypeWaitList: {
$count_wait = $this->getRSVPSeatTypeCount(RSVP::SeatTypeWaitList);
return $count_wait < intval($this->rsvp_max_user_wait_list_number);
}
}
return false;
}
public function getRSVPRegularCount():?int{
return $this->getRSVPSeatTypeCount(RSVP::SeatTypeRegular);
}
public function getRSVPWaitCount():?int{
return $this->getRSVPSeatTypeCount(RSVP::SeatTypeWaitList);
}
/**
* @param RSVP $rsvp
* @throws ValidationException
*/
public function addRSVPSubmission(RSVP $rsvp){
if(!$this->hasRSVPTemplate()){
throw new ValidationException(sprintf("Event %s has not RSVP configured.", $this->id));
}
if(!$this->getRSVPTemplate()->isEnabled()){
throw new ValidationException(sprintf("Event %s has not RSVP configured.", $this->id));
}
if($this->rsvp->contains($rsvp)) return;
$this->rsvp->add($rsvp);
$rsvp->setEvent($this);
}
/**
* @param RSVP $rsvp
*/
public function removeRSVPSubmission(RSVP $rsvp){
if(!$this->rsvp->contains($rsvp)) return;
$this->rsvp->removeElement($rsvp);
$rsvp->clearEvent();
}
/**
* @return string
*/
public function getStartDateNice():string
{
$start_date = $this->getLocalStartDate();
if(empty($start_date)) return 'TBD';
return $start_date->format("Y-m-d H:i:s");
}
/**
* @return string
*/
public function getEndDateNice():string
{
$end_date = $this->getLocalEndDate();
if(empty($end_date)) return 'TBD';
return $end_date->format("Y-m-d H:i:s");
}
/**
* @return string
*/
public function getDateNice():string {
$start_date = $this->getStartDateNice();
$end_date = $this->getEndDateNice();
$date_nice = '';
if ($start_date == 'TBD' || $end_date == 'TBD') return $start_date;
$date_nice = date('l, F j, g:ia', strtotime($start_date)).'-'.date('g:ia', strtotime($end_date));
return $date_nice;
}
}

View File

@ -0,0 +1,104 @@
<?php namespace App\Models\Foundation\Summit\Factories;
/**
* Copyright 2020 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Models\Foundation\Summit\Events\RSVP\RSVPMultiValueQuestionTemplate;
use App\Models\Foundation\Summit\Events\RSVP\RSVPQuestionTemplate;
use models\main\Member;
use models\summit\RSVP;
use models\summit\RSVPAnswer;
use models\summit\SummitEvent;
use models\exceptions\ValidationException;
/**
* Class SummitRSVPFactory
* @package App\Models\Foundation\Summit\Factories
*/
final class SummitRSVPFactory
{
/**
* @param SummitEvent $summitEvent
* @param Member $owner
* @param array $data
* @return RSVP
* @throws ValidationException
*/
public static function build(SummitEvent $summitEvent, Member $owner, array $data):RSVP{
return self::populate(new RSVP, $summitEvent, $owner, $data);
}
/**
* @param RSVP $rsvp
* @param SummitEvent $summitEvent
* @param Member $owner
* @param array $data
* @return RSVP
* @throws ValidationException
*/
public static function populate(RSVP $rsvp, SummitEvent $summitEvent, Member $owner, array $data):RSVP {
$rsvp->setOwner($owner);
if(!$rsvp->hasSeatTypeSet())
$rsvp->setSeatType($summitEvent->getCurrentRSVPSubmissionSeatType());
$template = $summitEvent->getRSVPTemplate();
if(isset($data['event_uri']) && !empty($data['event_uri'])){
$rsvp->setEventUri($data['event_uri']);
}
$answers = $data['answers'] ?? [];
// restructuring for a quick search
if(count($answers)){
$bucket = [];
foreach ($answers as $answer_dto){
$bucket[intval($answer_dto['question_id'])] = $answer_dto;
}
$answers = $bucket;
}
foreach($template->getQuestions() as $question){
if(!$question instanceof RSVPQuestionTemplate) continue;
$answer_dto = $answers[$question->getId()] ?? null;
$value = $answer_dto['value'] ?? null;
if($question->isMandatory() &&
(
is_null($value) ||
(is_string($value) && empty($value)) ||
(is_array($value)) && count($value) == 0
)
)
throw new ValidationException(sprintf("Question '%s' is mandatory.", $question->getLabel()));
$answer = $rsvp->findAnswerByQuestion($question);
if(is_null($answer))
$answer = new RSVPAnswer();
if(!$question->isValidValue($value))
throw new ValidationException(sprintf("Value is not valid for Question '%s'.", $question->getLabel()));
$answer->setValue($value);
$answer->setQuestion($question);
$rsvp->addAnswer($answer);
}
$summitEvent->addRSVPSubmission($rsvp);
return $rsvp;
}
}

View File

@ -1,20 +1,17 @@
<?php namespace models\oauth2;
use models\main\Member;
/**
* Copyright 2015 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
/**
* Copyright 2015 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use models\main\Member;
/**
* Interface IResourceServerContext
* Current Request OAUTH2 security context
@ -72,5 +69,15 @@ interface IResourceServerContext
/**
* @return Member|null
*/
public function getCurrentUser():?Member;
public function getCurrentUser(): ?Member;
/**
* @return null|string
*/
public function getAllowedOrigins();
/**
* @return null|string
*/
public function getAllowedReturnUris();
}

View File

@ -87,6 +87,22 @@ final class ResourceServerContext implements IResourceServerContext
return $this->getAuthContextVar('access_token');
}
/**
* @return null|string
*/
public function getAllowedOrigins()
{
return $this->getAuthContextVar('allowed_origins');
}
/**
* @return null|string
*/
public function getAllowedReturnUris()
{
return $this->getAuthContextVar('allowed_return_uris');
}
/**
* @return null|string
*/

View File

@ -82,6 +82,17 @@ class AppServiceProvider extends ServiceProvider
'groups' => 'sometimes|int_array',
];
static $rsvp_answer_dto_fields = [
'question_id',
'value',
];
static $rsvp_answer_validation_rules = [
'question_id' => 'required|integer',
'value' => 'sometimes|string_or_string_array',
];
/**
* Bootstrap any application services.
* @return void
@ -178,10 +189,32 @@ class AppServiceProvider extends ServiceProvider
$validator->addReplacer('string_array', function($message, $attribute, $rule, $parameters) use ($validator) {
return sprintf("%s should be an array of strings", $attribute);
});
if(!is_array($value)) return false;
if(!is_array($value))
return false;
foreach($value as $element)
{
if(!is_string($element)) return false;
if(!is_string($element))
return false;
}
return true;
});
Validator::extend('string_or_string_array', function($attribute, $value, $parameters, $validator)
{
$validator->addReplacer('string_or_string_array', function($message, $attribute, $rule, $parameters) use ($validator) {
return sprintf("%s should be a string or an array of strings", $attribute);
});
if(is_string($value))
return true;
if(!is_array($value))
return false;
foreach($value as $element)
{
if(!is_string($element))
return false;
}
return true;
});
@ -618,6 +651,31 @@ class AppServiceProvider extends ServiceProvider
Validator::extend('greater_than', function ($attribute, $value, $otherValue) {
return intval($value) > intval($otherValue[0]);
});
Validator::extend('rsvp_answer_dto_array', function($attribute, $value, $parameters, $validator)
{
$validator->addReplacer('rsvp_answer_dto_array', function($message, $attribute, $rule, $parameters) use ($validator) {
return sprintf
(
"%s should be an array of rsvp answer data {question_id : int, value: string, values: string array}",
$attribute);
});
if(!is_array($value)) return false;
foreach($value as $element)
{
foreach($element as $key => $element_val){
if(!in_array($key, self::$rsvp_answer_dto_fields))
return false;
}
// Creates a Validator instance and validates the data.
$validation = Validator::make($element, self::$rsvp_answer_validation_rules);
if($validation->fails())
return false;
}
return true;
});
}
/**

View File

@ -14,6 +14,8 @@
use App\EntityPersisters\AdminSummitEventActionSyncWorkRequestPersister;
use App\EntityPersisters\AdminSummitLocationActionSyncWorkRequestPersister;
use App\EntityPersisters\EntityEventPersister;
use App\Events\RSVPCreated;
use App\Events\RSVPUpdated;
use App\Factories\CalendarAdminActionSyncWorkRequest\AdminSummitLocationActionSyncWorkRequestFactory;
use App\Factories\CalendarAdminActionSyncWorkRequest\SummitEventDeletedCalendarSyncWorkRequestFactory;
use App\Factories\CalendarAdminActionSyncWorkRequest\SummitEventUpdatedCalendarSyncWorkRequestFactory;
@ -44,13 +46,16 @@ use App\Mail\BookableRoomReservationPaymentConfirmedEmail;
use App\Mail\BookableRoomReservationRefundAcceptedEmail;
use App\Mail\BookableRoomReservationRefundRequestedAdminEmail;
use App\Mail\BookableRoomReservationRefundRequestedOwnerEmail;
use App\Mail\Schedule\RSVPRegularSeatMail;
use App\Mail\Schedule\RSVPWaitListSeatMail;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Mail;
use LaravelDoctrine\ORM\Facades\EntityManager;
use models\main\Member;
use models\summit\RSVP;
use models\summit\SummitRoomReservation;
/**
* Class EventServiceProvider
* @package App\Providers
@ -376,5 +381,39 @@ final class EventServiceProvider extends ServiceProvider
if(is_null($reservation) || ! $reservation instanceof SummitRoomReservation) return;
Mail::send(new BookableRoomReservationCanceledEmail($reservation));
});
Event::listen(RSVPCreated::class, function($event){
if(!$event instanceof RSVPCreated) return;
$rsvp_id = $event->getRsvpId();
$rsvp_repository = EntityManager::getRepository(RSVP::class);
$rsvp = $rsvp_repository->find($rsvp_id);
if(is_null($rsvp) || ! $rsvp instanceof RSVP) return;
if($rsvp->getSeatType() == RSVP::SeatTypeRegular)
Mail::send(new RSVPRegularSeatMail($rsvp));
if($rsvp->getSeatType() == RSVP::SeatTypeWaitList)
Mail::send(new RSVPWaitListSeatMail($rsvp));
});
Event::listen(RSVPUpdated::class, function($event){
if(!$event instanceof RSVPUpdated) return;
$rsvp_id = $event->getRsvpId();
$rsvp_repository = EntityManager::getRepository(RSVP::class);
$rsvp = $rsvp_repository->find($rsvp_id);
if(is_null($rsvp) || ! $rsvp instanceof RSVP) return;
if($rsvp->getSeatType() == RSVP::SeatTypeRegular)
Mail::send(new RSVPRegularSeatMail($rsvp));
if($rsvp->getSeatType() == RSVP::SeatTypeWaitList)
Mail::send(new RSVPWaitListSeatMail($rsvp));
});
}
}

View File

@ -17,6 +17,7 @@ use models\exceptions\ValidationException;
use models\main\File;
use models\main\Member;
use models\summit\ConfirmationExternalOrderRequest;
use models\summit\RSVP;
use models\summit\Summit;
use models\summit\SummitAttendee;
use models\summit\SummitBookableVenueRoomAttributeType;
@ -143,14 +144,6 @@ interface ISummitService
*/
public function addEventToMemberFavorites(Summit $summit, Member $member, $event_id);
/**
* @param Summit $summit
* @param Member $member
* @param $event_id
* @return bool
*/
public function unRSVPEvent(Summit $summit, Member $member, $event_id);
/**
* @param Summit $summit
* @param int $event_id
@ -352,4 +345,36 @@ interface ISummitService
* @throws EntityNotFoundException
*/
public function deleteSummitLogo(int $summit_id):void;
/**
* @param Summit $summit
* @param Member $member
* @param int $event_id
* @param array $data
* @return RSVP
* @throws ValidationException
* @throws EntityNotFoundException
*/
public function addRSVP(Summit $summit, Member $member, int $event_id, array $data):RSVP;
/**
* @param Summit $summit
* @param Member $member
* @param int $event_id
* @param array $data
* @return RSVP
* @throws ValidationException
* @throws EntityNotFoundException
*/
public function updateRSVP(Summit $summit, Member $member, int $event_id, array $data):RSVP;
/**
* @param Summit $summit
* @param Member $member
* @param int $event_id
* @return bool
* @throws ValidationException
* @throws EntityNotFoundException
*/
public function unRSVPEvent(Summit $summit, Member $member, int $event_id);
}

View File

@ -15,10 +15,13 @@ use App\Events\MyFavoritesAdd;
use App\Events\MyFavoritesRemove;
use App\Events\MyScheduleAdd;
use App\Events\MyScheduleRemove;
use App\Events\RSVPCreated;
use App\Events\RSVPUpdated;
use App\Events\SummitDeleted;
use App\Events\SummitUpdated;
use App\Http\Utils\IFileUploader;
use App\Models\Foundation\Summit\Factories\SummitFactory;
use App\Models\Foundation\Summit\Factories\SummitRSVPFactory;
use App\Models\Foundation\Summit\Repositories\IDefaultSummitEventTypeRepository;
use App\Models\Utils\IntervalParser;
use App\Permissions\IPermissionsManager;
@ -52,6 +55,7 @@ use models\summit\ISummitEventRepository;
use models\summit\ISummitRepository;
use models\summit\Presentation;
use models\summit\PresentationType;
use models\summit\RSVP;
use models\summit\Summit;
use models\summit\SummitAttendee;
use models\summit\SummitAttendeeTicket;
@ -1227,37 +1231,6 @@ final class SummitService extends AbstractService implements ISummitService
});
}
/**
* @param Summit $summit
* @param Member $member
* @param $event_id
* @return bool
*/
public function unRSVPEvent(Summit $summit, Member $member, $event_id)
{
return $this->tx_service->transaction(function () use ($summit, $member, $event_id) {
$event = $summit->getScheduleEvent($event_id);
if (is_null($event)) {
throw new EntityNotFoundException('event not found on summit!');
}
if(!Summit::allowToSee($event, $member))
throw new EntityNotFoundException('event not found on summit!');
$rsvp = $member->getRsvpByEvent($event_id);
if(is_null($rsvp))
throw new ValidationException(sprintf("rsvp for event id %s does not exist for your member", $event_id));
$this->rsvp_repository->delete($rsvp);
$this->removeEventFromMemberSchedule($summit, $member, $event_id ,false);
return true;
});
}
/**
* @param Summit $summit
* @param int $event_id
@ -2128,4 +2101,158 @@ final class SummitService extends AbstractService implements ISummitService
$summit->clearLogo();
});
}
/**
* @param Summit $summit
* @param Member $member
* @param int $event_id
* @param array $data
* @return RSVP
* @throws Exception
*/
public function addRSVP(Summit $summit, Member $member, int $event_id, array $data): RSVP
{
$rsvp = $this->tx_service->transaction(function () use ($summit, $member, $event_id, $data) {
$event = $this->event_repository->getByIdExclusiveLock($event_id);
if (is_null($event) || !$event instanceof SummitEvent) {
throw new EntityNotFoundException('Event not found on summit.');
}
if ($event->getSummitId() != $summit->getId()) {
throw new EntityNotFoundException('Event not found on summit.');
}
if(!Summit::allowToSee($event, $member))
throw new EntityNotFoundException('Event not found on summit.');
if (!$event->hasRSVPTemplate()) {
throw new EntityNotFoundException('Event not found on summit.');
}
// add to schedule the RSVP event
if (!$member->isOnSchedule($event)) {
$this->addEventToMemberSchedule($summit, $member, $event_id, false);
}
$old_rsvp = $member->getRsvpByEvent($event_id);
if (!is_null($old_rsvp))
throw new ValidationException
(
sprintf
(
"Member %s already submitted an rsvp for event %s on summit %s.",
$member->getId(),
$event_id,
$summit->getId()
)
);
// create RSVP
return SummitRSVPFactory::build($event, $member, $data);
});
Event::fire(new RSVPCreated($rsvp));
return $rsvp;
}
/**
* @param Summit $summit
* @param Member $member
* @param int $event_id
* @param array $data
* @return RSVP
* @throws Exception
*/
public function updateRSVP(Summit $summit, Member $member, int $event_id, array $data): RSVP
{
return $this->tx_service->transaction(function () use ($summit, $member, $event_id, $data) {
$event = $this->event_repository->getByIdExclusiveLock($event_id);
if (is_null($event) || !$event instanceof SummitEvent) {
throw new EntityNotFoundException('Event not found on summit.');
}
if ($event->getSummitId() != $summit->getId()) {
throw new EntityNotFoundException('Event not found on summit.');
}
if (!Summit::allowToSee($event, $member))
throw new EntityNotFoundException('Event not found on summit.');
if (!$event->hasRSVPTemplate()) {
throw new EntityNotFoundException('Event not found on summit.');
}
// add to schedule the RSVP event
if (!$member->isOnSchedule($event)) {
throw new EntityNotFoundException('Event not found on summit.');
}
$rsvp = $member->getRsvpByEvent($event->getId());
if (is_null($rsvp))
throw new ValidationException
(
sprintf
(
"Member %s did not submitted an rsvp for event %s on summit %s.",
$member->getId(),
$event_id,
$summit->getId()
)
);
// update RSVP
$rsvp = SummitRSVPFactory::populate($rsvp, $event, $member, $data);
Event::fire(new RSVPUpdated($rsvp));
return $rsvp;
});
}
/**
* @param Summit $summit
* @param Member $member
* @param int $event_id
* @return bool|mixed
* @throws Exception
*/
public function unRSVPEvent(Summit $summit, Member $member, int $event_id)
{
return $this->tx_service->transaction(function () use ($summit, $member, $event_id) {
$event = $this->event_repository->getByIdExclusiveLock($event_id);
if (is_null($event) || !$event instanceof SummitEvent) {
throw new EntityNotFoundException('Event not found on summit.');
}
if ($event->getSummitId() != $summit->getId()) {
throw new EntityNotFoundException('Event not found on summit.');
}
if(!Summit::allowToSee($event, $member))
throw new EntityNotFoundException('Event not found on summit.');
$rsvp = $member->getRsvpByEvent($event_id);
if(is_null($rsvp))
throw new ValidationException(sprintf("RSVP for event id %s does not exist for your member.", $event_id));
$this->rsvp_repository->delete($rsvp);
$this->removeEventFromMemberSchedule($summit, $member, $event_id ,false);
return true;
});
}
}

13
config/schedule.php Normal file
View File

@ -0,0 +1,13 @@
<?php
/**
* Copyright 2020 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

View File

@ -1,6 +1,4 @@
<?php
namespace Database\Migrations\Model;
<?php namespace Database\Migrations\Model;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -0,0 +1,49 @@
<?php namespace Database\Migrations\Model;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Doctrine\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema as Schema;
use LaravelDoctrine\Migrations\Schema\Builder;
use LaravelDoctrine\Migrations\Schema\Table;
/**
* Class Version20200212125943
* @package Database\Migrations\Model
*/
class Version20200212125943 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
$builder = new Builder($schema);
if($schema->hasTable("RSVP") && !$builder->hasColumn("RSVP", "EventUri")) {
$builder->table('RSVP', function (Table $table) {
$table->string("EventUri")->setNotnull(false);
});
}
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
$builder = new Builder($schema);
if($schema->hasTable("RSVP") && $builder->hasColumn("RSVP", "EventUri")) {
$builder->table('RSVP', function (Table $table) {
$table->dropColumn("EventUri");
});
}
}
}

View File

@ -3953,11 +3953,29 @@ class ApiEndpointsSeeder extends Seeder
'http_method' => 'POST',
'scopes' => [sprintf('%s/me/summits/events/favorites/add', $current_realm)],
],
[
'name' => 'add-rsvp-member',
'route' => '/api/v1/summits/{id}/members/{member_id}/schedule/{event_id}/rsvp',
'http_method' => 'POST',
'scopes' => [
sprintf(SummitScopes::WriteSummitData, $current_realm)
],
],
[
'name' => 'update-rsvp-member',
'route' => '/api/v1/summits/{id}/members/{member_id}/schedule/{event_id}/rsvp',
'http_method' => 'PUT',
'scopes' => [
sprintf(SummitScopes::WriteSummitData, $current_realm)
],
],
[
'name' => 'delete-rsvp-member',
'route' => '/api/v1/summits/{id}/members/{member_id}/schedule/{event_id}/rsvp',
'http_method' => 'DELETE',
'scopes' => [sprintf(SummitScopes::WriteSummitData, $current_realm)],
'scopes' => [
sprintf(SummitScopes::WriteSummitData, $current_realm)
],
],
[
'name' => 'remove-from-own-member-favorites',

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
</head>
<body>
<p>Thank you for your RSVP to <strong>{!! $event_title !!}</strong> at {!! $event_date !!} . For your convenience, we have added this to My Schedule within the Summit Management tool.</p>
@if(!empty($event_uri))
<p>Be sure to synch it to your calendar by going <a href="{!! $event_uri !!}" target="_blank">here</a>.</p>
@endif
Please present a printed copy of this email at the entrance where the event is being held.<br/><br/>
******************************************************************************************<br/>
<p>
Attendee: {!! $owner_fullname !!}<br/>
Event: {!! $event_title !!}<br/>
Confirmation #: {!! $confirmation_number !!}<br/>
</p>
******************************************************************************************<br/>
<p>Cheers,<br/>{!! Config::get('app.tenant_name') !!} Support Team</p>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
</head>
<body>
<p>Thank you for signing up to <strong>{!! $event_title !!}<</strong> at {!! $event_date !!} At the moment, this class is full.
However, you've been added to the waitlist. If space becomes available, the Workshop presenter will contact you to let you know.</p>
<p>For your convenience, we have added this to My Schedule within the Summit Management tool.</p>
Be sure to synch it to your calendar by going <a href="{!! $event_uri !!}" target="_blank">here</a>.
If you are removed from the waitlist, please present a printed copy of this email at the entrance where the event is being held.<br/><br/>
******************************************************************************************<br/>
<p>
Attendee: {!! $owner_fullname !!}<br/>
Event: {!! $event_title !!}<br/>
Confirmation #: {!! $confirmation_number !!}<br/>
</p>
******************************************************************************************<br/>
<p>Cheers,<br/>{!! Config::get('app.tenant_name') !!} Support Team</p>
</body>
</html>

View File

@ -0,0 +1,130 @@
<?php
/**
* Copyright 2020 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
/**
* Class OAuth2RSVPSummitEventApiTest
*/
final class OAuth2RSVPSummitEventApiTest extends ProtectedApiTest
{
/**
* @param int $summit_id
* @param int $event_id
* @return false|string
*/
public function testAddRSVP($summit_id = 27, $event_id = 24344){
$params = array
(
'id' => $summit_id,
'member_id' => 'me',
'event_id' => $event_id,
);
$headers = array
(
"HTTP_Authorization" => " Bearer " . $this->access_token,
"CONTENT_TYPE" => "application/json",
"HTTP_Referer" => "https://www.openstack.org/summit/shanghai-2019/summit-schedule/events/".$event_id
);
$payload = [
'answers' => [
[
'question_id' => 209,
'value' => 'smarcet@gmail.com',
],
[
'question_id' => 210,
'value' => 'Sebastian',
],
[
'question_id' => 211,
'value' => 'Marcet',
],
[
'question_id' => 212,
'value' => 'Dev',
],
[
'question_id' => 213,
'value' => 'Tipit',
],
[
'question_id' => 214,
'value' => '+5491133943659',
],
[
'question_id' => 215,
'value' => [
'150', '151'
],
],
[
'question_id' => 216,
'value' => '155',
],
[
'question_id' => 218,
'value' => '161',
],
[
'question_id' => 219,
'value' => 'N/A',
],
]
];
$response = $this->action
(
"POST",
"OAuth2SummitMembersApiController@addEventRSVP",
$params,
[],
[],
[],
$headers,
json_encode($payload)
);
$rsvp = $response->getContent();
$this->assertResponseStatus(201);
return $rsvp;
}
public function testCurrentSummitMyMemberScheduleUnRSVP($summit_id = 27, $event_id = 24344)
{
$params = array
(
'id' => $summit_id,
'member_id' => 'me',
'event_id' => $event_id
);
$headers = array("HTTP_Authorization" => " Bearer " . $this->access_token);
$response = $this->action(
"DELETE",
"OAuth2SummitMembersApiController@deleteEventRSVP",
$params,
array(),
array(),
array(),
$headers
);
$content = $response->getContent();
$this->assertResponseStatus(204);
}
}

View File

@ -686,29 +686,6 @@ final class OAuth2SummitApiTest extends ProtectedApiTest
$this->assertResponseStatus(204);
}
public function testCurrentSummitMyAttendeeScheduleUnRSVP($event_id = 18639, $summit_id = 22)
{
//$this->testCurrentSummitMyAttendeeAddToSchedule($event_id, $summit_id);
$params = array
(
'id' => $summit_id,
'attendee_id' => 'me',
'event_id' => $event_id
);
$headers = array("HTTP_Authorization" => " Bearer " . $this->access_token);
$response = $this->action(
"DELETE",
"OAuth2SummitAttendeesApiController@deleteEventRSVP",
$params,
array(),
array(),
array(),
$headers
);
$content = $response->getContent();
$this->assertResponseStatus(204);
}
public function testGetMySpeakerFromCurrentSummit()
{
@ -1444,31 +1421,6 @@ final class OAuth2SummitApiTest extends ProtectedApiTest
$this->assertResponseStatus(204);
}
public function testCurrentSummitMyMemberScheduleUnRSVP($event_id = 18639, $summit_id = 22)
{
//$this->testCurrentSummitMyAttendeeAddToSchedule($event_id, $summit_id);
$params = array
(
'id' => $summit_id,
'member_id' => 'me',
'event_id' => $event_id
);
$headers = array("HTTP_Authorization" => " Bearer " . $this->access_token);
$response = $this->action(
"DELETE",
"OAuth2SummitMembersApiController@deleteEventRSVP",
$params,
array(),
array(),
array(),
$headers
);
$content = $response->getContent();
$this->assertResponseStatus(204);
}
public function testAddPresentationSlide($summit_id=25){
$repo = EntityManager::getRepository(\models\summit\Summit::class);

View File

@ -81,7 +81,7 @@ class AccessTokenServiceStub implements IAccessTokenService
'user_external_id' => '13867',
'expires_in' => 3600,
'application_type' => 'WEB_APPLICATION',
'allowed_return_uris' => '',
'allowed_return_uris' => 'https://www.openstack.org/OpenStackIdAuthenticator,https://www.openstack.org/Security/login',
'allowed_origins' => ''
]
);