Bouw je Startup met PHP Email Commands

Wat je gaat creëren

Invoering

Deze tutorial maakt deel uit van de Build Your Startup With PHP-serie op Envato Tuts +. In deze serie begeleid ik je door een opstart van concept naar realiteit te starten met behulp van mijn Meeting Planner-app als een realistisch voorbeeld. Elke stap die ik doe, zal ik de Meeting Planner-code vrijgeven als open-source voorbeelden waar je van kunt leren. Ik zal ook opstartgerelateerde zakelijke problemen aanpakken zodra deze zich voordoen.

Wat doet deze aflevering?

In de laatste zelfstudie zijn we e-mails gestuurd aan uitnodigingen voor vergaderingen die talloze koppelingen bevatten waarmee deelnemers kunnen reageren, dat wil zeggen de vergaderingspagina bekijken, alle plaatsen en tijden accepteren, een plaats of tijd afwijzen, enzovoort.

In deze zelfstudie zal ik bekijken hoe ik ervoor heb gekozen om die koppelingen op een veilige, functionele manier te maken en te verwerken. De meerderheid van de deelnemers aan de vergadering (vooral in het begin) zal eerder geen Meeting Planner hebben gebruikt - ze zullen ons onbekend zijn. Toch willen we ze veilig authenticeren om hen in staat te stellen het vergaderverzoek te bekijken en te gebruiken en om hun eigen vergaderverzoek te creëren voor de toekomst. We willen ook enkele waarborgen hebben voor wanneer mensen de vergaderverzoeken doorsturen met hun beveiligde codes zonder na te denken over de gevolgen (beginnelingen! Misschien, of gewoon gewone mensen)..

Ter herinnering, alle code voor Meeting Planner is geschreven in het Yii2 Framework voor PHP. Als je meer wilt weten over Yii2, bekijk dan mijn parallelle serie Programming With Yii2 bij Envato Tuts+.

Tegen de tijd dat je dit leest, kun je waarschijnlijk beginnen met het uitnodigen van uitnodigingen op de live website, MeetingPlanner.io (hou er rekening mee dat er nog veel werk aan de verbetering van gebruikerservaringen en polijsten te doen is). Ik neem deel aan de onderstaande opmerkingen en ben vooral geïnteresseerd als u aanvullende ideeën heeft of onderwerpen wilt voorstellen voor toekomstige zelfstudies. Je kunt me ook bereiken via Twitter @reifman.

Meeting Planner-opdrachten

Het belang van commando's

Tijdens mijn ontwerpproces dacht ik aan opdrachten in e-mail als elementen van zowel het vergaderplanningsproces als de tijd voorafgaand aan het evenement. 

Wanneer een deelnemer een e-mailuitnodiging ontvangt, moeten deze beveiligde rechten krijgen om de vergaderingspagina te bekijken, maar ook om te reageren op de vraag of specifieke plaatsen en tijden goed voor hen werken.

Nadat een vergadering is afgerond, kunnen we beginnen met het verzenden van herinneringen aan deelnemers die gespecialiseerde opdrachten aanbieden, zoals 'Ik kom te laat', wat de andere partijen in uw hachelijke situatie zou sms'en, of 'een wijziging in de plaats' of 'annuleren' aanvragen. 

Al deze commando's moeten de ontvanger verifiëren en hen beveiligde toegang tot de website bieden zodat Meeting Planner zijn antwoorden correct kan verwerken. De site moet er echter ook voor zorgen dat de volledige lijst met contactpersonen niet wordt vrijgegeven als ze per ongeluk een e-mail met een vergaderingsuitnodiging doorsturen naar een andere partij, die vervolgens op de links klikt. De secundaire partij kan eenvoudig inloggen op het Meeting Planner-account van de ontvanger en al hun vergaderingen en persoonlijke gegevens kunnen bekijken.

Welke opdrachten zijn nodig??

Toen ik nadacht over de visie voor de toepassing, zijn er een groot aantal potentiële opdrachten. Hier zijn enkele in de eerste uitnodiging (die ik op een bepaald moment misschien kan vereenvoudigen om de gebruikerservaring te verbeteren):

  • Bekijk de vergadering
  • Accepteer alle plaatsen en tijden
  • Weigeren de uitnodiging
  • Bepaalde plaatsen accepteren of weigeren
  • Bepaalde datums en tijden accepteren of weigeren
  • Afronding van de vergadering *
  • Stel een andere plaats voor *
  • Stel een andere datum en tijd voor *
  • Kies de laatste plaats *
  • Kies de definitieve datum en tijd *
  • Voeg notities toe of beantwoord deze
  • Bekijk een kaart van de locatie van plaatsen binnen de context van de vergadering
  • Controleer uw e-mailinstellingen
  • Blokkeer deze organisator om u te e-mailen
  • Afmelden voor alle e-mails van Meeting Planner

