Construirea sistemului de pornire cu comenzi PHP E-mail

Ce veți crea

Introducere

Acest tutorial face parte din programul Build Your Startup With PHP pe Envato Tuts +. În această serie, vă conduc prin lansarea unui startup de la concept la realitate, utilizând aplicația mea Planificator de întâlniri ca exemplu de viață reală. Fiecare pas de-a lungul drumului, voi lansa codul Planificatorului de Întâlniri ca exemple de sursă deschisă pe care le puteți învăța. Voi aborda, de asemenea, problemele de afaceri legate de pornire în momentul în care apar.

Ce inseamna acest episod?

În ultimul tutorial, am început să trimitem prin e-mail invitații la întâlniri care au inclus numeroase linkuri pentru ca participanții să răspundă, adică să vizualizeze pagina de întâlnire, să accepte toate locurile și orele, să respingă un loc sau un timp etc..

În acest tutorial, voi revizui modul în care am ales să construiesc și să procesez aceste linkuri într-o manieră sigură și funcțională. Majoritatea participanților la întâlniri (mai ales la început) nu vor mai folosi Planificatorul de Întâlniri înainte de a-i fi necunoscuți. Cu toate acestea, vom dori să le autentificăm în siguranță pentru a le permite să vizualizeze și să interacționeze cu cererea de întâlnire și să își creeze propriile pentru viitor. De asemenea, dorim să avem anumite măsuri de protecție atunci când oamenii înaintează cererile de întâlnire cu codurile lor sigure, fără să se gândească la ramificații (neofizii, probabil, sau doar oamenii obișnuiți).

Doar un memento, tot codul pentru Planificatorul întâlnirilor este scris în cadrul Yii2 Framework for PHP. Dacă doriți să aflați mai multe despre Yii2, verificați seria mea paralelă de programare cu Yii2 la Envato Tuts+.

În timp ce citiți acest lucru, puteți începe probabil să încercați invitații la întâlniri pe site-ul live, MeetingPlanner.io (rețineți că există multe experiențe de îmbunătățire a experienței utilizatorilor și lustruire de făcut). Particip la comentariile de mai jos și sunt interesat în special dacă aveți idei suplimentare sau doriți să sugerați subiecte pentru tutoriale viitoare. Puteți să mă contactați și pe Twitter @reifman.

Comenzile Planificatorului de întâlniri

Importanța comenzilor

În timpul procesului de proiectare, m-am gândit la comenzi în e-mail, ca elemente atât ale procesului de planificare a întâlnirii, cât și ale timpului până la evenimentul real. 

Atunci când un participant primește o invitație prin e-mail, va trebui să primească permisiuni sigure pentru a vedea pagina de întâlnire, dar și pentru a răspunde dacă anumite locuri și ore funcționează bine pentru ei.

După ce o întâlnire este finalizată, este posibil să începem să trimitem mementouri participanților care oferă comenzi specializate, cum ar fi "Mă întorc întârziere", care ar face textul celorlalte părți din situația dvs. dificilă sau "solicitați o schimbare în loc" sau "anulați". 

Toate aceste comenzi trebuie să autentifice destinatarul și să le furnizeze acces securizat pe site, astfel încât Planificatorul de Întâlniri să poată procesa corect răspunsurile. Cu toate acestea, site-ul trebuie să protejeze și să nu elibereze întreaga listă de contacte a unui membru dacă transmite în mod eronat un e-mail de invitație de întâlnire unei alte părți, care apoi face clic pe link-uri. Partea secundară s-ar conecta ușor la contul Planificatorului de întâlniri al destinatarului și va putea să vadă toate întâlnirile și informațiile personale ale acestuia.

Ce comenzi sunt necesare?

Pe măsură ce m-am gândit mai departe la viziunea aplicației, există un număr mare de comenzi potențiale. Iată câteva din invitația inițială (pe care o pot simplifica la un moment dat pentru a îmbunătăți experiența utilizatorului):

  • Vezi întâlnirea
  • Acceptați toate locurile și orele
  • Refuzați invitația
  • Acceptați sau respingeți anumite locuri
  • Acceptați sau respingeți anumite date și ore
  • Finalizați întâlnirea *
  • Sugerați un alt loc *
  • Propuneți o altă dată și o oră *
  • Alegeți locul final *
  • Alegeți data și ora finală *
  • Adăugați sau răspundeți la notele de întâlnire
  • Vizualizați o hartă a locației locurilor în contextul întâlnirii
  • Examinați setările de e-mail
  • Blocați acest organizator de la dvs. prin e-mail
  • Dezabonează-te de la toate e-mailurile Planificatorului de Întâlniri

Notă: Aspectul elementelor marcate cu stea (*) depinde de setările întâlnirii organizatorului.

