diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php index 251e0032..ac7a0b13 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php @@ -17,12 +17,16 @@ use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Request; use Illuminate\Support\Facades\Validator; +use libs\utils\HTMLCleaner; +use models\exceptions\EntityNotFoundException; +use models\exceptions\ValidationException; use models\oauth2\IResourceServerContext; use models\summit\IEventFeedbackRepository; use models\summit\ISpeakerRepository; use models\summit\ISummitEventRepository; use models\summit\ISummitRepository; use ModelSerializers\SerializerRegistry; +use services\model\ISpeakerService; use services\model\ISummitService; use utils\FilterParser; use utils\FilterParserException; @@ -36,7 +40,7 @@ use utils\PagingInfo; final class OAuth2SummitSpeakersApiController extends OAuth2ProtectedController { /** - * @var ISummitService + * @var ISpeakerService */ private $service; @@ -62,7 +66,7 @@ final class OAuth2SummitSpeakersApiController extends OAuth2ProtectedController ISummitEventRepository $event_repository, ISpeakerRepository $speaker_repository, IEventFeedbackRepository $event_feedback_repository, - ISummitService $service, + ISpeakerService $service, IResourceServerContext $resource_server_context ) { parent::__construct($resource_server_context); @@ -251,4 +255,64 @@ final class OAuth2SummitSpeakersApiController extends OAuth2ProtectedController } + public function addSpeaker($summit_id){ + try { + if(!Request::isJson()) return $this->error403(); + $data = Input::json(); + + $summit = SummitFinderStrategyFactory::build($this->repository, $this->resource_server_context)->find($summit_id); + if (is_null($summit)) return $this->error404(); + + $rules = array + ( + 'title' => 'required|string|max:100', + 'first_name' => 'required|string|max:100', + 'last_name' => 'required|string|max:100', + 'bio' => 'sometimes|string', + 'irc' => 'sometimes|string|max:50', + 'twitter' => 'sometimes|string|max:50', + 'member_id' => 'sometimes|integer', + 'email' => 'sometimes|string|max:50', + 'on_site_phone' => 'sometimes|string|max:50', + 'registered' => 'sometimes|boolean', + 'confirmed' => 'sometimes|boolean', + 'checked_in' => 'sometimes|boolean', + 'registration_code' => 'sometimes|string', + ); + + // Creates a Validator instance and validates the data. + $validation = Validator::make($data->all(), $rules); + + if ($validation->fails()) { + $messages = $validation->messages()->toArray(); + + return $this->error412 + ( + $messages + ); + } + + $fields = [ + 'title', + 'bio', + ]; + + $speaker = $this->service->addSpeaker($summit, HTMLCleaner::cleanData($data->all(), $fields)); + + return $this->created(SerializerRegistry::getInstance()->getSerializer($speaker)->serialize()); + } + 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 (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 1eba2d33..65e856a9 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -183,6 +183,7 @@ Route::group([ // speakers Route::group(array('prefix' => 'speakers'), function () { + Route::post('', [ 'middleware' => 'auth.user:administrators', 'uses' => 'OAuth2SummitSpeakersApiController@addSpeaker']); Route::get('', 'OAuth2SummitSpeakersApiController@getSpeakers'); Route::group(array('prefix' => '{speaker_id}'), function () { diff --git a/app/Models/Foundation/Main/EmailCreationRequests/EmailCreationRequest.php b/app/Models/Foundation/Main/EmailCreationRequests/EmailCreationRequest.php new file mode 100644 index 00000000..f425b46a --- /dev/null +++ b/app/Models/Foundation/Main/EmailCreationRequests/EmailCreationRequest.php @@ -0,0 +1,100 @@ +template_name; + } + + /** + * @param string $template_name + */ + public function setTemplateName($template_name) + { + $this->template_name = $template_name; + } + + /** + * @return bool + */ + public function isProcessed() + { + return $this->processed; + } + + /** + * @param bool $processed + */ + public function setProcessed($processed) + { + $this->processed = $processed; + } + + /** + * @return DateTime + */ + public function getProcessedDate() + { + return $this->processed_date; + } + + /** + * @param DateTime $processed_date + */ + public function setProcessedDate($processed_date) + { + $this->processed_date = $processed_date; + } + + public function __construct() + { + $this->processed = false; + parent::__construct(); + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Main/EmailCreationRequests/SpeakerCreationEmailCreationRequest.php b/app/Models/Foundation/Main/EmailCreationRequests/SpeakerCreationEmailCreationRequest.php new file mode 100644 index 00000000..b64d46aa --- /dev/null +++ b/app/Models/Foundation/Main/EmailCreationRequests/SpeakerCreationEmailCreationRequest.php @@ -0,0 +1,52 @@ +template_name = "presentation-speaker-creation"; + parent::__construct(); + } + + /** + * @return PresentationSpeaker + */ + public function getSpeaker() + { + return $this->speaker; + } + + /** + * @param PresentationSpeaker $speaker + */ + public function setSpeaker($speaker) + { + $this->speaker = $speaker; + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php b/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php index 366a4f7c..66b2b82a 100644 --- a/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php +++ b/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeaker.php @@ -60,12 +60,28 @@ class PresentationSpeaker extends SilverstripeBaseModel private $twitter_name; /** - * @ORM\ManyToOne(targetEntity="SpeakerRegistrationRequest") + * @ORM\Column(name="CreatedFromAPI", type="boolean") + */ + private $created_from_api; + + /** + * @ORM\ManyToOne(targetEntity="SpeakerRegistrationRequest", cascade={"persist"}) * @ORM\JoinColumn(name="RegistrationRequestID", referencedColumnName="ID") * @var SpeakerRegistrationRequest */ private $registration_request; + /** + * @ORM\OneToMany(targetEntity="PresentationSpeakerSummitAssistanceConfirmationRequest", mappedBy="speaker", cascade={"persist"}) + * @var PresentationSpeakerSummitAssistanceConfirmationRequest[] + */ + private $summit_assistances; + + /** + * @ORM\OneToMany(targetEntity="SpeakerSummitRegistrationPromoCode", mappedBy="speaker", cascade={"persist"}) + * @var SpeakerSummitRegistrationPromoCode[] + */ + private $promo_codes; /** * @ORM\ManyToMany(targetEntity="models\summit\Presentation", inversedBy="speakers") @@ -86,7 +102,6 @@ class PresentationSpeaker extends SilverstripeBaseModel */ private $moderated_presentations; - /** * @ORM\ManyToOne(targetEntity="models\main\File") * @ORM\JoinColumn(name="PhotoID", referencedColumnName="ID") @@ -200,8 +215,11 @@ class PresentationSpeaker extends SilverstripeBaseModel public function __construct() { parent::__construct(); + $this->presentations = new ArrayCollection; $this->moderated_presentations = new ArrayCollection; + $this->summit_assistances = new ArrayCollection; + $this->promo_codes = new ArrayCollection; } /** @@ -211,6 +229,16 @@ class PresentationSpeaker extends SilverstripeBaseModel $this->presentations->add($presentation); } + /** + * @param SpeakerSummitRegistrationPromoCode $code + * @return $this + */ + public function addPromoCode(SpeakerSummitRegistrationPromoCode $code){ + $this->promo_codes->add($code); + $code->setSpeaker($this); + return $this; + } + /** * @param null|int $summit_id * @param bool|true $published_ones @@ -317,6 +345,13 @@ class PresentationSpeaker extends SilverstripeBaseModel return $this->member; } + /** + * @param Member $member + */ + public function setMember(Member $member){ + $this->member = $member; + } + /** * @return bool */ @@ -330,6 +365,7 @@ class PresentationSpeaker extends SilverstripeBaseModel public function getMemberId() { try{ + if(is_null($this->member)) return 0; return $this->member->getId(); } catch(\Exception $ex){ @@ -351,6 +387,7 @@ class PresentationSpeaker extends SilverstripeBaseModel public function setRegistrationRequest($registration_request) { $this->registration_request = $registration_request; + $registration_request->setSpeaker($this); } /** @@ -364,4 +401,50 @@ class PresentationSpeaker extends SilverstripeBaseModel } return $fullname; } + + /** + * @return PresentationSpeakerSummitAssistanceConfirmationRequest[] + */ + public function getSummitAssistances() + { + return $this->summit_assistances; + } + + /** + * @param PresentationSpeakerSummitAssistanceConfirmationRequest $assistance + * @return $this + */ + public function addSummitAssistance(PresentationSpeakerSummitAssistanceConfirmationRequest $assistance){ + $this->summit_assistances->add($assistance); + $assistance->setSpeaker($this); + return $this; + } + + /** + * @return mixed + */ + public function getCreatedFromApi() + { + return $this->created_from_api; + } + + /** + * @param mixed $created_from_api + */ + public function setCreatedFromApi($created_from_api) + { + $this->created_from_api = $created_from_api; + } + + /** + * @param Summit $summit + * @return PresentationSpeakerSummitAssistanceConfirmationRequest + */ + public function buildAssistanceFor(Summit $summit) + { + $request = new PresentationSpeakerSummitAssistanceConfirmationRequest; + $request->setSummit($summit); + $request->setSpeaker($this); + return $request; + } } \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeakerSummitAssistanceConfirmationRequest.php b/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeakerSummitAssistanceConfirmationRequest.php new file mode 100644 index 00000000..3978a45e --- /dev/null +++ b/app/Models/Foundation/Summit/Events/Presentations/PresentationSpeakerSummitAssistanceConfirmationRequest.php @@ -0,0 +1,157 @@ +on_site_phone; + } + + /** + * @param string $on_site_phone + */ + public function setOnSitePhone($on_site_phone) + { + $this->on_site_phone = $on_site_phone; + } + + /** + * @return bool + */ + public function isRegistered() + { + return $this->registered; + } + + /** + * @param bool $registered + */ + public function setRegistered($registered) + { + $this->registered = $registered; + } + + /** + * @return bool + */ + public function isConfirmed() + { + return $this->is_confirmed; + } + + /** + * @param bool $is_confirmed + */ + public function setIsConfirmed($is_confirmed) + { + $this->is_confirmed = $is_confirmed; + } + + /** + * @return bool + */ + public function isCheckedIn() + { + return $this->checked_in; + } + + /** + * @param bool $checked_in + */ + public function setCheckedIn($checked_in) + { + $this->checked_in = $checked_in; + } + + /** + * @return PresentationSpeaker + */ + public function getSpeaker() + { + return $this->speaker; + } + + /** + * @param PresentationSpeaker $speaker + */ + public function setSpeaker($speaker) + { + $this->speaker = $speaker; + } + + /** + * @return Summit + */ + public function getSummit() + { + return $this->summit; + } + + /** + * @param Summit $summit + */ + public function setSummit($summit) + { + $this->summit = $summit; + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Events/Presentations/SpeakerRegistrationRequest.php b/app/Models/Foundation/Summit/Events/Presentations/SpeakerRegistrationRequest.php index 98cf34aa..7a49621b 100644 --- a/app/Models/Foundation/Summit/Events/Presentations/SpeakerRegistrationRequest.php +++ b/app/Models/Foundation/Summit/Events/Presentations/SpeakerRegistrationRequest.php @@ -11,15 +11,14 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ - use models\main\Member; +use models\utils\RandomGenerator; use models\utils\SilverstripeBaseModel; use Doctrine\ORM\Mapping AS ORM; use Doctrine\Common\Collections\ArrayCollection; - /** * Class SpeakerRegistrationRequest - * @ORM\Entity + * @ORM\Entity(repositoryClass="App\Repositories\Summit\DoctrineSpeakerRegistrationRequestRepository") * @ORM\Table(name="SpeakerRegistrationRequest") * @package models\summit */ @@ -49,7 +48,7 @@ class SpeakerRegistrationRequest extends SilverstripeBaseModel * @ORM\JoinColumn(name="SpeakerID", referencedColumnName="ID") * @var PresentationSpeaker */ - private $moderator; + private $speaker; /** * @ORM\ManyToOne(targetEntity="models\main\Member") @@ -58,6 +57,17 @@ class SpeakerRegistrationRequest extends SilverstripeBaseModel */ private $proposer; + /** + * @ORM\Column(name="ConfirmationHash", type="string") + * @var string + */ + private $confirmation_hash; + + /** + * @var string + */ + private $token; + /** * @return mixed */ @@ -109,17 +119,17 @@ class SpeakerRegistrationRequest extends SilverstripeBaseModel /** * @return PresentationSpeaker */ - public function getModerator() + public function getSpeaker() { - return $this->moderator; + return $this->speaker; } /** - * @param PresentationSpeaker $moderator + * @param PresentationSpeaker $speaker */ - public function setModerator($moderator) + public function setSpeaker($speaker) { - $this->moderator = $moderator; + $this->speaker = $speaker; } /** @@ -137,4 +147,36 @@ class SpeakerRegistrationRequest extends SilverstripeBaseModel { $this->proposer = $proposer; } + + /** + * @return string + */ + public function generateConfirmationToken() { + $generator = new RandomGenerator(); + $this->is_confirmed = false; + $this->confirmation_date = null; + $this->token = $generator->randomToken(); + $this->confirmation_hash = self::HashConfirmationToken($this->token); + return $this->token; + } + + public static function HashConfirmationToken($token){ + return md5($token); + } + + /** + * @return string + */ + public function getConfirmationHash() + { + return $this->confirmation_hash; + } + + /** + * @return string + */ + public function getToken() + { + return $this->token; + } } \ 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 86a490b5..e6d6972e 100644 --- a/app/Models/Foundation/Summit/Events/SummitEvent.php +++ b/app/Models/Foundation/Summit/Events/SummitEvent.php @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ - +use Doctrine\ORM\Mapping AS ORM; use App\Events\SummitEventCreated; use App\Events\SummitEventDeleted; use App\Events\SummitEventUpdated; @@ -23,13 +23,11 @@ use models\main\Member; use models\main\Tag; use models\utils\PreRemoveEventArgs; use models\utils\SilverstripeBaseModel; -use Doctrine\ORM\Mapping AS ORM; use Doctrine\Common\Collections\ArrayCollection; use DateTime; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Config; use Cocur\Slugify\Slugify; - /** * @ORM\Entity(repositoryClass="App\Repositories\Summit\DoctrineSummitEventRepository") * @ORM\Table(name="SummitEvent") diff --git a/app/Models/Foundation/Summit/PromoCodes/MemberSummitRegistrationPromoCode.php b/app/Models/Foundation/Summit/PromoCodes/MemberSummitRegistrationPromoCode.php new file mode 100644 index 00000000..c829dca5 --- /dev/null +++ b/app/Models/Foundation/Summit/PromoCodes/MemberSummitRegistrationPromoCode.php @@ -0,0 +1,137 @@ +first_name; + } + + /** + * @param mixed $first_name + */ + public function setFirstName($first_name) + { + $this->first_name = $first_name; + } + + /** + * @return string + */ + public function getLastName() + { + return $this->last_name; + } + + /** + * @param string $last_name + */ + public function setLastName($last_name) + { + $this->last_name = $last_name; + } + + /** + * @return mixed + */ + public function getEmail() + { + return $this->email; + } + + /** + * @param mixed $email + */ + public function setEmail($email) + { + $this->email = $email; + } + + /** + * @return mixed + */ + public function getType() + { + return $this->type; + } + + /** + * @param mixed $type + */ + public function setType($type) + { + $this->type = $type; + } + + /** + * @return Member + */ + public function getOwner() + { + return $this->owner; + } + + /** + * @param Member $owner + */ + public function setOwner($owner) + { + $this->owner = $owner; + } + +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/PromoCodes/SpeakerSummitRegistrationPromoCode.php b/app/Models/Foundation/Summit/PromoCodes/SpeakerSummitRegistrationPromoCode.php new file mode 100644 index 00000000..e2b55a92 --- /dev/null +++ b/app/Models/Foundation/Summit/PromoCodes/SpeakerSummitRegistrationPromoCode.php @@ -0,0 +1,73 @@ +type; + } + + /** + * @param string $type + */ + public function setType($type) + { + $this->type = $type; + } + + /** + * @return PresentationSpeaker + */ + public function getSpeaker() + { + return $this->speaker; + } + + /** + * @param PresentationSpeaker $speaker + */ + public function setSpeaker($speaker) + { + $this->speaker = $speaker; + } + + public function __construct() + { + parent::__construct(); + $this->redeemed = false; + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/PromoCodes/SponsorSummitRegistrationPromoCode.php b/app/Models/Foundation/Summit/PromoCodes/SponsorSummitRegistrationPromoCode.php new file mode 100644 index 00000000..b64a38f3 --- /dev/null +++ b/app/Models/Foundation/Summit/PromoCodes/SponsorSummitRegistrationPromoCode.php @@ -0,0 +1,54 @@ +sponsor; + } + + /** + * @param Company $sponsor + */ + public function setSponsor($sponsor) + { + $this->sponsor = $sponsor; + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/PromoCodes/SummitRegistrationPromoCode.php b/app/Models/Foundation/Summit/PromoCodes/SummitRegistrationPromoCode.php new file mode 100644 index 00000000..3cdb7566 --- /dev/null +++ b/app/Models/Foundation/Summit/PromoCodes/SummitRegistrationPromoCode.php @@ -0,0 +1,149 @@ +code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * @return bool + */ + public function isEmailSent() + { + return $this->email_sent; + } + + /** + * @param bool $email_sent + */ + public function setEmailSent($email_sent) + { + $this->email_sent = $email_sent; + } + + /** + * @return bool + */ + public function isRedeemed() + { + return $this->redeemed; + } + + /** + * @param bool $redeemed + */ + public function setRedeemed($redeemed) + { + $this->redeemed = $redeemed; + } + + /** + * @return string + */ + public function getSource() + { + return $this->source; + } + + /** + * @param string $source + */ + public function setSource($source) + { + $this->source = $source; + } + + /** + * @return Member + */ + public function getCreator() + { + return $this->creator; + } + + /** + * @param Member $creator + */ + public function setCreator($creator) + { + $this->creator = $creator; + } + + public function __construct() + { + $this->email_sent = false; + parent::__construct(); + } +} \ No newline at end of file diff --git a/app/Models/Foundation/Summit/Repositories/IEmailCreationRequestRepository.php b/app/Models/Foundation/Summit/Repositories/IEmailCreationRequestRepository.php new file mode 100644 index 00000000..f34e20fc --- /dev/null +++ b/app/Models/Foundation/Summit/Repositories/IEmailCreationRequestRepository.php @@ -0,0 +1,23 @@ +GetRandom(64, 0)); + } + } catch (Exception $ex) { + } + } + + // Fallback to good old mt_rand() + return uniqid(mt_rand(), true); + } + + /** + * Generates a random token that can be used for session IDs, CSRF tokens etc., based on + * hash algorithms. + * + * If you are using it as a password equivalent (e.g. autologin token) do NOT store it + * in the database as a plain text but encrypt it with Member::encryptWithUserSettings. + * + * @param String $algorithm Any identifier listed in hash_algos() (Default: whirlpool) + * + * @return String Returned length will depend on the used $algorithm + */ + public function randomToken($algorithm = 'whirlpool') { + return hash($algorithm, $this->generateEntropy()); + } +} diff --git a/app/Repositories/Main/DoctrineEmailCreationRequestRepository.php b/app/Repositories/Main/DoctrineEmailCreationRequestRepository.php new file mode 100644 index 00000000..aebd3c59 --- /dev/null +++ b/app/Repositories/Main/DoctrineEmailCreationRequestRepository.php @@ -0,0 +1,32 @@ +getEntityManager() + ->createQueryBuilder() + ->select("m") + ->from(\models\main\Member::class, "m") + ->where("m.email = :email") + ->setParameter("email", trim($email)) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); } /** diff --git a/app/Repositories/Summit/DoctrineSpeakerRegistrationRequestRepository.php b/app/Repositories/Summit/DoctrineSpeakerRegistrationRequestRepository.php new file mode 100644 index 00000000..a754a8b5 --- /dev/null +++ b/app/Repositories/Summit/DoctrineSpeakerRegistrationRequestRepository.php @@ -0,0 +1,86 @@ +getByHash($hash) != null; + } + + /** + * @param string $hash + * @return SpeakerRegistrationRequest + */ + public function getByHash($hash) + { + return $this->getEntityManager() + ->createQueryBuilder() + ->select("r") + ->from(SpeakerRegistrationRequest::class, "r") + ->where("r.confirmation_hash = :confirmation_hash") + ->setParameter("confirmation_hash", trim($hash)) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } + + /** + * @param string $email + * @return bool + */ + public function existByEmail($email) + { + return $this->getByEmail($email) != null; + } + + /** + * @param string $email + * @return SpeakerRegistrationRequest + */ + public function getByEmail($email) + { + return $this->getEntityManager() + ->createQueryBuilder() + ->select("r") + ->from(SpeakerRegistrationRequest::class, "r") + ->where("r.email = :email") + ->setParameter("email", trim($email)) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } +} \ No newline at end of file diff --git a/app/Repositories/Summit/DoctrineSpeakerRepository.php b/app/Repositories/Summit/DoctrineSpeakerRepository.php index 807c4883..b5bc3292 100644 --- a/app/Repositories/Summit/DoctrineSpeakerRepository.php +++ b/app/Repositories/Summit/DoctrineSpeakerRepository.php @@ -14,6 +14,7 @@ use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Query\ResultSetMappingBuilder; +use models\main\Member; use models\summit\ISpeakerRepository; use models\summit\PresentationSpeaker; use models\summit\Summit; @@ -22,7 +23,6 @@ use utils\Filter; use utils\Order; use utils\PagingInfo; use utils\PagingResponse; - /** * Class DoctrineSpeakerRepository * @package App\Repositories\Summit @@ -355,4 +355,21 @@ SQL; { return PresentationSpeaker::class; } + + /** + * @param Member $member + * @return PresentationSpeaker + */ + public function getByMember(Member $member) + { + return $this->getEntityManager() + ->createQueryBuilder() + ->select("s") + ->from(PresentationSpeaker::class, "s") + ->where("s.member = :member") + ->setParameter("member", $member) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } } \ No newline at end of file diff --git a/app/Repositories/Summit/DoctrineSpeakerSummitRegistrationPromoCodeRepository.php b/app/Repositories/Summit/DoctrineSpeakerSummitRegistrationPromoCodeRepository.php new file mode 100644 index 00000000..f5e8b562 --- /dev/null +++ b/app/Repositories/Summit/DoctrineSpeakerSummitRegistrationPromoCodeRepository.php @@ -0,0 +1,107 @@ +getId() == 0) return null; + return $this->getEntityManager() + ->createQueryBuilder() + ->select("c") + ->from(SpeakerSummitRegistrationPromoCode::class, "c") + ->where("c.speaker = :speaker") + ->andWhere("c.summit = :summit") + ->setParameter("speaker", $speaker) + ->setParameter("summit", $summit) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } + + /** + * @param string $code + * @param Summit $summit + * @return bool + */ + public function isAssignedCode($code, Summit $summit) + { + return $this->getAssignedCode($code, $summit) != null; + } + + /** + * @param string $code + * @param Summit $summit + * @return SpeakerSummitRegistrationPromoCode + */ + public function getAssignedCode($code, Summit $summit) + { + return $this->getEntityManager() + ->createQueryBuilder() + ->select("c") + ->from(SpeakerSummitRegistrationPromoCode::class, "c") + ->where("c.speaker is not null") + ->andWhere("c.summit = :summit") + ->andWhere("c.code = :code") + ->setParameter("summit", $summit) + ->setParameter("code", trim($code)) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } + + /** + * @param string $code + * @param Summit $summit + * @return SpeakerSummitRegistrationPromoCode + */ + public function getNotAssignedCode($code, Summit $summit) + { + return $this->getEntityManager() + ->createQueryBuilder() + ->select("c") + ->from(SpeakerSummitRegistrationPromoCode::class, "c") + ->where("c.speaker is null") + ->andWhere("c.summit = :summit") + ->andWhere("c.code = :code") + ->setParameter("summit", $summit) + ->setParameter("code", trim($code)) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } +} \ No newline at end of file diff --git a/app/Repositories/Summit/DoctrineSummitRegistrationPromoCodeRepository.php b/app/Repositories/Summit/DoctrineSummitRegistrationPromoCodeRepository.php new file mode 100644 index 00000000..a116fe5a --- /dev/null +++ b/app/Repositories/Summit/DoctrineSummitRegistrationPromoCodeRepository.php @@ -0,0 +1,31 @@ +speaker_repository = $speaker_repository; + $this->member_repository = $member_repository; + $this->folder_repository = $folder_repository; + $this->speaker_registration_request_repository = $speaker_registration_request_repository; + $this->registration_code_repository = $registration_code_repository; + $this->email_creation_request_repository = $email_creation_request_repository; + $this->tx_service = $tx_service; + } + + /** + * @param Summit $summit + * @param array $data + * @throws ValidationException + * @return PresentationSpeaker + */ + public function addSpeaker(Summit $summit, array $data){ + + return $this->tx_service->transaction(function() use($data, $summit){ + + $speaker = new PresentationSpeaker(); + $speaker->setCreatedFromApi(true); + $member_id = 0; + + if(!isset($data['email']) && !isset($data['member_id'])) + throw + new ValidationException + ("you must provide an email or a member_id in order to create a speaker!"); + + if(isset($data['member_id']) && intval($data['member_id']) > 0){ + $member_id = intval($data['member_id']); + $existent_speaker = $this->speaker_repository->getByMember($member_id); + if(!is_null($existent_speaker)) + throw new ValidationException + ( + sprintf + ( + "member_id %s already has assigned an speaker!", + $member_id + ) + ); + + $member = $this->member_repository->getById($member_id); + if(is_null($member)) + throw new EntityNotFoundException(sprintf("member id %s does not exists!", $member_id)); + $speaker->setMember($member); + } + + $this->updateSpeakerMainData($speaker, $data); + + if($member_id === 0 && isset($data['email'])){ + $email = trim($data['email']); + $member = $this->member_repository->getByEmail($email); + if(is_null($member)){ + $this->registerSpeaker($speaker, $email); + } + else + { + $existent_speaker = $this->speaker_repository->getByMember($member); + if(!is_null($existent_speaker)) + throw new ValidationException + ( + sprintf + ( + "member id %s already has assigned a speaker id %s!", + $member->getIdentifier(), + $existent_speaker->getIdentifier() + ) + ); + $speaker->setMember($member); + } + } + + $on_site_phone = isset($data['on_site_phone']) ? trim($data['on_site_phone']) : null; + $registered = isset($data['registered']) ? 1 : 0; + $checked_in = isset($data['checked_in']) ? 1 : 0; + $confirmed = isset($data['confirmed']) ? 1 : 0; + + $summit_assistance = $speaker->buildAssistanceFor($summit); + $summit_assistance->setOnSitePhone($on_site_phone); + $summit_assistance->setRegistered($registered); + $summit_assistance->setIsConfirmed($confirmed); + $summit_assistance->setCheckedIn($checked_in); + + $speaker->addSummitAssistance($summit_assistance); + + $reg_code = isset($data['registration_code']) ? trim($data['registration_code']) : null; + if(!empty($reg_code)){ + $this->registerSummitPromoCodeByValue($speaker, $summit, $reg_code); + } + $this->speaker_repository->add($speaker); + + $email_request = new SpeakerCreationEmailCreationRequest(); + $email_request->setSpeaker($speaker); + $this->email_creation_request_repository->add($email_request); + + return $speaker; + }); + } + + /** + * @param PresentationSpeaker $speaker + * @param string $email + * @return SpeakerRegistrationRequest + * @throws ValidationException + */ + private function registerSpeaker(PresentationSpeaker $speaker, $email){ + + if($this->speaker_registration_request_repository->existByEmail($email)) + throw new ValidationException(sprintf("email %s already has a Speaker Registration Request", $email)); + + $registration_request = new SpeakerRegistrationRequest(); + $registration_request->setEmail($email); + + do { + $registration_request->generateConfirmationToken(); + }while($this->speaker_registration_request_repository->existByHash($registration_request->getConfirmationHash())); + + $speaker->setRegistrationRequest($registration_request); + return $registration_request; + } + + /** + * @param PresentationSpeaker $speaker + * @param Summit $summit + * @param string $reg_code + * @return SpeakerSummitRegistrationPromoCode + * @throws ValidationException + */ + public function registerSummitPromoCodeByValue(PresentationSpeaker $speaker, Summit $summit, $reg_code){ + + return $this->tx_service->transaction(function() use($speaker, $summit, $reg_code) { + $existent_code = $this->registration_code_repository->getBySpeakerAndSummit($speaker, $summit); + + // we are trying to update the promo code with another one .... + if ($existent_code && $reg_code !== $existent_code->getCode()) { + throw new ValidationException(sprintf( + 'speaker has been already assigned to another registration code (%s)', $existent_code->getCode() + )); + } + + if ($assigned_code = $this->registration_code_repository->getAssignedCode($reg_code, $summit)) { + throw new ValidationException(sprintf( + 'there is another speaker with that code for this summit ( speaker id %s )', $assigned_code->getSpeaker()->getId() + )); + } + + $code = $this->registration_code_repository->getNotAssignedCode($reg_code, $summit); + + if (is_null($code)) { + //create it + $code = new SpeakerSummitRegistrationPromoCode(); + $code->setSummit($summit); + $code->setCode($reg_code); + } + + $speaker->addPromoCode($code); + + return $code; + }); + } + + private function updateSpeakerMainData(PresentationSpeaker $speaker, array $data){ + if(isset($data['title'])) + $speaker->setTitle(trim($data['title'])); + + if(isset($data['bio'])) + $speaker->setBio(trim($data['bio'])); + + if(isset($data['first_name'])) + $speaker->setFirstName(trim($data['first_name'])); + + if(isset($data['last_name'])) + $speaker->setLastName(trim($data['last_name'])); + + if(isset($data['irc'])) + $speaker->setIrcHandle(trim($data['irc'])); + + if(isset($data['twitter'])) + $speaker->setTwitterName(trim($data['twitter'])); + + } + +} \ No newline at end of file diff --git a/app/Services/Model/SummitService.php b/app/Services/Model/SummitService.php index 85a6f81d..27d1f2ae 100644 --- a/app/Services/Model/SummitService.php +++ b/app/Services/Model/SummitService.php @@ -79,6 +79,7 @@ final class SummitService implements ISummitService * minimun number of minutes that an event must last */ const MIN_EVENT_MINUTES = 5; + /** * @var ITransactionService */ @@ -144,7 +145,6 @@ final class SummitService implements ISummitService */ private $company_repository; - /** * @var IGroupRepository */ diff --git a/app/Services/ServicesProvider.php b/app/Services/ServicesProvider.php index c102041f..912ef0b8 100644 --- a/app/Services/ServicesProvider.php +++ b/app/Services/ServicesProvider.php @@ -53,6 +53,8 @@ class ServicesProvider extends ServiceProvider App::singleton('services\model\ISummitService', 'services\model\SummitService'); + App::singleton('services\model\ISpeakerService', 'services\model\SpeakerService'); + App::singleton('services\model\IPresentationService', 'services\model\PresentationService'); App::singleton('services\model\IChatTeamService', 'services\model\ChatTeamService'); diff --git a/database/seeds/ApiEndpointsSeeder.php b/database/seeds/ApiEndpointsSeeder.php index 2a903592..41b375c9 100644 --- a/database/seeds/ApiEndpointsSeeder.php +++ b/database/seeds/ApiEndpointsSeeder.php @@ -157,6 +157,14 @@ class ApiEndpointsSeeder extends Seeder sprintf(SummitScopes::ReadAllSummitData, $current_realm) ], ), + array( + 'name' => 'add-speaker', + 'route' => '/api/v1/summits/{id}/speakers', + 'http_method' => 'POST', + 'scopes' => [ + sprintf(SummitScopes::WriteSpeakersData, $current_realm), + ], + ), array( 'name' => 'get-all-speakers', 'route' => '/api/v1/speakers', diff --git a/database/seeds/ApiScopesSeeder.php b/database/seeds/ApiScopesSeeder.php index e9a5feae..67c3635a 100644 --- a/database/seeds/ApiScopesSeeder.php +++ b/database/seeds/ApiScopesSeeder.php @@ -71,7 +71,7 @@ final class ApiScopesSeeder extends Seeder 'description' => 'Allows to remove Summit events as favorite', ), array( - 'name' => sprintf('%s/summits/write', $current_realm), + 'name' => sprintf(SummitScopes::WriteSummitData, $current_realm), 'short_description' => 'Write Summit Data', 'description' => 'Grants write access for Summits Data', ), @@ -109,7 +109,12 @@ final class ApiScopesSeeder extends Seeder 'name' => sprintf('%s/summits/read-notifications', $current_realm), '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', + ), ]; foreach ($scopes as $scope_info) { diff --git a/tests/OAuth2SpeakersApiTest.php b/tests/OAuth2SpeakersApiTest.php new file mode 100644 index 00000000..c91b97d2 --- /dev/null +++ b/tests/OAuth2SpeakersApiTest.php @@ -0,0 +1,133 @@ + $summit_id, + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $data = [ + + 'title' => 'Developer!', + 'first_name' => 'Sebastian', + 'last_name' => 'Marcet', + 'email' => 'sebastian.ge4.marcet@gmail.com' + ]; + + $response = $this->action + ( + "POST", + "OAuth2SummitSpeakersApiController@addSpeaker", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $this->assertResponseStatus(201); + $content = $response->getContent(); + $speaker = json_decode($content); + $this->assertTrue($speaker->id > 0); + return $speaker; + } + + public function testPostSpeakerRegCode($summit_id = 23) + { + $params = [ + + 'id' => $summit_id, + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $data = [ + + 'title' => 'Developer!', + 'first_name' => 'Sebastian', + 'last_name' => 'Marcet', + 'email' => 'sebastian.ge7.marcet@gmail.com', + 'registration_code' => 'SPEAKER_00001' + ]; + + $response = $this->action + ( + "POST", + "OAuth2SummitSpeakersApiController@addSpeaker", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $this->assertResponseStatus(201); + $content = $response->getContent(); + $speaker = json_decode($content); + $this->assertTrue($speaker->id > 0); + return $speaker; + } + + public function testPostSpeakerExistent($summit_id = 23) + { + $params = [ + + 'id' => $summit_id, + ]; + + $headers = [ + "HTTP_Authorization" => " Bearer " . $this->access_token, + "CONTENT_TYPE" => "application/json" + ]; + + $data = [ + + 'title' => 'Developer!', + 'first_name' => 'Sebastian', + 'last_name' => 'Marcet', + 'email' => 'sebastian@tipit.net', + ]; + + $response = $this->action + ( + "POST", + "OAuth2SummitSpeakersApiController@addSpeaker", + $params, + [], + [], + [], + $headers, + json_encode($data) + ); + + $this->assertResponseStatus(201); + $content = $response->getContent(); + $speaker = json_decode($content); + $this->assertTrue($speaker->id > 0); + return $speaker; + } +} \ No newline at end of file diff --git a/tests/ProtectedApiTest.php b/tests/ProtectedApiTest.php index 704bcf00..05f3ce13 100644 --- a/tests/ProtectedApiTest.php +++ b/tests/ProtectedApiTest.php @@ -11,12 +11,11 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ - use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Config; use models\oauth2\AccessToken; use App\Models\ResourceServer\IAccessTokenService; - +use App\Security\SummitScopes; /** * Class AccessTokenServiceStub */ @@ -57,6 +56,7 @@ class AccessTokenServiceStub implements IAccessTokenService $url . '/teams/write', $url . '/me/summits/events/favorites/add', $url . '/me/summits/events/favorites/delete', + sprintf(SummitScopes::WriteSpeakersData, $url), ); return AccessToken::createFromParams('123456789', implode(' ', $scopes), '1', $realm, '1','11624', 3600, 'WEB_APPLICATION', '', ''); @@ -101,6 +101,7 @@ class AccessTokenServiceStub2 implements IAccessTokenService $url . '/teams/write', $url . '/me/summits/events/favorites/add', $url . '/me/summits/events/favorites/delete', + sprintf(SummitScopes::WriteSpeakersData, $url), ); return AccessToken::createFromParams('123456789', implode(' ', $scopes), '1', $realm, null,null, 3600, 'SERVICE', '', '');