Notitie: Het uiterlijk van items met een ster (*) is afhankelijk van de vergaderingsinstellingen van de organisator.

Nadat de vergadering is gepland, zijn er ook verschillende vervolgopdrachten:

  • Verander de vergadering opnieuw
  • Annuleer de vergadering
  • Laat me een kaart zien
  • Krijg een routebeschrijving
  • Vraag een wijziging aan in de tijd
  • Vraag een wijziging aan de plaats aan
  • Laat partijen weten dat u te laat bent

Architecturale overwegingen

Gezien de overvloed aan verschillende opdrachten, vond ik het nuttig om ze allemaal identiek te authenticeren en te verwerken in een enkele controller. 

Voor nu maakte ik een enkel verwerkingspunt in MeetingController, maar ik verwacht dat ik later een speciale CommandController zal maken. Ik heb ook overwogen om in de toekomst een API-toegangsbesturingseenheid te maken en alle functionaliteit van de toepassing via dit beveiligde toegangspunt te kanaliseren. Voor nu houd ik dat vol.

Om te beginnen gaf ik elke opdracht een specifieke constante definitie binnen het model Meeting.php:

 const COMMAND_HOME = 5; const COMMAND_VIEW = 10; const COMMAND_VIEW_MAP = 20; const COMMAND_FINALIZE = 50; const COMMAND_CANCEL = 60; const COMMAND_ACCEPT_ALL = 70; const COMMAND_ACCEPT_PLACE = 100; const COMMAND_REJECT_PLACE = 110; const COMMAND_ACCEPT_ALL_PLACES = 120; const COMMAND_CHOOSE_PLACE = 150; const COMMAND_ACCEPT_TIME = 200; const COMMAND_REJECT_TIME = 210; const COMMAND_ACCEPT_ALL_TIMES = 220; const COMMAND_CHOOSE_TIME = 250; const COMMAND_ADD_PLACE = 300; const COMMAND_ADD_TIME = 310; const COMMAND_ADD_NOTE = 320; const COMMAND_FOOTER_EMAIL = 400; const COMMAND_FOOTER_BLOCK = 410; const COMMAND_FOOTER_BLOCK_ALL = 420;

De commandolinks opbouwen

Ik besloot dat, voor nu, elke opdracht de volgende URL-argumenten zal hebben:

  • $ id voor de meeting_Id
  • $ cmd voor de opdrachtactie (van constanten hierboven)
  • $ obj_id voor welk object dan ook zou kunnen worden gehandeld, d.w.z. plaats- of datumtijd
  • $ actor_id voor de user_id die de opdracht oproept
  • $ k voor de sleutel die de $ actor_id op hun account verifieert

De meerderheid van de deelnemers zal zich aanvankelijk niet hebben geregistreerd, maar we creëren wel authenticatiesleutels gekoppeld aan hun uitnodigingsmails wanneer de vergadering wordt gemaakt. Dat is de manier waarop we hun links verifiëren via e-mailuitnodigingen.

Hier is een voorbeeld-URL-koppeling die is ingesloten in e-mails:

http://meetingplanner.io/meeting/command?id=27&cmd=70&actor_id=18&k=9cHGl... 1x

Gezien de complexiteit van het maken van de URL's met verschillende argumenten op veel plaatsen in de code, heb ik een gemaakt /common/components/MiscHelpers.php bibliotheek, die begon met buildCommand:

$ MEETING_ID, 'cmd' => $ cmd 'actor_id' => $ actor_id, 'k' => $ auth_key, 'obj_id' => $ obj_id,], true); ?> 

Hier is een voorbeeld van ons bellen met de view-html.php view file buildCommand () voor het weergeven van rijen plaatsen. Elke plaats heeft opdrachten die al deze argumenten in URL's moeten weergeven:

    

plaats-> name; ?>
plaats-> omgeving; ?> id, $ user_id, $ auth_key)); ?>