Odată ce planificarea este planificată, există și o varietate de comenzi de urmărire:

  • Stergeți întâlnirea
  • Anulați întâlnirea
  • Arătați-mi o hartă
  • Obțineți indicații de conducere
  • Solicitați o modificare a timpului
  • Solicitați o modificare a locului
  • Anunță părțile pe care le conduci târziu

Considerații arhitecturale

Având în vedere multitudinea de comenzi diferite, am simțit că ar fi util să le autentificăm și să le procesăm în mod identic într-un singur controler. 

Pentru moment, am creat un singur punct de procesare în MeetingController, dar mă aștept să creez mai târziu un CommandController dedicat. De asemenea, am luat în considerare crearea unui controler de acces API în viitor și canalizarea tuturor funcționalităților aplicației prin intermediul acestui singur punct de intrare securizat. Deocamdată, o să țin cont de asta.

Pentru a începe, am dat fiecare comandă o definiție constantă specifică în modelul 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;

Construirea legăturilor de comandă

Am decis ca, pentru moment, fiecare comanda va avea urmatoarele argumente URL:

  • $ id pentru întâlnirea_de
  • $ cmd pentru acțiunea de comandă (de la constantele de mai sus)
  • $ obj_id pentru orice obiect ar putea fi acționat, adică locul sau data
  • $ actor_id pentru user_id care invocă comanda
  • $ k pentru cheia care autentifică $ actor_id în contul lor

Majoritatea participanților la început nu s-au înregistrat, dar facem cheile de autentificare legate de e-mailurile de invitație atunci când este creată întâlnirea. Deci, astfel vom autentifica legăturile lor prin invitații prin e-mail.

Iată un exemplu de link URL încorporat în e-mailuri:

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

Având în vedere complexitatea creării adreselor URL cu diverse argumente din multe locuri din cod, am creat un /common/components/MiscHelpers.php bibliotecă, care a început cu buildCommand:

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

Iată un exemplu de invitație-html.php pentru vizualizarea fișierului de vizualizare buildCommand () pentru afișarea rândurilor de locuri. Fiecare loc are comenzi care trebuie să furnizeze toate aceste argumente în adrese URL:

    

ń> nume; ?>
ń> vecinătate; ?> id, $ USER_ID, $ auth_key)); ?>

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

Puteți vedea cum arată mai jos:

Procesarea comenzilor

Apoi, am construit funcția de controler pentru autentificarea și procesarea comenzilor. Iată prima parte:

funcția publică funcțiaCommand ($ id, $ cmd = 0, $ obj_id = 0, $ actor_id = 0, $ k = 0) $ performAuth = true; $ authResult = false; // Gestionați sesiunea de intrare dacă (! Yii :: $ app-> user-> isGuest) if (Yii :: $ app-> user-> getId ()! = $ Actor_id) // de făcut: o alegere de a nu vă deconectați Yii :: $ app-> user-> logout ();  altfel // user actor_id este deja conectat la $ authResult = true; $ performAuth = false; 

Inițial, am vrut să pun la dispoziție măsuri de siguranță pentru testarea mea, precum și pentru trimiterea de e-mailuri cu legăturile de autentificare.

Un eveniment pe care îl verific este dacă $ actor_id este un utilizator diferit decât cel înregistrat în acest moment. Acest lucru s-ar putea întâmpla în timpul testelor cu mai multe conturi sau s-ar putea întâmpla dacă participantul le transmite invitația organizatorului. În cele din urmă, vă voi oferi informații despre situație și voi oferi alegeri oamenilor. Cu toate acestea, deocamdată, trebuie doar să înregistrez utilizatorul curent înainte de a autentifica utilizatorul solicitant.

Dacă utilizatorul este deja conectat ca $ actor_id, apoi sunt autentificate. Dacă nu sunt autentificate, executați verificarea autentificării:

dacă ($ performAuth) // echo 'guest'; $ person = noi \ comune \ modele \ Utilizator; $ identitate = $ persoană-> identificare ($ actor_id); dacă ($ identitate-> validateAuthKey ($ k)) Yii :: $ app-> user-> login (identitate $); // echo 'authenticated'; $ AuthResult = true;  altfel // echo 'fail'; $ AuthResult = false; 

Utilizăm funcțiile YII built-in findIdentity și validateAuthKey pentru aceasta.

În viitorul apropiat, intenționez să fac autentificarea de la e-mail, oferind un acces limitat la caracteristicile contului. De exemplu, ori de câte ori utilizatorii nu sunt conectați, ci faceți clic pe linkurile de comandă, atunci le vom restrânge activitățile doar la acea întâlnire și câteva caracteristici conexe. Nu vor putea să vadă alte întâlniri, prietenii titularului contului etc. Cu toate acestea, vom oferi un link prietenos pentru ca aceștia să se conecteze la contul lor prin parolă sau conectare socială. Acest lucru va reduce la minimum efectele de securitate ale persoanelor care transmit invitații în jurul lor.

