diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2PresentationApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2PresentationApiController.php index c251de3f..100e705a 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/OAuth2PresentationApiController.php +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2PresentationApiController.php @@ -12,8 +12,10 @@ * limitations under the License. **/ +use libs\utils\HTMLCleaner; use models\exceptions\EntityNotFoundException; use models\exceptions\ValidationException; +use models\main\IMemberRepository; use models\oauth2\IResourceServerContext; use models\summit\ISummitEventRepository; use models\summit\ISummitRepository; @@ -48,18 +50,32 @@ final class OAuth2PresentationApiController extends OAuth2ProtectedController */ private $presentation_repository; + /** + * @var IMemberRepository + */ + private $member_repository; + /** + * OAuth2PresentationApiController constructor. + * @param IPresentationService $presentation_service + * @param ISummitRepository $summit_repository + * @param ISummitEventRepository $presentation_repository + * @param IMemberRepository $member_repository + * @param IResourceServerContext $resource_server_context + */ public function __construct ( IPresentationService $presentation_service, ISummitRepository $summit_repository, ISummitEventRepository $presentation_repository, + IMemberRepository $member_repository, IResourceServerContext $resource_server_context ) { parent::__construct($resource_server_context); $this->presentation_repository = $presentation_repository; $this->presentation_service = $presentation_service; + $this->member_repository = $member_repository; $this->summit_repository = $summit_repository; } @@ -232,4 +248,160 @@ final class OAuth2PresentationApiController extends OAuth2ProtectedController return $this->error500($ex); } } + + /** + * @param $summit_id + * @return mixed + */ + public function submitPresentation($summit_id){ + try { + + $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->resource_server_context)->find($summit_id); + if (is_null($summit)) return $this->error404(); + + if(!Request::isJson()) return $this->error400(); + + $member_id = $this->resource_server_context->getCurrentUserExternalId(); + if(is_null($member_id)) + return $this->error403(); + + $member = $this->member_repository->getById($member_id); + + if(is_null($member)) + return $this->error403(); + + $data = Input::json(); + + $rules = + [ + 'title' => 'required|string|max:100', + 'description' => 'required|string', + 'social_description' => 'required|string|max:100', + 'level' => 'required|in:Beginner,Intermediate,Advanced,N/A', + 'attendees_expected_learnt' => 'required|string|max:1000', + 'type_id' => 'required|integer', + 'track_id' => 'required|integer', + 'attending_media' => 'required|boolean', + 'links' => 'required|url_array', + 'extra_questions' => 'sometimes|entity_value_array', + ]; + + $data = $data->all(); + // Creates a Validator instance and validates the data. + $validation = Validator::make($data, $rules); + + if ($validation->fails()) { + $ex = new ValidationException; + $ex->setMessages($validation->messages()->toArray()); + throw $ex; + } + + $fields = [ + 'title', + 'description', + 'social_summary', + 'attendees_expected_learnt', + ]; + + $presentation = $this->presentation_service->submitPresentation($summit, $member, HTMLCleaner::cleanData($data, $fields)); + + return $this->created(SerializerRegistry::getInstance()->getSerializer($presentation)->serialize()); + } + catch (EntityNotFoundException $ex1) + { + Log::warning($ex1); + return $this->error404(['message' => $ex1->getMessage()]); + } + catch (ValidationException $ex2) + { + Log::warning($ex2); + return $this->error412($ex2->getMessages()); + } + catch (Exception $ex) + { + Log::error($ex); + return $this->error500($ex); + } + } + + /** + * @param $summit_id + * @param $presentation_id + * @return mixed + */ + public function updatePresentationSubmission($summit_id, $presentation_id){ + try { + + $summit = SummitFinderStrategyFactory::build($this->summit_repository, $this->resource_server_context)->find($summit_id); + if (is_null($summit)) return $this->error404(); + + if(!Request::isJson()) return $this->error400(); + + $member_id = $this->resource_server_context->getCurrentUserExternalId(); + if(is_null($member_id)) + return $this->error403(); + + $member = $this->member_repository->getById($member_id); + + if(is_null($member)) + return $this->error403(); + + $data = Input::json(); + + $rules = + [ + 'title' => 'sometimes|string|max:100', + 'description' => 'sometimes|string', + 'social_description' => 'sometimes|string|max:100', + 'level' => 'sometimes|in:Beginner,Intermediate,Advanced,N/A', + 'attendees_expected_learnt' => 'sometimes|string|max:1000', + 'type_id' => 'sometimes|integer', + 'track_id' => 'sometimes|integer', + 'attending_media' => 'sometimes|boolean', + 'links' => 'sometimes|url_array', + 'extra_questions' => 'sometimes|entity_value_array', + ]; + + $data = $data->all(); + // Creates a Validator instance and validates the data. + $validation = Validator::make($data, $rules); + + if ($validation->fails()) { + $ex = new ValidationException; + $ex->setMessages($validation->messages()->toArray()); + throw $ex; + } + + $fields = [ + 'title', + 'description', + 'social_summary', + 'attendees_expected_learnt', + ]; + + $presentation = $this->presentation_service->updatePresentationSubmission( + $summit, + $presentation_id, + $member, + HTMLCleaner::cleanData($data, $fields) + ); + + return $this->updated(SerializerRegistry::getInstance()->getSerializer($presentation)->serialize()); + } + catch (EntityNotFoundException $ex1) + { + Log::warning($ex1); + return $this->error404(); + } + catch (ValidationException $ex2) + { + Log::warning($ex2); + return $this->error412($ex2->getMessages()); + } + catch (Exception $ex) + { + Log::error($ex); + return $this->error500($ex); + } + } } \ No newline at end of file diff --git a/app/Http/routes.php b/app/Http/routes.php index bff4a6aa..259fcf91 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -270,8 +270,14 @@ Route::group([ // presentations Route::group(['prefix' => 'presentations'], function () { + // opened without role CFP - valid selection plan on CFP status + Route::post('', 'OAuth2PresentationApiController@submitPresentation'); + Route::group(['prefix' => '{presentation_id}'], function () { + // opened without role CFP - valid selection plan on CFP status + Route::put('', 'OAuth2PresentationApiController@updatePresentationSubmission'); + Route::group(['prefix' => 'videos'], function () { Route::get('', 'OAuth2PresentationApiController@getPresentationVideos'); diff --git a/app/ModelSerializers/SerializerRegistry.php b/app/ModelSerializers/SerializerRegistry.php index 39f47f52..cbc1eb36 100644 --- a/app/ModelSerializers/SerializerRegistry.php +++ b/app/ModelSerializers/SerializerRegistry.php @@ -38,6 +38,7 @@ use App\ModelSerializers\PushNotificationMessageSerializer; use App\ModelSerializers\Software\OpenStackComponentSerializer; use App\ModelSerializers\Software\OpenStackReleaseSerializer; use App\ModelSerializers\Summit\AdminSummitSerializer; +use App\ModelSerializers\Summit\Presentation\TrackQuestions\TrackAnswerSerializer; use App\ModelSerializers\Summit\RSVP\Templates\RSVPDropDownQuestionTemplateSerializer; use App\ModelSerializers\Summit\RSVP\Templates\RSVPLiteralContentQuestionTemplateSerializer; use App\ModelSerializers\Summit\RSVP\Templates\RSVPMultiValueQuestionTemplateSerializer; @@ -107,6 +108,7 @@ final class SerializerRegistry $this->registry['PresentationCategoryGroup'] = PresentationCategoryGroupSerializer::class; $this->registry['PrivatePresentationCategoryGroup'] = PrivatePresentationCategoryGroupSerializer::class; $this->registry['Tag'] = TagSerializer::class; + $this->registry['TrackAnswer'] = TrackAnswerSerializer::class; $this->registry['SummitEvent'] = SummitEventSerializer::class; $this->registry['SummitGroupEvent'] = SummitGroupEventSerializer::class; $this->registry['SummitEventMetricsSnapshot'] = SummitEventMetricsSnapshotSerializer::class; diff --git a/app/ModelSerializers/Summit/Presentation/PresentationSerializer.php b/app/ModelSerializers/Summit/Presentation/PresentationSerializer.php index 9d9f960c..71481633 100644 --- a/app/ModelSerializers/Summit/Presentation/PresentationSerializer.php +++ b/app/ModelSerializers/Summit/Presentation/PresentationSerializer.php @@ -30,7 +30,6 @@ class PresentationSerializer extends SummitEventSerializer ]; protected static $allowed_fields = [ - 'track_id', 'moderator_speaker_id', 'level', @@ -47,6 +46,7 @@ class PresentationSerializer extends SummitEventSerializer 'videos', 'speakers', 'links', + 'extra_questions', ]; /** @@ -103,6 +103,15 @@ class PresentationSerializer extends SummitEventSerializer $values['videos'] = $videos; } + if(in_array('extra_questions', $relations)) + { + $answers = []; + foreach ($presentation->getAnswers() as $answer) { + $answers[]= SerializerRegistry::getInstance()->getSerializer($answer)->serialize(); + } + $values['extra_questions'] = $answers; + } + if (!empty($expand)) { foreach (explode(',', $expand) as $relation) { switch (trim($relation)) { diff --git a/app/ModelSerializers/Summit/Presentation/TrackQuestions/TrackAnswerSerializer.php b/app/ModelSerializers/Summit/Presentation/TrackQuestions/TrackAnswerSerializer.php new file mode 100644 index 00000000..c50d16f8 --- /dev/null +++ b/app/ModelSerializers/Summit/Presentation/TrackQuestions/TrackAnswerSerializer.php @@ -0,0 +1,26 @@ + 'value:json_string', + 'QuestionName' => 'question_name:json_string', + 'QuestionId' => 'question_id:json_int', + ]; +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/Materials/PresentationMaterial.php b/app/Models/Foundation/Summit/Events/Presentations/Materials/PresentationMaterial.php index b9809e28..1228e27c 100644 --- a/app/Models/Foundation/Summit/Events/Presentations/Materials/PresentationMaterial.php +++ b/app/Models/Foundation/Summit/Events/Presentations/Materials/PresentationMaterial.php @@ -199,4 +199,8 @@ abstract class PresentationMaterial extends SilverstripeBaseModel public function inserted($args){ Event::fire(new PresentationMaterialCreated($this, $args)); } + + public function clearPresentation(){ + $this->presentation = null; + } } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/Presentation.php b/app/Models/Foundation/Summit/Events/Presentations/Presentation.php index dfff1147..fcfb554b 100644 --- a/app/Models/Foundation/Summit/Events/Presentations/Presentation.php +++ b/app/Models/Foundation/Summit/Events/Presentations/Presentation.php @@ -12,11 +12,13 @@ * limitations under the License. **/ use Doctrine\ORM\Mapping AS ORM; +use App\Models\Foundation\Summit\Events\Presentations\TrackQuestions\TrackAnswer; use App\Models\Foundation\Summit\SelectionPlan; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\ArrayCollection; +use App\Models\Foundation\Summit\Events\Presentations\TrackQuestions\TrackQuestionTemplate; use models\exceptions\ValidationException; - +use models\main\Member; /** * Class Presentation * @ORM\Entity @@ -116,8 +118,16 @@ class Presentation extends SummitEvent */ private $moderator; + /** - * @ORM\ManyToOne(targetEntity="App\Models\Foundation\Summit\SelectionPlan") + * @ORM\ManyToOne(targetEntity="models\main\Member") + * @ORM\JoinColumn(name="CreatorID", referencedColumnName="ID", onDelete="SET NULL") + * @var Member + */ + private $creator; + + /** + * @ORM\ManyToOne(targetEntity="App\Models\Foundation\Summit\SelectionPlan", inversedBy="presentations") * @ORM\JoinColumn(name="SelectionPlanID", referencedColumnName="ID") * @var SelectionPlan */ @@ -149,6 +159,12 @@ class Presentation extends SummitEvent */ private $selected_presentations; + /** + * @ORM\OneToMany(targetEntity="App\Models\Foundation\Summit\Events\Presentations\TrackQuestions\TrackAnswer", mappedBy="presentation", cascade={"persist"}, orphanRemoval=true, fetch="EXTRA_LAZY") + * @var TrackAnswer[] + */ + private $answers; + /** * @return bool */ @@ -179,6 +195,7 @@ class Presentation extends SummitEvent $this->materials = new ArrayCollection(); $this->speakers = new ArrayCollection(); + $this->answers = new ArrayCollection(); $this->to_record = false; $this->attending_media = false; } @@ -251,6 +268,7 @@ class Presentation extends SummitEvent * @param PresentationSpeaker $speaker */ public function addSpeaker(PresentationSpeaker $speaker){ + if($this->speakers->contains($speaker)) return; $this->speakers->add($speaker); $speaker->addPresentation($this); } @@ -556,4 +574,107 @@ class Presentation extends SummitEvent public function clearSelectionPlan(){ $this->selection_plan = null; } + + /** + * @return Member + */ + public function getCreator() + { + return $this->creator; + } + + /** + * @param Member $creator + */ + public function setCreator(Member $creator) + { + $this->creator = $creator; + } + + /** + * @return TrackAnswer[] + */ + public function getAnswers() + { + return $this->answers; + } + + /** + * @param TrackAnswer[] $answers + */ + public function setAnswers($answers) + { + $this->answers = $answers; + } + + /** + * @param TrackAnswer $answer + */ + public function addAnswer(TrackAnswer $answer){ + $this->answers->add($answer); + $answer->setPresentation($this); + } + + /** + * @param string $link + * @return PresentationLink|null + */ + public function findLink($link){ + $links = $this->getLinks(); + + foreach ($links as $entity){ + if($entity->getLink() == $link) + return $entity; + } + return null; + } + + public function clearLinks(){ + $links = $this->getLinks(); + + foreach ($links as $link){ + $this->materials->removeElement($link); + $link->clearPresentation(); + } + } + + /** + * @param TrackQuestionTemplate $question + * @return TrackAnswer|null + */ + public function getTrackExtraQuestionAnswer(TrackQuestionTemplate $question){ + $criteria = Criteria::create(); + $criteria->where(Criteria::expr()->eq('question', $question)); + $res = $this->answers->matching($criteria)->first(); + return $res === false ? null : $res; + } + + /** + * @return int + */ + public function getCreatorId() + { + try{ + if(is_null($this->creator)) return 0; + return $this->creator->getId(); + } + catch(\Exception $ex){ + return 0; + } + } + + /** + * @return int + */ + public function getSelectionPlanId() + { + try{ + if(is_null($this->selection_plan)) return 0; + return $this->selection_plan->getId(); + } + catch(\Exception $ex){ + return 0; + } + } + } diff --git a/app/Models/Foundation/Summit/Events/Presentations/PresentationCategory.php b/app/Models/Foundation/Summit/Events/Presentations/PresentationCategory.php index 536285ce..53df92b1 100644 --- a/app/Models/Foundation/Summit/Events/Presentations/PresentationCategory.php +++ b/app/Models/Foundation/Summit/Events/Presentations/PresentationCategory.php @@ -11,10 +11,11 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -use Doctrine\Common\Collections\Criteria; -use Doctrine\ORM\Event\PreUpdateEventArgs; use Doctrine\ORM\Mapping AS ORM; +use Doctrine\ORM\Event\PreUpdateEventArgs; use Doctrine\ORM\Query\ResultSetMappingBuilder; +use App\Models\Foundation\Summit\Events\Presentations\TrackQuestions\TrackQuestionTemplate; +use Doctrine\Common\Collections\Criteria; use models\main\Tag; use models\utils\SilverstripeBaseModel; use Doctrine\Common\Collections\ArrayCollection; @@ -153,12 +154,49 @@ class PresentationCategory extends SilverstripeBaseModel */ protected $allowed_tags; + + /** + * @ORM\ManyToMany(targetEntity="App\Models\Foundation\Summit\Events\Presentations\TrackQuestions\TrackQuestionTemplate", cascade={"persist"}, inversedBy="tracks") + * @ORM\JoinTable(name="PresentationCategory_ExtraQuestions", + * joinColumns={@ORM\JoinColumn(name="PresentationCategoryID", referencedColumnName="ID")}, + * inverseJoinColumns={@ORM\JoinColumn(name="TrackQuestionTemplateID", referencedColumnName="ID")} + * ) + * @var TrackQuestionTemplate[] + */ + protected $extra_questions; + + + /** + * @param int $id + * @return TrackQuestionTemplate|null + */ + public function getExtraQuestionById($id){ + $res = $this->extra_questions->filter(function(TrackQuestionTemplate $question) use($id){ + return $question->getIdentifier() == $id; + }); + $res = $res->first(); + return $res === false ? null : $res; + } + + /** + * @param string $name + * @return TrackQuestionTemplate|null + */ + public function getExtraQuestionByName($name){ + $res = $this->extra_questions->filter(function(TrackQuestionTemplate $question) use($name){ + return $question->getName() == trim($name); + }); + $res = $res->first(); + return $res === false ? null : $res; + } + public function __construct() { parent::__construct(); $this->groups = new ArrayCollection; $this->allowed_tags = new ArrayCollection; + $this->extra_questions = new ArrayCollection; $this->session_count = 0; $this->alternate_count = 0; $this->lightning_alternate_count = 0; diff --git a/app/Models/Foundation/Summit/Events/Presentations/PresentationCategoryGroup.php b/app/Models/Foundation/Summit/Events/Presentations/PresentationCategoryGroup.php index 88bae2eb..b9d22fc4 100644 --- a/app/Models/Foundation/Summit/Events/Presentations/PresentationCategoryGroup.php +++ b/app/Models/Foundation/Summit/Events/Presentations/PresentationCategoryGroup.php @@ -138,10 +138,10 @@ class PresentationCategoryGroup extends SilverstripeBaseModel /** * owning side - * @ORM\ManyToMany(targetEntity="models\summit\PresentationCategory", inversedBy="groups") + * @ORM\ManyToMany(targetEntity="models\summit\PresentationCategory") * @ORM\JoinTable(name="PresentationCategoryGroup_Categories", - * joinColumns={@ORM\JoinColumn(name="PresentationCategoryGroupID", referencedColumnName="ID")}, - * inverseJoinColumns={@ORM\JoinColumn(name="PresentationCategoryID", referencedColumnName="ID")} + * joinColumns={@ORM\JoinColumn(name="PresentationCategoryGroupID", referencedColumnName="id")}, + * inverseJoinColumns={@ORM\JoinColumn(name="PresentationCategoryID", referencedColumnName="id")} * ) * @var PresentationCategory[] */ diff --git a/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackAnswer.php b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackAnswer.php new file mode 100644 index 00000000..e1401418 --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackAnswer.php @@ -0,0 +1,112 @@ +value; + } + + /** + * @param string $value + */ + public function setValue($value) + { + $this->value = $value; + } + + /** + * @return TrackQuestionTemplate + */ + public function getQuestion() + { + return $this->question; + } + + /** + * @param TrackQuestionTemplate $question + */ + public function setQuestion($question) + { + $this->question = $question; + } + + /** + * @return Presentation + */ + public function getPresentation() + { + return $this->presentation; + } + + /** + * @param Presentation $presentation + */ + public function setPresentation($presentation) + { + $this->presentation = $presentation; + } + + /** + * @return string + */ + public function getQuestionName(){ + return $this->question->getName(); + } + + /** + * @return int + */ + public function getQuestionId(){ + return $this->question->getId(); + } + + public function __construct() + { + parent::__construct(); + } + +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackCheckBoxListQuestionTemplate.php b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackCheckBoxListQuestionTemplate.php new file mode 100644 index 00000000..19e0e16b --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackCheckBoxListQuestionTemplate.php @@ -0,0 +1,43 @@ + self::ClassName, + ]; + + /** + * @return array + */ + public static function getMetadata(){ + return array_merge(TrackMultiValueQuestionTemplate::getMetadata(), self::$metadata); + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackCheckBoxQuestionTemplate.php b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackCheckBoxQuestionTemplate.php new file mode 100644 index 00000000..6f391e05 --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackCheckBoxQuestionTemplate.php @@ -0,0 +1,42 @@ + self::ClassName, + ]; + + /** + * @return array + */ + public static function getMetadata(){ + return array_merge(TrackSingleValueTemplateQuestion::getMetadata(), self::$metadata); + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackDropDownQuestionTemplate.php b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackDropDownQuestionTemplate.php new file mode 100644 index 00000000..72caec60 --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackDropDownQuestionTemplate.php @@ -0,0 +1,97 @@ +is_multi_select = false; + $this->is_country_selector = false; + } + + /** + * @return bool + */ + public function isMultiSelect() + { + return $this->is_multi_select; + } + + /** + * @param bool $is_multi_select + */ + public function setIsMultiSelect($is_multi_select) + { + $this->is_multi_select = $is_multi_select; + } + + /** + * @return bool + */ + public function isCountrySelector() + { + return $this->is_country_selector; + } + + /** + * @param bool $is_country_selector + */ + public function setIsCountrySelector($is_country_selector) + { + $this->is_country_selector = $is_country_selector; + } + + + const ClassName = 'TrackDropDownQuestionTemplate'; + + /** + * @return string + */ + public function getClassName(){ + return self::ClassName; + } + + public static $metadata = [ + 'class_name' => self::ClassName, + 'is_multi_select' => 'boolean', + 'is_country_selector' => 'boolean', + ]; + + /** + * @return array + */ + public static function getMetadata(){ + return array_merge(TrackMultiValueQuestionTemplate::getMetadata(), self::$metadata); + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackLiteralContentQuestionTemplate.php b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackLiteralContentQuestionTemplate.php new file mode 100644 index 00000000..54fe95bc --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackLiteralContentQuestionTemplate.php @@ -0,0 +1,66 @@ +content; + } + + /** + * @param string $content + */ + public function setContent($content) + { + $this->content = $content; + } + + const ClassName = 'TrackLiteralContentQuestionTemplate'; + + /** + * @return string + */ + public function getClassName(){ + return self::ClassName; + } + + public static $metadata = [ + 'class_name' => self::ClassName, + 'content' => 'string', + ]; + + /** + * @return array + */ + public static function getMetadata(){ + return array_merge(TrackQuestionTemplate::getMetadata(), self::$metadata); + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackMultiValueQuestionTemplate.php b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackMultiValueQuestionTemplate.php new file mode 100644 index 00000000..f78eb628 --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackMultiValueQuestionTemplate.php @@ -0,0 +1,121 @@ +values = new ArrayCollection; + } + + /** + * @return string + */ + public function getEmptyString() + { + return $this->empty_string; + } + + /** + * @param string $empty_string + */ + public function setEmptyString($empty_string) + { + $this->empty_string = $empty_string; + } + + /** + * @return TrackQuestionValueTemplate[] + */ + public function getValues() + { + return $this->values; + } + + /** + * @param TrackQuestionValueTemplate[] $values + */ + public function setValues($values) + { + $this->values = $values; + } + + /** + * @return TrackQuestionValueTemplate + */ + public function getDefaultValue() + { + return $this->default_value; + } + + /** + * @param TrackQuestionValueTemplate $default_value + */ + public function setDefaultValue($default_value) + { + $this->default_value = $default_value; + } + + /** + * @return string + */ + public function getClassName(){ + return self::ClassName; + } + + const ClassName = 'TrackMultiValueQuestionTemplate'; + + public static $metadata = [ + 'class_name' => self::ClassName, + 'empty_string' => 'string', + 'default_value_id' => 'int', + 'values' => 'array' + ]; + + /** + * @return array + */ + public static function getMetadata(){ + return array_merge(TrackQuestionTemplate::getMetadata(), self::$metadata); + } + +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackQuestionTemplate.php b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackQuestionTemplate.php new file mode 100644 index 00000000..60a4df85 --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackQuestionTemplate.php @@ -0,0 +1,236 @@ +is_mandatory = false; + $this->is_read_only = false; + $this->tracks = new ArrayCollection(); + $this->answers = new ArrayCollection(); + } + + public static $metadata = [ + 'name' => 'string', + 'label' => 'string', + 'is_mandatory' => 'boolean', + 'is_read_only' => 'boolean', + 'after_question' => 'string', + 'tracks' => 'array', + 'answers' => 'array', + ]; + + /** + * @return array + */ + public static function getMetadata(){ + return self::$metadata; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * @param string $label + */ + public function setLabel($label) + { + $this->label = $label; + } + + /** + * @return bool + */ + public function isMandatory() + { + return $this->is_mandatory; + } + + /** + * @param bool $is_mandatory + */ + public function setIsMandatory($is_mandatory) + { + $this->is_mandatory = $is_mandatory; + } + + /** + * @return bool + */ + public function isReadOnly() + { + return $this->is_read_only; + } + + /** + * @param bool $is_read_only + */ + public function setIsReadOnly($is_read_only) + { + $this->is_read_only = $is_read_only; + } + + /** + * @return string + */ + public function getAfterQuestion() + { + return $this->after_question; + } + + /** + * @param string $after_question + */ + public function setAfterQuestion($after_question) + { + $this->after_question = $after_question; + } + + /** + * @return PresentationCategory[] + */ + public function getTracks() + { + return $this->tracks; + } + + /** + * @param PresentationCategory[] $tracks + */ + public function setTracks($tracks) + { + $this->tracks = $tracks; + } + + /** + * @return TrackAnswer[] + */ + public function getAnswers() + { + return $this->answers; + } + + /** + * @param TrackAnswer[] $answers + */ + public function setAnswers($answers) + { + $this->answers = $answers; + } + + /** + * @param TrackAnswer $answer + */ + public function addAnswer(TrackAnswer $answer){ + $this->answers->add($answer); + $answer->setQuestion($this); + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackQuestionValueTemplate.php b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackQuestionValueTemplate.php new file mode 100644 index 00000000..e317a04e --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackQuestionValueTemplate.php @@ -0,0 +1,113 @@ +value; + } + + /** + * @param string $value + */ + public function setValue($value) + { + $this->value = $value; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * @param string $label + */ + public function setLabel($label) + { + $this->label = $label; + } + + /** + * @return int + */ + public function getOrder() + { + return $this->order; + } + + /** + * @param int $order + */ + public function setOrder($order) + { + $this->order = $order; + } + + /** + * @return TrackMultiValueQuestionTemplate + */ + public function getOwner() + { + return $this->owner; + } + + /** + * @param TrackMultiValueQuestionTemplate $owner + */ + public function setOwner($owner) + { + $this->owner = $owner; + } + +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackRadioButtonListQuestionTemplate.php b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackRadioButtonListQuestionTemplate.php new file mode 100644 index 00000000..877354f4 --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackRadioButtonListQuestionTemplate.php @@ -0,0 +1,43 @@ + self::ClassName, + ]; + + /** + * @return array + */ + public static function getMetadata(){ + return array_merge(TrackMultiValueQuestionTemplate::getMetadata(), self::$metadata); + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackSingleValueTemplateQuestion.php b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackSingleValueTemplateQuestion.php new file mode 100644 index 00000000..672ca1e0 --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackSingleValueTemplateQuestion.php @@ -0,0 +1,66 @@ + self::ClassName, + 'initial_value' => 'string', + ]; + + /** + * @return array + */ + public static function getMetadata(){ + return array_merge(TrackQuestionTemplate::getMetadata(), self::$metadata); + } + + /** + * @return string + */ + public function getInitialValue() + { + return $this->initial_value; + } + + /** + * @param string $initial_value + */ + public function setInitialValue($initial_value) + { + $this->initial_value = $initial_value; + } + +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackTextBoxQuestionTemplate.php b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackTextBoxQuestionTemplate.php new file mode 100644 index 00000000..2d6988eb --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/TrackQuestions/TrackTextBoxQuestionTemplate.php @@ -0,0 +1,42 @@ + self::ClassName, + ]; + + /** + * @return array + */ + public static function getMetadata(){ + return array_merge(TrackSingleValueTemplateQuestion::getMetadata(), self::$metadata); + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/SummitEvent.php b/app/Models/Foundation/Summit/Events/SummitEvent.php index c86dea9c..dd08f998 100644 --- a/app/Models/Foundation/Summit/Events/SummitEvent.php +++ b/app/Models/Foundation/Summit/Events/SummitEvent.php @@ -12,7 +12,6 @@ * limitations under the License. **/ use App\Models\Foundation\Summit\Events\RSVP\RSVPTemplate; -use Doctrine\ORM\Mapping AS ORM; use App\Events\SummitEventCreated; use App\Events\SummitEventDeleted; use App\Events\SummitEventUpdated; @@ -28,6 +27,7 @@ use DateTime; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Config; use Cocur\Slugify\Slugify; +use Doctrine\ORM\Mapping AS ORM; /** * @ORM\Entity(repositoryClass="App\Repositories\Summit\DoctrineSummitEventRepository") * @ORM\Table(name="SummitEvent") diff --git a/app/Models/Foundation/Summit/SelectionPlan.php b/app/Models/Foundation/Summit/SelectionPlan.php index 6c67da6d..0724446e 100644 --- a/app/Models/Foundation/Summit/SelectionPlan.php +++ b/app/Models/Foundation/Summit/SelectionPlan.php @@ -15,6 +15,7 @@ use Doctrine\ORM\Mapping AS ORM; use App\Models\Utils\TimeZoneEntity; use Doctrine\Common\Collections\ArrayCollection; use models\summit\Presentation; +use models\summit\PresentationCategory; use models\summit\PresentationCategoryGroup; use models\summit\Summit; use models\summit\SummitOwned; @@ -328,4 +329,64 @@ class SelectionPlan extends SilverstripeBaseModel $this->presentations->add($presentation); $presentation->setSelectedPresentations($this); } + + public function getStageStatus($stage) { + + $getStartDate = "get{$stage}BeginDate"; + $getEndDate = "get{$stage}EndDate"; + $start_date = $this->$getStartDate(); + $end_date = $this->$getEndDate(); + + if (empty($start_date) || empty($end_date)) { + return null; + } + + $utc_time_zone = new \DateTimeZone('UTC'); + $start_date->setTimeZone($utc_time_zone); + $end_date->setTimeZone($utc_time_zone); + $now = new \DateTime('now', new \DateTimeZone( 'UTC')); + + if ($now > $end_date) { + return Summit::STAGE_FINISHED; + } else if ($now < $start_date) { + return Summit::STAGE_UNSTARTED; + } else { + return Summit::STAGE_OPEN; + } + } + + /** + * @param PresentationCategory $track + * @return bool + */ + public function hasTrack(PresentationCategory $track){ + foreach($this->category_groups as $track_group){ + if($track_group->hasCategory($track->getIdentifier())) return true; + } + return false; + } + + /** + * @return bool + */ + public function isVotingOpen() + { + return $this->getStageStatus('Voting') === Summit::STAGE_OPEN; + } + + /** + * @return bool + */ + public function isSubmissionOpen() + { + return $this->getStageStatus('Submission') === Summit::STAGE_OPEN; + } + + /** + * @return bool + */ + public function isSelectionOpen() + { + return $this->getStageStatus('Selection') === Summit::STAGE_OPEN; + } } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Speakers/PresentationSpeaker.php b/app/Models/Foundation/Summit/Speakers/PresentationSpeaker.php index 5f6fed45..197e18ae 100644 --- a/app/Models/Foundation/Summit/Speakers/PresentationSpeaker.php +++ b/app/Models/Foundation/Summit/Speakers/PresentationSpeaker.php @@ -11,6 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ +use App\Models\Foundation\Summit\SelectionPlan; use Doctrine\ORM\Mapping AS ORM; use App\Events\PresentationSpeakerCreated; use App\Events\PresentationSpeakerDeleted; @@ -393,6 +394,38 @@ class PresentationSpeaker extends SilverstripeBaseModel }); } + const ROLE_SPEAKER = 'ROLE_SPEAKER'; + const ROLE_CREATOR = 'ROLE_CREATOR'; + const ROLE_MODERATOR ='ROLE_MODERATOR'; + + /** + * @param SelectionPlan $selectionPlan + * @param $role + * @return array + */ + public function getPresentationsBySelectionPlanAndRole(SelectionPlan $selectionPlan, $role){ + + if($role == self::ROLE_SPEAKER){ + $res = $this->presentations->filter(function(Presentation $presentation) use($selectionPlan){ + if($presentation->getSelectionPlanId() != $selectionPlan->getId()) return false; + if($presentation->getSummit()->getId() != $selectionPlan->getSummitId()) return false; + if($presentation->getModeratorId() == $this->getId()) return false; + if($presentation->getCreatorId() == $this->getMemberId()) return false; + }); + return $res->toArray(); + } + + if($role == self::ROLE_CREATOR){ + return $selectionPlan->getSummit()->getCreatedPresentations($this, $selectionPlan); + } + + if($role == self::ROLE_MODERATOR){ + return $selectionPlan->getSummit()->getModeratedPresentationsBy($this, $selectionPlan); + } + + return []; + } + /** * @param Summit $summit * @param string $role diff --git a/app/Models/Foundation/Summit/Summit.php b/app/Models/Foundation/Summit/Summit.php index 4cb6fcc3..adc60da5 100644 --- a/app/Models/Foundation/Summit/Summit.php +++ b/app/Models/Foundation/Summit/Summit.php @@ -20,6 +20,7 @@ use App\Models\Utils\TimeZoneEntity; use DateTime; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; +use Doctrine\ORM\Query\Expr\Select; use models\exceptions\ValidationException; use models\main\Company; use models\main\File; @@ -840,6 +841,43 @@ class Summit extends SilverstripeBaseModel return $query->setParameter('summit_id', $this->getIdentifier())->getResult(); } + + /** + * @param PresentationSpeaker $speaker + * @param SelectionPlan $selectionPlan + * @return array + */ + public function getModeratedPresentationsBy(PresentationSpeaker $speaker, SelectionPlan $selectionPlan){ + $query = $this->createQuery("SELECT p from models\summit\Presentation p + JOIN p.summit s + JOIN p.moderator m + JOIN p.selection_plan sp + WHERE s.id = :summit_id and m.id = :moderator_id and sp.id = :selection_plan_id"); + return $query + ->setParameter('summit_id', $this->getIdentifier()) + ->setParameter('moderator_id', $speaker->getIdentifier()) + ->setParameter('selection_plan_id', $selectionPlan->getIdentifier()) + ->getResult(); + } + + /** + * @param PresentationSpeaker $speaker + * @param SelectionPlan $selectionPlan + * @return array + */ + public function getCreatedPresentations(PresentationSpeaker $speaker, SelectionPlan $selectionPlan){ + $query = $this->createQuery("SELECT p from models\summit\Presentation p + JOIN p.summit s + JOIN p.creator c + JOIN p.selection_plan sp + WHERE s.id = :summit_id and c.id = :creator_id and sp.id = :selection_plan_id"); + return $query + ->setParameter('summit_id', $this->getIdentifier()) + ->setParameter('creator_id', $speaker->getMemberId()) + ->setParameter('selection_plan_id', $selectionPlan->getIdentifier()) + ->getResult(); + } + /** * @param int $event_id * @return null|SummitEvent @@ -2116,4 +2154,50 @@ SQL; return $this; } + /** + * @return SelectionPlan[] + */ + public function getActiveSelectionPlans() { + $criteria = Criteria::create(); + $criteria->where(Criteria::expr()->eq('is_enabled', 1)); + return $this->selection_plans->matching($criteria)->toArray(); + } + + /** + * @return bool + */ + public function isSubmissionOpen() + { + foreach ($this->getActiveSelectionPlans() as $plan) { + if ($plan->isSubmissionOpen()) + return true; + } + return false; + } + + /** + * @return bool + */ + public function isPresentationEditionAllowed() + { + return $this->isSubmissionOpen() || $this->isVotingOpen(); + } + + /** + * @return bool + */ + public function isVotingOpen() + { + foreach ($this->getActiveSelectionPlans() as $plan) { + if ($plan->isVotingOpen()) { + return true; + } + } + return false; + } + + const STAGE_UNSTARTED = -1; + const STAGE_OPEN = 0; + const STAGE_FINISHED = 1; + } diff --git a/app/Models/Foundation/Summit/TrackTagGroupAllowedTag.php b/app/Models/Foundation/Summit/TrackTagGroupAllowedTag.php index e43bccd7..830a8be0 100644 --- a/app/Models/Foundation/Summit/TrackTagGroupAllowedTag.php +++ b/app/Models/Foundation/Summit/TrackTagGroupAllowedTag.php @@ -39,7 +39,7 @@ class TrackTagGroupAllowedTag extends BaseEntity private $tag; /** - * @ORM\ManyToOne(targetEntity="TrackTagGroup") + * @ORM\ManyToOne(targetEntity="TrackTagGroup", inversedBy="allowed_tags") * @ORM\JoinColumn(name="TrackTagGroupID", referencedColumnName="ID") * @var TrackTagGroup */ diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 1d72553a..2ce46635 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -198,6 +198,33 @@ class AppServiceProvider extends ServiceProvider return true; }); + Validator::extend('url_array', function($attribute, $value, $parameters, $validator) + { + $validator->addReplacer('url_array', function($message, $attribute, $rule, $parameters) use ($validator) { + return sprintf("%s should be an array of urls", $attribute); + }); + if(!is_array($value)) return false; + foreach($value as $element) + { + if(!filter_var($element, FILTER_VALIDATE_URL)) return false; + } + return true; + }); + + Validator::extend('entity_value_array', function($attribute, $value, $parameters, $validator) + { + $validator->addReplacer('entity_value_array', function($message, $attribute, $rule, $parameters) use ($validator) { + return sprintf("%s should be an array of {id,value} tuple", $attribute); + }); + if(!is_array($value)) return false; + foreach($value as $element) + { + if(!isset($element['id'])) return false; + if(!isset($element['value'])) return false; + } + return true; + }); + Validator::extend('team_permission', function($attribute, $value, $parameters, $validator) { $validator->addReplacer('team_permission', function($message, $attribute, $rule, $parameters) use ($validator) { diff --git a/app/Security/SummitScopes.php b/app/Security/SummitScopes.php index c19b03d8..c1ebf1e2 100644 --- a/app/Security/SummitScopes.php +++ b/app/Security/SummitScopes.php @@ -52,4 +52,6 @@ final class SummitScopes const WriteSummitSpeakerAssistanceData = '%s/summit-speaker-assistance/write'; const WriteTicketTypeData = '%s/ticket-types/write'; + + const WritePresentationData = '%s/summits/write-presentation'; } \ No newline at end of file diff --git a/app/Services/Model/IPresentationService.php b/app/Services/Model/IPresentationService.php index 6eb4106c..7ef996ec 100644 --- a/app/Services/Model/IPresentationService.php +++ b/app/Services/Model/IPresentationService.php @@ -12,9 +12,12 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ - +use models\exceptions\EntityNotFoundException; +use models\exceptions\ValidationException; +use models\main\Member; +use models\summit\Presentation; use models\summit\PresentationVideo; - +use models\summit\Summit; /** * Interface IPresentationService * @package services\model @@ -43,4 +46,25 @@ interface IPresentationService * @return void */ public function deleteVideo($presentation_id, $video_id); + + /** + * @param Summit $summit + * @param Member $member + * @param array $data + * @return Presentation + * @throws ValidationException + * @throws EntityNotFoundException + */ + public function submitPresentation(Summit $summit, Member $member, array $data); + + /** + * @param Summit $summit + * @param int $presentation_id + * @param Member $member + * @param array $data + * @return Presentation + * @throws ValidationException + * @throws EntityNotFoundException + */ + public function updatePresentationSubmission(Summit $summit, $presentation_id, Member $member, array $data); } \ No newline at end of file diff --git a/app/Services/Model/PresentationService.php b/app/Services/Model/PresentationService.php index 06382cb1..76e3af78 100644 --- a/app/Services/Model/PresentationService.php +++ b/app/Services/Model/PresentationService.php @@ -1,4 +1,5 @@ presentation_repository = $presentation_repository; - $this->video_factory = $video_factory; + $this->speaker_repository = $speaker_repository; + $this->video_factory = $video_factory; } /** @@ -61,21 +76,21 @@ final class PresentationService */ public function addVideoTo($presentation_id, array $video_data) { - $video = $this->tx_service->transaction(function() use($presentation_id, $video_data){ + $video = $this->tx_service->transaction(function () use ($presentation_id, $video_data) { $presentation = $this->presentation_repository->getById($presentation_id); - if(is_null($presentation)) + if (is_null($presentation)) throw new EntityNotFoundException('presentation not found!'); - if(!$presentation instanceof Presentation) + if (!$presentation instanceof Presentation) throw new EntityNotFoundException('presentation not found!'); - if($presentation->hasVideos()) + if ($presentation->hasVideos()) throw new ValidationException(sprintf('presentation %s already has a video!', $presentation_id)); - if(!isset($video_data['name'])) $video_data['name'] = $presentation->getTitle(); + if (!isset($video_data['name'])) $video_data['name'] = $presentation->getTitle(); $video = $this->video_factory->build($video_data); @@ -95,34 +110,34 @@ final class PresentationService */ public function updateVideo($presentation_id, $video_id, array $video_data) { - $video = $this->tx_service->transaction(function() use($presentation_id, $video_id, $video_data){ + $video = $this->tx_service->transaction(function () use ($presentation_id, $video_id, $video_data) { $presentation = $this->presentation_repository->getById($presentation_id); - if(is_null($presentation)) + if (is_null($presentation)) throw new EntityNotFoundException('presentation not found!'); - if(!$presentation instanceof Presentation) + if (!$presentation instanceof Presentation) throw new EntityNotFoundException('presentation not found!'); $video = $presentation->getVideoBy($video_id); - if(is_null($video)) + if (is_null($video)) throw new EntityNotFoundException('video not found!'); - if(!$video instanceof PresentationVideo) + if (!$video instanceof PresentationVideo) throw new EntityNotFoundException('video not found!'); - if(isset($video_data['name'])) + if (isset($video_data['name'])) $video->setName(trim($video_data['name'])); - if(isset($video_data['you_tube_id'])) + if (isset($video_data['you_tube_id'])) $video->setYoutubeId(trim($video_data['you_tube_id'])); - if(isset($video_data['description'])) + if (isset($video_data['description'])) $video->setDescription(trim($video_data['description'])); - if(isset($video_data['display_on_site'])) + if (isset($video_data['display_on_site'])) $video->setDisplayOnSite((bool)$video_data['display_on_site']); return $video; @@ -139,22 +154,22 @@ final class PresentationService */ public function deleteVideo($presentation_id, $video_id) { - $this->tx_service->transaction(function() use($presentation_id, $video_id){ + $this->tx_service->transaction(function () use ($presentation_id, $video_id) { $presentation = $this->presentation_repository->getById($presentation_id); - if(is_null($presentation)) + if (is_null($presentation)) throw new EntityNotFoundException('presentation not found!'); - if(!$presentation instanceof Presentation) + if (!$presentation instanceof Presentation) throw new EntityNotFoundException('presentation not found!'); $video = $presentation->getVideoBy($video_id); - if(is_null($video)) + if (is_null($video)) throw new EntityNotFoundException('video not found!'); - if(!$video instanceof PresentationVideo) + if (!$video instanceof PresentationVideo) throw new EntityNotFoundException('video not found!'); $presentation->removeVideo($video); @@ -163,4 +178,243 @@ final class PresentationService }); } + + /** + * @param Summit $summit + * @return int + */ + public function getSubmissionLimitFor(Summit $summit) + { + $res = -1; + if($summit->isSubmissionOpen()) { + $res = intval($summit->getCurrentSelectionPlanByStatus(SelectionPlan::STATUS_SUBMISSION)->getMaxSubmissionAllowedPerUser()); + } + + // zero means infinity + return $res === 0 ? PHP_INT_MAX : $res; + } + + /** + * @param Summit $summit + * @param Member $member + * @param array $data + * @return Presentation + * @throws ValidationException + * @throws EntityNotFoundException + * @throws \Exception + */ + public function submitPresentation(Summit $summit, Member $member, array $data) + { + return $this->tx_service->transaction(function () use ($summit, $member, $data) { + + $current_selection_plan = $summit->getCurrentSelectionPlanByStatus(SelectionPlan::STATUS_SUBMISSION); + $current_speaker = $this->speaker_repository->getByMember($member); + + if (is_null($current_speaker)) + throw new ValidationException(trans()); + + if (is_null($current_selection_plan)) + throw new ValidationException(trans()); + + // check qty + + $limit = $this->getSubmissionLimitFor($summit); + $count = count($current_speaker->getPresentationsBySelectionPlanAndRole($current_selection_plan, PresentationSpeaker::ROLE_CREATOR)) + + count($current_speaker->getPresentationsBySelectionPlanAndRole($current_selection_plan, PresentationSpeaker::ROLE_MODERATOR)) + + count($current_speaker->getPresentationsBySelectionPlanAndRole($current_selection_plan, PresentationSpeaker::ROLE_SPEAKER)); + + if ($count >= $limit) + throw new ValidationException(trans( + 'validation_errors.PresentationService.submitPresentation.limitReached', + ['limit' => $limit])); + + $presentation = new Presentation(); + $presentation->setCreator($member); + $presentation->setSelectionPlan($current_selection_plan); + + $summit->addEvent($presentation); + + $presentation->setProgress(Presentation::PHASE_SUMMARY); + + $presentation = $this->saveOrUpdatePresentation + ( + $summit, + $current_selection_plan, + $presentation, + $current_speaker, + $data + ); + + + return $presentation; + }); + + } + + /** + * @param Summit $summit + * @param int $presentation_id + * @param Member $member + * @param array $data + * @return Presentation + * @throws ValidationException + * @throws EntityNotFoundException + */ + public function updatePresentationSubmission(Summit $summit, $presentation_id, Member $member, array $data){ + return $this->tx_service->transaction(function () use ($summit, $presentation_id, $member, $data) { + + $current_selection_plan = $summit->getCurrentSelectionPlanByStatus(SelectionPlan::STATUS_SUBMISSION); + $current_speaker = $this->speaker_repository->getByMember($member); + + if (is_null($current_speaker)) + throw new ValidationException(trans()); + + if (is_null($current_selection_plan)) + throw new ValidationException(trans()); + + $presentation = $summit->getEvent($presentation_id); + + if (is_null($current_selection_plan)) + throw new EntityNotFoundException(trans()); + + return $this->saveOrUpdatePresentation + ( + $summit, + $current_selection_plan, + $presentation, + $current_speaker, + $data + ); + }); + } + + /** + * @param Summit $summit + * @param SelectionPlan $selection_plan + * @param Presentation $presentation + * @param PresentationSpeaker $current_speaker + * @param array $data + * @return Presentation + * @throws \Exception + */ + private function saveOrUpdatePresentation(Summit $summit, + SelectionPlan $selection_plan, + Presentation $presentation, + PresentationSpeaker $current_speaker, + array $data + ){ + return $this->tx_service->transaction(function () use ($summit, $selection_plan, $presentation, $current_speaker, $data) { + $event_type = $summit->getEventType(intval($data['type_id'])); + if (is_null($event_type)) { + throw new EntityNotFoundException( + trans( + 'not_found_errors.PresentationService.saveOrUpdatePresentation.eventTypeNotFound', + ['type_id' => $data['type_id']] + ) + ); + } + + if(!$event_type instanceof PresentationType){ + throw new ValidationException(trans( + 'validation_errors.PresentationService.saveOrUpdatePresentation.invalidPresentationType', + ['type_id' => $event_type->getIdentifier()]) + ); + } + + if(!$event_type->isShouldBeAvailableOnCfp()){ + throw new ValidationException(trans( + 'validation_errors.PresentationService.saveOrUpdatePresentation.notAvailableCFP', + ['type_id' => $event_type->getIdentifier()])); + } + + $track = $summit->getPresentationCategory(intval($data['track_id'])); + if (is_null($track)) { + throw new EntityNotFoundException( + trans( + 'not_found_errors.PresentationService.saveOrUpdatePresentation.trackNotFound', + ['track_id' => $data['track_id']] + ) + ); + } + + if(!$selection_plan->hasTrack($track)){ + throw new ValidationException(trans( + 'validation_errors.PresentationService.saveOrUpdatePresentation.trackDontBelongToSelectionPlan', + [ + 'selection_plan_id' => $selection_plan->getIdentifier(), + 'track_id' => $track->getIdentifier(), + ])); + } + + if(isset($data['title'])) + $presentation->setTitle(html_entity_decode(trim($data['title']))); + + if(isset($data['description'])) + $presentation->setAbstract(html_entity_decode(trim($data['description']))); + + if(isset($data['social_description'])) + $presentation->setSocialSummary(strip_tags(trim($data['social_description']))); + + if(isset($data['level'])) + $presentation->setLevel($data['level']); + + if(isset($data['attendees_expected_learnt'])) + $presentation->setAttendeesExpectedLearnt(html_entity_decode($data['attendees_expected_learnt'])); + + $presentation->setAttendingMedia(isset($data['attending_media']) ? + filter_var($data['attending_media'], FILTER_VALIDATE_BOOLEAN) : 0); + + $presentation->setType($event_type); + $presentation->setCategory($track); + // add me as speaker + $presentation->addSpeaker($current_speaker); + + if (isset($data['links'])) { + $presentation->clearLinks(); + foreach ($data['links'] as $link) { + $presentationLink = new PresentationLink(); + $presentationLink->setName(trim($link)); + $presentationLink->setLink(trim($link)); + $presentation->addLink($presentationLink); + } + } + + // extra questions values + if (isset($data['extra_questions'])) { + foreach ($data['extra_questions'] as $extra_question) { + if(!isset($extra_question['id'])) continue; + if(!isset($extra_question['value'])) continue; + + $extra_question_id = $extra_question['id']; + $extra_question_value = $extra_question['value']; + + $track_question = $track->getExtraQuestionById($extra_question_id); + if(is_null($track_question)){ + throw new EntityNotFoundException( + trans( + 'not_found_errors.PresentationService.saveOrUpdatePresentation.trackQuestionNotFound', + ['question_id' => $extra_question_id] + ) + ); + } + + $answer = $presentation->getTrackExtraQuestionAnswer($track_question); + + if(is_null($answer)){ + $answer = new TrackAnswer(); + $presentation->addAnswer($answer); + $track_question->addAnswer($answer); + } + + if(is_array($extra_question_value) ){ + $extra_question_value = str_replace('{comma}', ',', $extra_question_value); + $extra_question_value = implode(',', $extra_question_value); + } + + $answer->setValue($extra_question_value); + } + } + return $presentation; + }); + } } \ No newline at end of file diff --git a/app/Services/Model/SummitService.php b/app/Services/Model/SummitService.php index a7ea4029..49d59bb1 100644 --- a/app/Services/Model/SummitService.php +++ b/app/Services/Model/SummitService.php @@ -86,7 +86,6 @@ final class SummitService extends AbstractService implements ISummitService */ const MIN_EVENT_MINUTES = 5; - /** * @var ISummitEventRepository */ diff --git a/database/seeds/ApiEndpointsSeeder.php b/database/seeds/ApiEndpointsSeeder.php index 22fe8cb8..1dc83649 100644 --- a/database/seeds/ApiEndpointsSeeder.php +++ b/database/seeds/ApiEndpointsSeeder.php @@ -1348,6 +1348,27 @@ class ApiEndpointsSeeder extends Seeder 'http_method' => 'POST', 'scopes' => [sprintf('%s/summits/confirm-external-orders', $current_realm)], ), + // presentation submissions + [ + 'name' => 'submit-presentation', + 'route' => '/api/v1/summits/{id}/presentations', + 'http_method' => 'POST', + 'scopes' => [ + sprintf(SummitScopes::WriteSummitData, $current_realm), + sprintf(SummitScopes::WriteEventData, $current_realm), + sprintf(SummitScopes::WritePresentationData, $current_realm) + ], + ], + [ + 'name' => 'update-submit-presentation', + 'route' => '/api/v1/summits/{id}/presentations/{presentation_id}', + 'http_method' => 'PUT', + 'scopes' => [ + sprintf(SummitScopes::WriteSummitData, $current_realm), + sprintf(SummitScopes::WriteEventData, $current_realm), + sprintf(SummitScopes::WritePresentationData, $current_realm) + ], + ], //videos [ 'name' => 'get-presentation-videos', diff --git a/database/seeds/ApiScopesSeeder.php b/database/seeds/ApiScopesSeeder.php index 7764f000..e6f629ff 100644 --- a/database/seeds/ApiScopesSeeder.php +++ b/database/seeds/ApiScopesSeeder.php @@ -43,41 +43,46 @@ final class ApiScopesSeeder extends Seeder $api = EntityManager::getRepository(\App\Models\ResourceServer\Api::class)->findOneBy(['name' => 'summits']); $scopes = [ - array( + [ 'name' => sprintf(SummitScopes::ReadSummitData, $current_realm), 'short_description' => 'Get Summit Data', 'description' => 'Grants read only access for Summits Data', - ), - array( + ], + [ 'name' => sprintf(SummitScopes::ReadAllSummitData, $current_realm), 'short_description' => 'Get All Summits Data', 'description' => 'Grants read only access for All Summits Data', - ), - array( + ], + [ 'name' => sprintf('%s/me/read', $current_realm), 'short_description' => 'Get own summit member data', 'description' => 'Grants read only access for our own summit member data', - ), - array( + ], + [ 'name' => sprintf('%s/me/summits/events/favorites/add', $current_realm), 'short_description' => 'Allows to add Summit events as favorite', 'description' => 'Allows to add Summit events as favorite', - ), - array( + ], + [ 'name' => sprintf('%s/me/summits/events/favorites/delete', $current_realm), 'short_description' => 'Allows to remove Summit events as favorite', 'description' => 'Allows to remove Summit events as favorite', - ), - array( + ], + [ 'name' => sprintf(SummitScopes::WriteSummitData, $current_realm), 'short_description' => 'Write Summit Data', 'description' => 'Grants write access for Summits Data', - ), + ], array( - 'name' => sprintf('%s/summits/write-event', $current_realm), + 'name' => sprintf(SummitScopes::WriteEventData, $current_realm), 'short_description' => 'Write Summit Events', 'description' => 'Grants write access for Summits Events', ), + array( + 'name' => sprintf(SummitScopes::WritePresentationData, $current_realm), + 'short_description' => 'Write Summit Presentations', + 'description' => 'Grants write access for Summits Presentations', + ), array( 'name' => sprintf('%s/summits/delete-event', $current_realm), 'short_description' => 'Delete Summit Events', @@ -108,11 +113,11 @@ final class ApiScopesSeeder extends Seeder 'short_description' => 'Allow to read summit notifications', 'description' => 'Allow to read summit notifications', ), - array( + [ 'name' => sprintf(SummitScopes::WriteSpeakersData, $current_realm), 'short_description' => 'Write Speakers Data', 'description' => 'Grants write access for Speakers Data', - ), + ], [ 'name' => sprintf(SummitScopes::WriteMySpeakersData, $current_realm), 'short_description' => 'Write My Speakers Profile Data', diff --git a/resources/lang/en/not_found_errors.php b/resources/lang/en/not_found_errors.php index 7dafa011..924788a8 100644 --- a/resources/lang/en/not_found_errors.php +++ b/resources/lang/en/not_found_errors.php @@ -83,4 +83,8 @@ return [ 'SummitSelectionPlanService.addTrackGroupToSelectionPlan.TrackGroupNotFound' => 'track group :track_group_id not found on summit :summit_id', 'SummitSelectionPlanService.deleteTrackGroupToSelectionPlan.SelectionPlanNotFound' => 'selection plan :selection_plan_id not found on summit :summit_id', 'SummitSelectionPlanService.deleteTrackGroupToSelectionPlan.TrackGroupNotFound' => 'track group :track_group_id not found on summit :summit_id', + // Presentations + 'PresentationService.saveOrUpdatePresentation.trackNotFound' => 'track :track_id not found.', + 'PresentationService.submitPresentation.eventTypeNotFound' => 'event type :type_id not found.', + 'PresentationService.saveOrUpdatePresentation.trackQuestionNotFound' => 'extra question :question_id not found.', ]; \ No newline at end of file diff --git a/resources/lang/en/validation_errors.php b/resources/lang/en/validation_errors.php index 47b0fda5..2d26ebde 100644 --- a/resources/lang/en/validation_errors.php +++ b/resources/lang/en/validation_errors.php @@ -78,4 +78,9 @@ return [ 'Summit.checkSelectionPlanConflicts.conflictOnVotingWorkflow' => 'there is a conflict on voting dates with selection plan :selection_plan_id on summit :summit_id', 'SummitSelectionPlanService.addSelectionPlan.alreadyExistName' => 'there is already another selection plan with same name on summit :summit_id', 'SummitSelectionPlanService.updateSelectionPlan.alreadyExistName' => 'there is already another selection plan with same name on summit :summit_id', + // Presentations + 'PresentationService.saveOrUpdatePresentation.invalidPresentationType' => 'type id :type_id is not a valid presentation type', + 'PresentationService.saveOrUpdatePresentation.notAvailableCFP' => 'type id :type_id is not a available for CFP', + 'PresentationService.saveOrUpdatePresentation.trackDontBelongToSelectionPlan' => 'track :track_id does not belongs to selection plan :selection_plan_id', + 'PresentationService.submitPresentation.limitReached' => 'You reached the limit :limit of presentations.', ]; \ No newline at end of file diff --git a/tests/OAuth2PresentationSubmissionTest.php b/tests/OAuth2PresentationSubmissionTest.php new file mode 100644 index 00000000..56c90f38 --- /dev/null +++ b/tests/OAuth2PresentationSubmissionTest.php @@ -0,0 +1,68 @@ + $summit_id, + ]; + + $title = str_random(16).'_presentation'; + $data = [ + 'title' => $title, + 'description' => 'this is a description', + 'social_description' => 'this is a social description', + 'level' => 'N/A', + 'attendees_expected_learnt' => 'super duper', + 'type_id' => 171, + 'track_id' => 248, + 'attending_media' => true, + 'links' => ['https://www.google.com'], + 'extra_questions' => [ + [ + 'id' => 24, + 'value' => 'test', + ] + ] + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $response = $this->action( + "POST", + "OAuth2PresentationApiController@submitPresentation", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $content = $response->getContent(); + $this->assertResponseStatus(201); + $presentation = json_decode($content); + $this->assertTrue(!is_null($presentation)); + $this->assertEquals($title, $presentation->title); + return $presentation; + } +} \ No newline at end of file