id, $ user_id, $ auth_key)); ?> | id, $ user_id, $ auth_key)); ?> participant_choose_place) ?> | id, $ user_id, $ auth_key)); ?>

Je kunt hieronder zien hoe ze eruitzien:

De opdrachten verwerken

Vervolgens heb ik de controllerfunctie gebouwd voor het verifiëren en verwerken van de opdrachten. Dit is het eerste deel:

openbare functie actionCommand ($ id, $ cmd = 0, $ obj_id = 0, $ actor_id = 0, $ k = 0) $ performAuth = true; $ authResult = false; // Beheer de inkomende sessie als (! Yii :: $ app-> user-> isGuest) if (Yii :: $ app-> user-> getId ()! = $ Actor_id) // te doen: geef gebruiker een keuze om niet uit te loggen Yii :: $ app-> user-> logout ();  else // user actor_id is al ingelogd in $ authResult = true; $ performAuth = false; 

Aanvankelijk wilde ik voorzorgsmaatregelen bieden voor mijn eigen testen, evenals mensen die e-mails doorsturen met hun authenticatielinks.

Een gebeurtenis die ik controleer, is of het $ actor_id is een andere gebruiker dan de momenteel ingelogde gebruiker. Dat kan gebeuren tijdens het testen met meerdere accounts, of het kan gebeuren als de deelnemer zijn uitnodiging doorstuurt naar de organisator. Uiteindelijk zal ik informatie geven over de situatie en keuzes bieden voor mensen. Voorlopig log ik echter de huidige gebruiker uit voordat ik de verzoekende gebruiker authenticeer.

Als de gebruiker al is aangemeld als de $ actor_id, dan worden ze geverifieerd. Als ze niet zijn geverifieerd, voeren we de verificatiecontrole uit:

if ($ performAuth) // echo 'guest'; $ person = new \ common \ models \ User; $ identity = $ person-> findIdentity ($ actor_id); if ($ identity-> validateAuthKey ($ k)) Yii :: $ app-> user-> login ($ identity); // echo 'geauthenticeerd'; $ AuthResult = true;  else // echo 'fail'; $ AuthResult = false; 

We gebruiken Yii's ingebouwde findIdentity en validerenAuthKey-functies hiervoor.

In de nabije toekomst, ben ik van plan om authenticatie via e-mail een beperkte toegang tot accountfuncties te bieden. Wanneer gebruikers bijvoorbeeld niet zijn aangemeld maar op opdrachtkoppelingen klikken, beperken we hun activiteiten tot alleen die vergadering en enkele gerelateerde functies. Ze zullen geen andere vergaderingen kunnen zien, vrienden van de accounthouder, enz. We bieden echter een vriendelijke link om in te loggen op hun account via een wachtwoord of sociale login. Dit minimaliseert de beveiligingsimpact van mensen die uitnodigingen rondsturen.

Evenzo, als een nieuwe gebruiker die nog nooit eerder is geregistreerd op een opdrachtkoppeling klikt, zullen we herinneringen voor hen weergeven om zich te registreren en een wachtwoord of sociale aanmelding te maken. Het User.php-model heeft statusvelden die aangeven of een gebruiker zich ooit heeft geregistreerd of dat ze passief zijn uitgenodigd voor een vergadering.

Als de authenticatie nu slaagt, kunnen we voortaan elk van de opdrachten verwerken:

if (! $ authResult) $ this-> redirect (['site / authfailure']);  else // NIET DOEN controleren of gebruiker PASSIEF is // indien actief, stel SESSION in om aan te geven dat hij zich aanmeldt via opdracht // indien PASSIEF inloggen // - als er geen wachtwoord is, setflash om te linken om wachtwoord te maken // - vergaderpagina - knipperen naar beveiligingsbeperking van die meeting view // - meeting index - redirect om alleen die meeting te bekijken (doe dit ook op andere indexpagina's) $ meeting = $ this-> findModel ($ id); switch ($ cmd) case Meeting :: COMMAND_HOME: $ this-> goHome (); breken; case Meeting :: COMMAND_VIEW: $ this-> redirect (['meeting / view', 'id' => $ id]); breken; case Meeting :: COMMAND_VIEW_MAP: $ this-> redirect (['meeting / viewplace', 'id' => $ id, 'meeting_place_id' => $ obj_id]); breken; case Meeting :: COMMAND_FINALIZE: $ this-> redirect (['meeting / finalize', 'id' => $ id]); breken; case Meeting :: COMMAND_CANCEL: $ this-> redirect (['meeting / cancel', 'id' => $ id]); breken; case Meeting :: COMMAND_ACCEPT_ALL: MeetingTimeChoice :: setAll ($ id, $ actor_id); MeetingPlaceChoice :: SETALL ($ id, $ actor_id); $ This-> redirect ([vergadering / view ', 'id'=> $ id]); breken; case Meeting :: COMMAND_ACCEPT_ALL_PLACES: MeetingPlaceChoice :: setAll ($ id, $ actor_id); $ This-> redirect ([vergadering / view ', 'id'=> $ id]); breken; case Meeting :: COMMAND_ACCEPT_ALL_TIMES: MeetingTimeChoice :: setAll ($ id, $ actor_id); $ This-> redirect ([vergadering / view ', 'id'=> $ id]); breken; case Meeting :: COMMAND_ADD_PLACE: $ this-> redirect (['meeting-place / create', 'meeting_id' => $ id]); breken; case Meeting :: COMMAND_ADD_TIME: $ this-> redirect (['meeting-time / create', 'meeting_id' => $ id]); breken; case Meeting :: COMMAND_ADD_NOTE: $ this-> redirect (['meeting-note / create', 'meeting_id' => $ id]); breken; case Meeting :: COMMAND_ACCEPT_PLACE: $ mpc = MeetingPlaceChoice :: find () -> where (['meeting_place_id' => $ obj_id, 'user_id' => $ actor_id]) -> one (); MeetingPlaceChoice :: set ($ MPC> id, MeetingPlaceChoice :: STATUS_YES); $ This-> redirect ([vergadering / view ', 'id'=> $ id]); breken; case Meeting :: COMMAND_REJECT_PLACE: $ mpc = MeetingPlaceChoice :: find () -> where (['meeting_place_id' => $ obj_id, 'user_id' => $ actor_id]) -> one (); MeetingPlaceChoice :: set ($ MPC> id, MeetingPlaceChoice :: STATUS_NO); $ This-> redirect ([vergadering / view ', 'id'=> $ id]); breken; case Meeting :: COMMAND_CHOOSE_PLACE: MeetingPlace :: setChoice ($ id, $ obj_id, $ actor_id); $ This-> redirect ([vergadering / view ', 'id'=> $ id]); breken; case Meeting :: COMMAND_ACCEPT_TIME: $ mtc = MeetingTimeChoice :: find () -> where (['meeting_time_id' => $ obj_id, 'user_id' => $ actor_id]) -> one (); MeetingTimeChoice :: set ($ MTC> id, MeetingTimeChoice :: STATUS_YES); $ This-> redirect ([vergadering / view ', 'id'=> $ id]); breken; case Meeting :: COMMAND_REJECT_TIME: $ mtc = MeetingTimeChoice :: find () -> where (['meeting_time_id' => $ obj_id, 'user_id' => $ actor_id]) -> one (); MeetingTimeChoice :: set ($ MTC> id, MeetingTimeChoice :: STATUS_NO); $ This-> redirect ([vergadering / view ', 'id'=> $ id]); breken; case Meeting :: COMMAND_CHOOSE_TIME: MeetingTime :: setChoice ($ id, $ obj_id, $ actor_id); $ This-> redirect ([vergadering / view ', 'id'=> $ id]); breken; case Meeting :: COMMAND_FOOTER_EMAIL: case Meeting :: COMMAND_FOOTER_BLOCK: case Meeting :: COMMAND_FOOTER_BLOCK_ALL: $ this-> redirect (['site \ unavailable', 'meeting_id' => $ id]); breken; standaard: $ this-> redirect (['site \ error', 'meeting_id' => $ id]); breken; 

Voor functies die ik nog niet heb gebouwd, heb ik een weergave gemaakt om aan te geven dat de functie niet beschikbaar is, bijvoorbeeld. /views/site/unavailable.php, of als het commando verkeerd wordt begrepen, dan /views/site/error.php.

Twee voorbeeldopdrachten

Laten we twee voorbeeldopdrachten bekijken. Laten we eerst eens een andere plaats voorstellen:

case Meeting :: COMMAND_ADD_PLACE: $ this-> redirect (['meeting-place / create', 'meeting_id' => $ id]); breken;