În mod similar, dacă un utilizator nou care nu sa înregistrat niciodată înainte de a face clic pe un link de comandă, vom afișa mementouri pentru ca aceștia să se înregistreze și să creeze o parolă sau un login social. Modelul User.php conține câmpuri de stare care indică dacă un utilizator sa înregistrat vreodată sau dacă a fost invitat pasiv la o întâlnire.

Pentru moment, dacă autentificarea reușește, putem procesa fiecare dintre comenzi:

dacă (! $ authResult) $ this-> redirect (['site / authfailure']);  altceva // DACĂ verificați dacă utilizatorul este PASIVE // dacă este activ, setați SESSION pentru a indica autentificarea prin comanda // dacă vă conectați PASSIVE // - dacă nu există nici o parolă, setflash pentru a crea link-ul pentru a crea parola // - pagina de întâlnire - flash la limitarea securității acelei întâlniri // // - indexul întâlnirilor - redirecționarea pentru a vedea doar acea întâlnire (face și pe alte pagini index) $ meeting = $ this-> findModel ($ id); switch ($ cmd) întâlnire case :: COMMAND_HOME: $ this-> goHome (); pauză; caz Întâlnire :: COMMAND_VIEW: $ this-> redirect (['întâlnire / vizualizare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_VIEW_MAP: $ this-> redirect (['întâlnire / vizualizare', 'id' => $ id, 'meeting_place_id' => $ obj_id]); pauză; caz Întâlnire :: COMMAND_FINALIZE: $ this-> redirect (['întâlnire / finalizare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_CANCEL: $ this-> redirect (['întâlnire / anulare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_ACCEPT_ALL: MeetingTimeChoice :: setAll ($ id, $ actor_id); MeetingPlaceChoice :: setAll ($ id, $ actor_id); $ This-> redirecționeze ([ 'întâlnire / vizualizare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_ACCEPT_ALL_PLACES: MeetingPlaceChoice :: setAll ($ id, $ actor_id); $ This-> redirecționeze ([ 'întâlnire / vizualizare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_ACCEPT_ALL_TIMES: MeetingTimeChoice :: setAll ($ id, $ actor_id); $ This-> redirecționeze ([ 'întâlnire / vizualizare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_ADD_PLACE: $ this-> redirect (['meeting-place / create', 'meeting_id' => $ id]); pauză; caz Întâlnire :: COMMAND_ADD_TIME: $ this-> redirect (['meeting-time / create', 'meeting_id' => $ id]); pauză; caz Întâlnire :: COMMAND_ADD_NOTE: $ this-> redirect (['meeting-note / create', 'meeting_id' => $ id]); pauză; caz Întâlnire :: COMMAND_ACCEPT_PLACE: $ mpc = MeetingPlaceChoice :: find () -> unde (['meeting_place_id' => $ obj_id, 'user_id' => $ actor_id]) -> one; MeetingPlaceChoice :: set ($ mpc-> id, MeetingPlaceChoice :: STATUS_YES); $ This-> redirecționeze ([ 'întâlnire / vizualizare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_REJECT_PLACE: $ mpc = MeetingPlaceChoice :: find () -> unde (['meeting_place_id' => $ obj_id, 'user_id' => $ actor_id]) -> unul (); MeetingPlaceChoice :: set ($ mpc-> id, MeetingPlaceChoice :: STATUS_NO); $ This-> redirecționeze ([ 'întâlnire / vizualizare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_CHOOSE_PLACE: MeetingPlace :: setChoice ($ id, $ obj_id, $ actor_id); $ This-> redirecționeze ([ 'întâlnire / vizualizare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_ACCEPT_TIME: $ mtc = MeetingTimeChoice :: find () -> unde (['meeting_time_id' => $ obj_id, 'user_id' => $ actor_id]) -> un (); MeetingTimeChoice :: set ($ mtc-> id, MeetingTimeChoice :: STATUS_YES); $ This-> redirecționeze ([ 'întâlnire / vizualizare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_REJECT_TIME: $ mtc = MeetingTimeChoice :: find () -> unde (['meeting_time_id' => $ obj_id, 'user_id' => $ actor_id]) -> unul (); MeetingTimeChoice :: set ($ mtc-> id, MeetingTimeChoice :: STATUS_NO); $ This-> redirecționeze ([ 'întâlnire / vizualizare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_CHOOSE_TIME: MeetingTime :: setChoice ($ id, $ obj_id, $ actor_id); $ This-> redirecționeze ([ 'întâlnire / vizualizare', 'id' => $ id]); pauză; caz Întâlnire :: COMMAND_FOOTER_EMAIL: caz Întâlnire :: COMMAND_FOOTER_BLOCK: întâlnire caz :: COMMAND_FOOTER_BLOCK_ALL: $ this-> redirect (['site \ indisponibil', 'meeting_id' => $ id]); pauză; implicit: $ this-> redirect (['site \ error', 'meeting_id' => $ id]); pauză; 

