Ce tutoriel fait partie de la série Construire votre démarrage avec PHP sur Envato Tuts +. Dans cette série, je vous guide dans le lancement d'une startup du concept à la réalité, en utilisant mon application Meeting Planner comme exemple concret. À chaque étape du processus, je publierai le code de Meeting Planner sous forme d’exemples open source à partir desquels vous pourrez apprendre. Je traiterai également les problèmes liés au démarrage au fur et à mesure qu'ils surviennent.
Dans le dernier tutoriel, nous avons commencé à envoyer des invitations à des réunions par e-mail contenant de nombreux liens permettant aux participants de répondre, par exemple, afficher la page de la réunion, accepter tous les lieux et heures, refuser un lieu ou une heure, etc..
Dans ce didacticiel, je vais décrire comment j'ai choisi de construire et de traiter ces liens de manière fonctionnelle et sécurisée. La majorité des participants à la réunion (surtout au début) n'auront pas utilisé Meeting Planner avant, ils nous seront inconnus. Cependant, nous voudrons les authentifier en toute sécurité pour leur permettre de visualiser et d'interagir avec la demande de réunion et de créer la leur pour le futur. Nous souhaitons également disposer de certaines garanties lorsque les personnes transmettent les demandes de réunion avec leurs codes sécurisés sans se soucier des conséquences (néophytes! Peut-être ou simplement des gens ordinaires)..
Pour rappel, tout le code de Meeting Planner est écrit dans le framework Yii2 pour PHP. Si vous souhaitez en savoir plus sur Yii2, consultez mes séries parallèles Programming With Yii2 at Envato Tuts+.
Au moment où vous lisez ceci, vous pouvez probablement commencer à essayer des invitations à des réunions sur le site Web en direct, MeetingPlanner.io (gardez à l’esprit, il reste encore beaucoup de travail d’amélioration de l’expérience utilisateur et de polissage à effectuer). Je participe aux discussions ci-dessous et m'intéresse particulièrement si vous avez des idées supplémentaires ou si vous souhaitez suggérer des sujets pour de futurs tutoriels. Vous pouvez également me joindre sur Twitter @reifman.
Au cours de mon processus de conception, les commandes dans les e-mails ont été considérées comme des éléments à la fois du processus de planification de la réunion et de l'heure de l'événement..
Lorsqu'un participant reçoit une invitation par courrier électronique, des autorisations sécurisées lui sont proposées pour afficher la page de la réunion, mais également pour déterminer si des lieux et des horaires spécifiques lui conviennent ou non..
Une fois la réunion finalisée, nous pouvons commencer à envoyer des rappels aux participants proposant des commandes spécialisées telles que "Je suis en retard", qui pourraient envoyer un message texte aux autres parties, ou "demander un changement de lieu" ou "annuler"..
Toutes ces commandes doivent authentifier le destinataire et leur fournir un accès sécurisé au site Web afin que Meeting Planner puisse traiter correctement leurs réponses. Toutefois, le site doit également éviter de publier la liste complète des contacts des membres qui transmettent par erreur un courrier électronique d'invitation à une réunion à une autre partie, qui clique ensuite sur les liens. La partie secondaire se connecterait facilement au compte Meeting Planner du destinataire et serait en mesure de voir toutes ses réunions et ses informations personnelles..
En réfléchissant davantage à la vision de l'application, il existe un grand nombre de commandes potentielles. En voici quelques unes dans l'invitation initiale (que je pourrais simplifier à un moment donné pour améliorer l'expérience utilisateur):
Remarque: L'apparence des éléments suivis (*) dépend des paramètres de réunion de l'organisateur..
Une fois la réunion planifiée, il existe également diverses commandes de suivi:
Étant donné la multitude de commandes, j’ai pensé qu’il serait utile de les authentifier et de les traiter toutes à l’identique dans un seul contrôleur..
Pour l'instant, j'ai créé un seul point de traitement dans MeetingController, mais je m'attends à créer ultérieurement un CommandController dédié. J'ai également envisagé de créer un contrôleur d'accès API à l'avenir et de canaliser toutes les fonctionnalités de l'application via ce point d'entrée sécurisé unique. Pour l'instant, je vais attendre.
Pour commencer, j'ai donné à chaque commande une définition constante spécifique dans le modèle 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;
J'ai décidé que, pour l'instant, chaque commande aurait les arguments d'URL suivants:
$ id
pour le meeting_Id$ cmd
pour l'action de commande (à partir des constantes ci-dessus)$ obj_id
quel que soit l'objet pouvant faire l'objet d'une action, c'est-à-dire lieu ou date heure$ actor_id
pour l'identifiant utilisateur invoquant la commandek $
pour la clé qui authentifie le $ actor_id sur leur compteAu début, la majorité des participants ne se seront pas inscrits, mais nous créons des clés d'authentification liées à leurs courriels d'invitation lors de la création de la réunion. C'est ainsi que nous authentifions leurs liens à partir d'invitations par courrier électronique..
Voici un exemple de lien URL intégré dans des courriels:
http://meetingplanner.io/meeting/command?id=27&cmd=70&actor_id=18&k=9cHGl… 1x
Compte tenu de la complexité de la création des URL avec divers arguments à plusieurs endroits du code, j'ai créé un /common/components/MiscHelpers.php
bibliothèque, qui a commencé avec buildCommand
:
$ meeting_id, 'cmd' => $ cmd, 'actor_id' => $ actor_id, 'k' => $ auth_key, 'obj_id' => $ obj_id,], true); ?>
Voici un exemple d'appel de fichier d'affichage invitation-html.php buildCommand ()
pour afficher des rangées de lieux. Chaque lieu a des commandes qui doivent fournir tous ces arguments dans les URL:
lieu-> nom; ?>
lieu-> voisinage; ?> id, $ user_id, $ auth_key)); ?>id, $ user_id, $ auth_key)); ?> | id, $ user_id, $ auth_key)); ?> participant_choose_place) ?> | id, $ user_id, $ auth_key)); ?>
Vous pouvez voir à quoi ils ressemblent ci-dessous:
Ensuite, j'ai créé la fonction de contrôleur pour authentifier et traiter les commandes. Voici la première partie:
fonction publique actionCommand ($ id, $ cmd = 0, $ obj_id = 0, $ actor_id = 0, $ k = 0) $ performAuth = true; $ authResult = false; // Gère la session entrante si (! Yii :: $ app-> user-> isGuest) if (Yii :: $ app-> user-> getId ()! = $ Actor_id) // à faire: donner à l'utilisateur un choix de ne pas vous déconnecter Yii :: $ app-> utilisateur-> logout (); else // l'utilisateur actor_id est déjà connecté $ authResult = true; $ performAuth = false;
Au départ, je souhaitais fournir des garanties pour mes propres tests ainsi que pour les personnes transmettant des courriels avec leurs liens d'authentification..
Un événement que je vérifie est si le $ actor_id
est un utilisateur différent de l'utilisateur actuellement connecté. Cela peut se produire lors de tests avec plusieurs comptes ou si le participant envoie son invitation à l'organisateur. En fin de compte, je fournirai des informations sur la situation et offrirai des choix aux personnes. Cependant, pour l'instant, je déconnecte simplement l'utilisateur actuel avant de l'authentifier..
Si l'utilisateur est déjà connecté en tant que $ actor_id
, alors ils sont authentifiés. S'ils ne sont pas authentifiés, nous effectuons le contrôle d'authentification:
if ($ performAuth) // echo 'guest'; $ person = new \ common \ models \ User; $ identity = $ person-> findIdentity ($ actor_id); if ($ identity-> validateAuthKey ($ k)) Yii :: $ app-> user-> login ($ identity); // echo 'authentifié'; $ authResult = true; else // echo 'fail'; $ authResult = false;
Nous utilisons les fonctions intégrées findIdentity et validateAuthKey de Yii pour cette opération..
Dans un proche avenir, je prévois de faire en sorte que l’authentification par courrier électronique fournisse un accès limité aux fonctionnalités du compte. Par exemple, chaque fois que des utilisateurs ne sont pas connectés mais cliquent sur des liens de commande, nous limiterons leurs activités à cette réunion et à quelques fonctionnalités connexes. Ils ne pourront pas voir les autres réunions, les amis du titulaire du compte, etc. Nous leur proposerons cependant un lien convivial leur permettant de se connecter à leur compte via un mot de passe ou un identifiant social. Cela minimisera les impacts sur la sécurité des personnes envoyant des invitations vers.
De même, si un nouvel utilisateur qui ne s'est jamais enregistré auparavant clique sur un lien de commande, nous lui présenterons des rappels lui permettant de s'inscrire et de créer un mot de passe ou un identifiant social. Le modèle User.php comporte des champs d'état indiquant si un utilisateur s'est déjà inscrit ou s'il a été invité de manière passive à une réunion..
Pour l'instant, si l'authentification réussit, nous pouvons simplement traiter chacune des commandes:
if (! $ authResult) $ this-> redirect (['site / authfailure']); else // TO DO vérifier si l'utilisateur est PASSIF // si actif, définissez SESSION pour indiquer la connexion via la commande // si connexion PASSIVE // - si aucun mot de passe, définissez flash pour créer un lien pour créer un mot de passe // - page de réunion - flash pour limiter la sécurité de cette vue de réunion // - meeting index - rediriger pour afficher uniquement cette réunion (faites-le également sur d'autres pages d'index) $ meeting = $ this-> findModel ($ id); switch ($ cmd) case Meeting :: COMMAND_HOME: $ this-> goHome (); Pause; case Meeting :: COMMAND_VIEW: $ this-> redirect (['meeting / view', 'id' => $ id]); Pause; case Meeting :: COMMAND_VIEW_MAP: $ this-> redirect (['meeting / viewplace', 'id' => $ id, 'meeting_place_id' => $ obj_id]); Pause; case Meeting :: COMMAND_FINALIZE: $ this-> redirect (['meeting / finalize', 'id' => $ id]); Pause; case Meeting :: COMMAND_CANCEL: $ this-> redirect (['meeting / cancel', 'id' => $ id]); Pause; case Meeting :: COMMAND_ACCEPT_ALL: MeetingTimeChoice :: setAll ($ id, $ actor_id); MeetingPlaceChoice :: setAll ($ id, $ actor_id); $ this-> redirect (['meeting / view', 'id' => $ id]); Pause; case Meeting :: COMMAND_ACCEPT_ALL_PLACES: MeetingPlaceChoice :: setAll ($ id, $ actor_id); $ this-> redirect (['meeting / view', 'id' => $ id]); Pause; case Meeting :: COMMAND_ACCEPT_ALL_TIMES: MeetingTimeChoice :: setAll ($ id, $ actor_id); $ this-> redirect (['meeting / view', 'id' => $ id]); Pause; case Meeting :: COMMAND_ADD_PLACE: $ this-> redirect (['meeting-place / create', 'id_reunion' => $ id]); Pause; case Meeting :: COMMAND_ADD_TIME: $ this-> redirect (['meeting-time / create', 'id_reunion' => $ id]); Pause; case Meeting :: COMMAND_ADD_NOTE: $ this-> redirect (['meeting-note / create', 'id_reunion' => $ id]); Pause; case Meeting :: COMMAND_ACCEPT_PLACE: $ mpc = MeetingPlaceChoice :: find () -> où (['meeting_place_id' => $ obj_id, 'user_id' => $ actor_id]) -> one (); MeetingPlaceChoice :: set ($ mpc-> id, MeetingPlaceChoice :: STATUS_YES); $ this-> redirect (['meeting / view', 'id' => $ id]); Pause; case Meeting :: COMMAND_REJECT_PLACE: $ mpc = MeetingPlaceChoice :: find () -> où (['meeting_place_id' => $ obj_id, 'user_id' => $ actor_id]) -> one (); MeetingPlaceChoice :: set ($ mpc-> id, MeetingPlaceChoice :: STATUS_NO); $ this-> redirect (['meeting / view', 'id' => $ id]); Pause; case Meeting :: COMMAND_CHOOSE_PLACE: MeetingPlace :: setChoice ($ id, $ obj_id, $ actor_id); $ this-> redirect (['meeting / view', 'id' => $ id]); Pause; 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 (['meeting / view', 'id' => $ id]); Pause; case Meeting :: COMMAND_REJECT_TIME: $ mtc = MeetingTimeChoice :: find () -> où (['meeting_time_id' => $ obj_id, 'user_id' => $ actor_id]) -> one (); MeetingTimeChoice :: set ($ mtc-> id, MeetingTimeChoice :: STATUS_NO); $ this-> redirect (['meeting / view', 'id' => $ id]); Pause; case Meeting :: COMMAND_CHOOSE_TIME: MeetingTime :: setChoice ($ id, $ obj_id, $ actor_id); $ this-> redirect (['meeting / view', 'id' => $ id]); Pause; case Meeting :: COMMAND_FOOTER_EMAIL: case Meeting :: COMMAND_FOOTER_BLOCK: case Meeting :: COMMAND_FOOTER_BLOCK_ALL: $ this-> redirect (['site \ non disponible', 'id_reunion' => $ id]); Pause; défaut: $ this-> redirect (['site \ error', 'meeting_id' => $ id]); Pause;
Pour les fonctionnalités que je n'ai pas encore créées, j'ai créé une vue pour indiquer qu'elle n'est pas disponible, par exemple. /views/site/unavailable.php
, ou si la commande est mal comprise, alors /views/site/error.php
.
Regardons deux exemples de commandes. Tout d'abord, regardons en suggérant un autre endroit:
case Meeting :: COMMAND_ADD_PLACE: $ this-> redirect (['meeting-place / create', 'id_reunion' => $ id]); Pause;
Dans ce cas, la fonctionnalité nécessite que l'utilisateur revienne sur notre site Web pour remplir un formulaire dans lequel il peut sélectionner un nouveau lieu. Donc, nous les redirigeons simplement vers la page de création de lieu de réunion pour cette meeting_id
. Et ils sont déjà authentifiés et connectés d'en haut.
Voici un exemple de cette notification-le menu de navigation est le reflet du contexte de la réunion, par exemple. Réunion de petit déjeuner:
Deuxièmement, voyons comment accepter toutes les dates et heures:
case Meeting :: COMMAND_ACCEPT_ALL_TIMES: MeetingTimeChoice :: setAll ($ id, $ actor_id); $ this-> redirect (['meeting / view', 'id' => $ id]); Pause;
Dans ce cas, nous devons accepter tous les horaires pour cette réunion et $ actor_id
. L'acceptation se fait de manière transparente en coulisse. Après cela, nous pouvons les rediriger pour voir la réunion.
Voici à quoi cela ressemble lorsque vous atteignez la vue de la réunion avec tout ce qui est accepté, par exemple. d'accord, d'accord, d'accord pour les lieux et horaires ci-dessous:
L'implémentation de toutes ces commandes a certainement pris un certain temps, mais les fonctionnalités de Meeting Planner ont vraiment commencé à prendre vie. Et j'ai pu envoyer mes premières invitations dans le monde.
Une femme avec qui je sortais savait que je m'apprêtais à achever cette fonctionnalité et a donc décidé de me motiver pour la terminer plus rapidement. Dit-elle:
"Je ne sais pas du tout quand je vais vous voir car je n'ai pas encore reçu mon invitation de planificateur de réunion."
Après quelques jours de travail supplémentaire, je lui ai envoyé la deuxième invitation de planificateur de réunion. La première est allée à un ami pour qu'elle la teste..
Impressionnant, lorsque ma date a reçu son invitation, elle a rapidement demandé deux fonctionnalités utiles. Tout d'abord, elle a dit qu'elle n'était pas sûre de pouvoir se présenter à notre rendez-vous à moins que l'événement ne soit dans le calendrier Google de son téléphone (en général, je préfère sortir avec des utilisateurs iOS, pas avec Android). Le prochain tutoriel racontera l’histoire de la création d’un fichier iCal (.ics) à importer (pour que ma date sache où aller). Je ne vous garderai pas en suspens-j'ai terminé la fonction à temps pour notre rendez-vous.
Deuxièmement, elle a demandé un film auquel j'avais pensé mais que je n'avais pas compris à quel point c'était important. Elle voulait pouvoir spécifier un lieu avec une heure. En d’autres termes, Restaurant Canlis le vendredi à 19h mais le Paseo le samedi à 20h. Actuellement, les lieux et les horaires sont proposés séparément et non combinés. Je vais sauvegarder cette fonctionnalité pour un futur épisode.
Cela soulève la question générale de savoir comment, au cours du processus de démarrage, collectez-vous régulièrement les commentaires des personnes et intégrez-les dans vos exigences et dans la planification du développement. Tous vos utilisateurs ne vous proposeront pas de dates en échange de leurs fonctionnalités préférées. J'ai prévu un épisode de tutoriel pour expliquer comment faire cela à l'avenir, malgré le manque de motivation secondaire.
Dans le prochain épisode, je détaillerai les fichiers de construction du calendrier (.ics) à importer dans Google Calendar, Outlook et Apple Calendar avec les détails de l'invitation. Inclure les coordonnées et les cartes et gérer les problèmes de fuseau horaire sont des aspects clés de cette.
Surveillez les prochains tutoriels dans la rubrique Création de votre démarrage avec la série PHP. J'espère que vous avez hâte d'essayer Meeting Planner. Essayez maintenant!
N'hésitez pas à ajouter vos questions et commentaires ci-dessous; J'essaie de participer régulièrement aux discussions. Vous pouvez également me joindre sur Twitter @reifman.