In dit geval vereist de functionaliteit dat de gebruiker naar onze website terugkeert om een ​​formulier in te vullen waarin ze een nieuwe plaats kunnen selecteren. We verwijzen ze daarom gewoon door naar de pagina voor het maken van ontmoetingsplaatsen MEETING_ID. En ze zijn al geverifieerd en ingelogd van bovenaf. 

Hier is een voorbeeld van deze opmerking: het broodkruimelmenu weerspiegelt de context van de vergadering, bijvoorbeeld. Ontbijten:

Ten tweede, laten we alle datums en tijden accepteren:

case Meeting :: COMMAND_ACCEPT_ALL_TIMES: MeetingTimeChoice :: setAll ($ id, $ actor_id); $ This-> redirect ([vergadering / view ', 'id'=> $ id]); breken;

In dit geval moeten we alle tijden accepteren voor die vergadering en $ actor_id. De acceptatie gebeurt transparant achter de schermen. Daarna kunnen we ze omleiden om de vergadering te bekijken.

Hier is hoe het eruit ziet wanneer de vergaderingsweergave wordt bereikt met alles wat wordt geaccepteerd, bijvoorbeeld. okeokeoke voor onderstaande plaatsen en tijden:

Een grappig verhaal

Het implementeren van al deze opdrachten kostte beslist enige tijd, maar de functies van Meeting Planner begonnen echt leven te nemen. En ik was in staat om mijn eerste uitnodigingen de wereld in te sturen.

Een vrouw met wie ik aan het daten was, wist dat ik deze functionaliteit bijna wilde voltooien, dus besloot ze me te motiveren om het sneller af te maken. Ze zei:

"Ik heb geen idee wanneer ik je later zal zien omdat ik mijn uitnodiging voor een vergaderplanner nog niet heb ontvangen." 

Met een paar dagen extra werk stuurde ik haar de tweede uitnodiging voor een vergaderplanner - de eerste ging naar een vriend voor het testen.

Indrukwekkend, toen mijn datum haar uitnodiging ontving, vroeg ze al snel om twee handige functies. Ten eerste zei ze dat ze niet zeker wist of ze zou kunnen verschijnen voor onze date, tenzij het evenement in de Google-agenda van haar telefoon stond (meestal geef ik de voorkeur aan iOS-gebruikers, niet aan Android). De volgende tutorial zal het verhaal vertellen van het bouwen van een iCal (.ics) -bestand om te importeren (dus mijn date zou weten waar te gaan). Ik zal je niet in spanning houden - ik heb de functie op tijd klaargemaakt voor onze date.

Ten tweede vroeg ze om een ​​functie waar ik aan had gedacht, maar die het belang ervan niet besefte. Ze wilde een plaats met een tijd kunnen specificeren. Met andere woorden, Canlis Restaurant op vrijdag om 19.00 uur, maar Paseo op zaterdag om 20.00 uur. Momenteel worden plaatsen en tijden afzonderlijk aangeboden en niet in combinatie. Ik bewaar deze functie voor een toekomstige aflevering. 

Dit werpt de algemene vraag op hoe u tijdens het opstartproces regelmatig feedback van mensen verzamelt en deze in uw vereisten en ontwikkelingsplanning integreert. Niet al uw gebruikers bieden u datums aan in ruil voor hun favoriete functies. Ik heb een tutorial-aflevering gepland om te bespreken hoe dit in de toekomst ook kan, ondanks het gebrek aan secundaire motivatie.

Wat is het volgende?

In de volgende aflevering zal ik gedetailleerde informatie geven over het bouwen van agendabestanden (.ics) voor importeren naar Google Agenda, Outlook en Apple Calendar met de details van de uitnodiging. Het opnemen van contactgegevens en kaarten en het beheren van tijdzonekwesties zijn allemaal belangrijke aspecten hiervan.

Kijk uit voor komende tutorials in mijn Building Your Startup met PHP-serie - ik hoop dat je graag aan het plannen bent om Meeting Planner uit te proberen. Probeer het nu meteen!

Voel je vrij om je vragen en opmerkingen hieronder toe te voegen; Ik probeer regelmatig deel te nemen aan de discussies. Je kunt me ook bereiken via Twitter @reifman.

Gerelateerde Links

  • De website van de ontmoetingsplanner
  • Programmeren met Yii2: Aan de slag
  • De Yii2 Developer Exchange