Pentru funcțiile pe care nu le-am construit încă, am creat o vedere pentru a indica faptul că această caracteristică nu este disponibilă, de ex. /views/site/unavailable.php, sau dacă comanda este înțeles greșit, atunci /views/site/error.php.

Două comenzi de probă

Să examinăm două comenzi de exemplu. În primul rând, să ne uităm la sugerarea altui loc:

caz Întâlnire :: COMMAND_ADD_PLACE: $ this-> redirect (['meeting-place / create', 'meeting_id' => $ id]); pauză;

În acest caz, funcționalitatea cere ca utilizatorul să revină pe site-ul nostru pentru a completa un formular în care să poată selecta un nou loc. Deci, le redirecționăm spre pagina de creație a locului de întâlnire MEETING_ID. Au fost deja autentificate și logate de sus. 

Iată un exemplu de acest lucru - observați că meniul de pescuit reflectă contextul întâlnirii, de ex. Mic dejun:

În al doilea rând, să ne uităm la acceptarea tuturor datelor și timpurilor:

caz Întâlnire :: COMMAND_ACCEPT_ALL_TIMES: MeetingTimeChoice :: setAll ($ id, $ actor_id); $ This-> redirecționeze ([ 'întâlnire / vizualizare', 'id' => $ id]); pauză;

În acest caz, trebuie să acceptăm toate momentele pentru această întâlnire și $ actor_id. Acceptarea se face transparent în spatele scenei. După aceea, le putem redirecționa pentru a vedea întâlnirea.

Iată cum arată atunci când ajungeți la vizualizarea întâlnirii cu tot ce este acceptat, de ex. binebinebine pentru locurile și orele de mai jos:

O poveste amuzanta

Implementarea tuturor acestor comenzi a durat cu siguranță, dar funcțiile Planificatorului de întâlniri au început să ia viața. Și am putut trimite primele mele invitații în lume.

O femeie pe care o întâlneam știa că mă apropii de terminarea acestei funcționalități, așa că ea a decis să mă motiveze să termin mai repede. Ea a spus:

"Nu am nici o idee când vă voi mai vedea pentru că nu am primit încă invitația mea de Planificator de întâlniri". 

Cu câteva zile suplimentare de muncă, i-am trimis cea de-a doua invitație de planificare a întâlnirii - prima a mers la un prieten pentru testare.

Impresionant, când data mea a primit invitația, ea a cerut rapid două caracteristici utile. În primul rând, a spus că nu era sigură că va putea să apară pentru data noastră, cu excepția cazului în care evenimentul se găsește în Google Calendarul telefonului său (în general, prefer să fiu utilizatorilor iOS, nu Android). Următorul tutorial va spune povestea construirii unui fișier iCal (.ics) pentru import (așa că data mea ar ști unde să meargă). Nu te voi menține în suspans - am terminat filmul în timp pentru întâlnirea noastră.

În al doilea rând, ea a cerut o trăsătură pe care m-am gândit, dar nu i-am dat seama de importanța ei. Vroia să poată specifica un loc cu un timp. Cu alte cuvinte, restaurantul Canlis vine vineri la ora 19:00, dar Paseo sâmbătă la ora 20:00. În prezent, locurile și orele sunt oferite separat și nu în combinație. Voi salva această funcție pentru un viitor episod. 

Aceasta ridică problema generală a modului în care în timpul procesului de pornire colectați în mod regulat feedback de la oameni și integrați-l în cerințele și planificarea dezvoltării. Nu toți utilizatorii dvs. vă vor oferi date în schimbul caracteristicilor lor preferate. Am un episod de tutorial planificat să discut despre cum să faci asta și în viitor, în ciuda lipsei motivației secundare.

Ce urmeaza?

În următorul episod, voi detalia crearea fișierelor Calendar (.ics) pentru a fi importate în Google Calendar, Outlook și Apple Calendar, cu detaliile invitației. Includerea detaliilor de contact și a hărților și gestionarea problemelor legate de fusul orar sunt toate aspectele cheie ale acestui lucru.

Uita-te pentru tutoriale viitoare în clădirea dvs. de pornire cu seria PHP-sper că sunteți dornici de a încerca planificator de întâlnire. Încearcă-o chiar acum!

Nu ezitați să adăugați întrebările și comentariile de mai jos; Încerc să particip la discuții în mod regulat. Puteți să mă contactați și pe Twitter @reifman.

Link-uri conexe

  • Site-ul Planificatorului de întâlniri
  • Programarea cu Yii2: Noțiuni de bază
  • Schimbul de dezvoltatori Yii2
Cod