Configuration de l'API Gmail pour remplacer l'extension PHP IMAP et fonctionner avec le protocole OAuth2

Une fois l'un des chanceux, il n'est pas préparé au fait qu'à partir du 15 février 2021, l'autorisation de Gmail et d'autres produits ne passera que par l'OAuth, j'ai lu l'article " l'extension Google enterre PHP IMAP " et triste a commencé à agir sur le remplacement de l'extension PHP IMAP votre projet sur l'API Google. Il y avait plus de questions que de réponses, alors j'ai griffonné un manuel en même temps.



J'ai PHP IMAP utilisé pour les tâches suivantes:



  1. Suppression des anciennes lettres des boîtes aux lettres . Malheureusement, dans le panneau de configuration du compte G Suite d'entreprise, vous ne pouvez configurer la période de suppression des messages de toutes les boîtes aux lettres de l'organisation qu'après N jours après réception. Cependant, je dois supprimer les lettres uniquement dans les boîtes aux lettres spécifiées et après un nombre de jours différent spécifié après réception.
  2. Filtrage, analyse et marquage des lettres . Un grand nombre de courriers sont envoyés depuis notre site en mode automatique, dont certains ne parviennent pas aux destinataires, dont, par conséquent, des rapports viennent. Il faut attraper ces rapports, les démonter, trouver un client par email et former une lettre lisible par l'homme pour le responsable, afin qu'il puisse contacter le client et clarifier la pertinence de l'adresse email.


Nous allons résoudre ces deux tâches à l'aide de l'API Gmail dans cet article (et en même temps désactiver l'accès pour les applications non sécurisées dans les paramètres de la boîte aux lettres, ce qui a été activé pour que PHP IMAP fonctionne et, en fait, cessera de fonctionner un jour terrible en février 2021). Nous utiliserons le compte dit de service de l'application Gmail, qui, avec une configuration appropriée, permet de se connecter à toutes les boîtes aux lettres de l'organisation et d'y effectuer toutes les actions.



1. Créez un projet dans la console des développeurs de l'API Google



Avec l'aide de ce projet, nous effectuerons une interaction API avec Gmail et y créerons le même compte de service.



Pour créer un projet:



  1. Accédez à la console des développeurs de l'API Google et connectez-vous en tant qu'administrateur G Suite (enfin, ou qui est votre utilisateur avec tous les droits)
  2. Nous recherchons le bouton "Créer un projet".



    J'ai trouvé ici:
    image



    Et puis ici:
    image



    Renseignez le nom du projet et enregistrez:



    Création de projet
    image



  3. Accédez au projet et cliquez sur le bouton "Activer l'API et les services":



    Activer l'API et les services
    image



    Choisir l'API Gmail



2. Créez et configurez un compte de service



Pour ce faire, vous pouvez utiliser le manuel officiel ou continuer à lire:



  1. Accédez à notre API Gmail ajoutée, cliquez sur le bouton "Créer des informations d'identification" et sélectionnez "Compte de service":



    Création de compte de service
    image



    Remplissez quelque chose et cliquez sur "Créer":



    Détails du compte de service
    image



    Tout le reste peut être laissé vide:



    Droits d'accès pour le compte de service
    image



    image



  2. , . G Suite, « — API».



    API
    image

    image



  3. « »:



    image



    «», « » , « OAuth» — :



    - https://mail.google.com/ -

    - https://www.googleapis.com/auth/gmail.modify -

    - https://www.googleapis.com/auth/gmail.readonly -

    - https://www.googleapis.com/auth/gmail.metadata -




    image

    image



  4. « G Suite»:



    image



    Et remplissez également le nom de votre produit dans le champ ci-dessous.

  5. Vous devez maintenant créer une clé de compte de service: c'est un fichier qui devrait être disponible dans votre application. Lui, en fait, sera utilisé pour l'autorisation.



    Pour ce faire, depuis la page "Identifiants" de votre projet, suivez le lien "Gérer les comptes de service":



    Identifiants
    image



    et sélectionnez "Actions - Créer une clé", tapez: JSON:



    Gestion des comptes de service
    image



    Après cela, un fichier clé sera généré et téléchargé sur votre ordinateur, qui doit être placé dans votre projet et auquel vous aurez accès lorsque vous appelez l'API Gmail.



Cela termine la configuration de l'API Gmail, puis il y aura un peu de mon code cacao, en fait, implémentant les fonctions qui ont été résolues par l'extension PHP IMAP jusqu'à présent.



3. Écriture du code



Il existe une assez bonne documentation officielle ( cliquez et cliquez ) pour l' API Gmail , que j'ai utilisée. Mais depuis que j'ai commencé à écrire un manuel détaillé, je vais joindre mon propre code cacao.



Donc, tout d'abord, nous installons la bibliothèque cliente Google (apiclient) en utilisant composer:



composer require google/apiclient



(Au début, en tant que véritable expert littéraire, j'ai installé la version 2.0 du client api, comme indiqué dans PHP Quickstart , mais au premier démarrage, toutes sortes de vornings et d'alarmes sont tombés sur PHP 7.4 , donc je ne vous conseille pas de faire de même)



Ensuite, sur la base d'exemples tirés de la documentation officielle, nous écrivons notre propre classe pour travailler avec Gmail, sans oublier de spécifier le fichier de clé du compte de service:



Cours pour travailler avec Gmail
<?php
//     Gmail
class GmailAPI
{
    private $credentials_file = __DIR__ . '/../Gmail/credentials.json'; //   

    // ---------------------------------------------------------------------------------------------
    /**
     *   Google_Service_Gmail Authorized Gmail API instance
     *
     * @param  string $strEmail  
     * @return Google_Service_Gmail Authorized Gmail API instance
     * @throws Exception
     */
    function getService(string $strEmail){
        //    
        try{
            $client = new Google_Client();
            $client->setAuthConfig($this->credentials_file);
            $client->setApplicationName('My Super Project');
            $client->setScopes(Google_Service_Gmail::MAIL_GOOGLE_COM);
            $client->setSubject($strEmail);
            $service = new Google_Service_Gmail($client);
        }catch (Exception $e) {
            throw new \Exception('   getService: '.$e->getMessage());
        }
        return $service;
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *    ID    
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  array $arrOptionalParams      
     *         Gmail  after: 2020/08/20 in:inbox label:
     *      q  $opt_param
     * @return array  ID     array('arrErrors' => $arrErrors),   
     * @throws Exception
     */
    function listMessageIDs(Google_Service_Gmail $service, string $strEmail, array $arrOptionalParams = array()) {
        $arrIDs = array(); //  ID 

        $pageToken = NULL; //     
        $messages = array(); //    

        //  
        $opt_param = array();
        //    ,       Gmail      q
        if (count($arrOptionalParams)) $opt_param['q'] = str_replace('=', ':', http_build_query($arrOptionalParams, null, ' '));

        //   ,   ,     
        do {
            try {
                if ($pageToken) {
                    $opt_param['pageToken'] = $pageToken;
                }
                $messagesResponse = $service->users_messages->listUsersMessages($strEmail, $opt_param);
                if ($messagesResponse->getMessages()) {
                    $messages = array_merge($messages, $messagesResponse->getMessages());
                    $pageToken = $messagesResponse->getNextPageToken();
                }
            } catch (Exception $e) {
                throw new \Exception('   listMessageIDs: '.$e->getMessage());
            }
        } while ($pageToken);

        //   ID  
        if (count($messages)) {
            foreach ($messages as $message) {
                $arrIDs[] = $message->getId();
            }
        }
        return $arrIDs;
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *      ID  batchDelete
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  array $arrIDs  ID      listMessageIDs
     * @throws Exception
     */
    function deleteMessages(Google_Service_Gmail $service, string $strEmail, array $arrIDs){
        //      1000 ,      batchDelete
        $arrParts = array_chunk($arrIDs, 999);
        if (count($arrParts)){
            foreach ($arrParts as $arrPartIDs){
                try{
                    //     
                    $objBatchDeleteMessages = new Google_Service_Gmail_BatchDeleteMessagesRequest();
                    //   
                    $objBatchDeleteMessages->setIds($arrPartIDs);
                    //  
                    $service->users_messages->batchDelete($strEmail,$objBatchDeleteMessages);
                }catch (Exception $e) {
                    throw new \Exception('   deleteMessages: '.$e->getMessage());
                }
            }
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *     get
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  string $strMessageID ID 
     * @param  string $strFormat The format to return the message in.
     * Acceptable values are:
     * "full": Returns the full email message data with body content parsed in the payload field; the raw field is not used. (default)
     * "metadata": Returns only email message ID, labels, and email headers.
     * "minimal": Returns only email message ID and labels; does not return the email headers, body, or payload.
     * "raw": Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used.
     * @param  array $arrMetadataHeaders When given and format is METADATA, only include headers specified.
     * @return  object Message
     * @throws Exception
     */
    function getMessage(Google_Service_Gmail $service, string $strEmail, string $strMessageID, string $strFormat = 'full', array $arrMetadataHeaders = array()){
        $arrOptionalParams = array(
            'format' => $strFormat // ,    
        );
        //   - metadata,     
        if (($strFormat == 'metadata') and count($arrMetadataHeaders))
            $arrOptionalParams['metadataHeaders'] = implode(',',$arrMetadataHeaders);

        try{
            $objMessage = $service->users_messages->get($strEmail, $strMessageID,$arrOptionalParams);
            return $objMessage;
        }catch (Exception $e) {
            throw new \Exception('   getMessage: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *   ,    
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @return  object $objLabels -  -  
     * @throws Exception
     */
    function listLabels(Google_Service_Gmail $service, string $strEmail){
        try{
            $objLabels = $service->users_labels->listUsersLabels($strEmail);
            return $objLabels;
        }catch (Exception $e) {
            throw new \Exception('   listLabels: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *     ()  
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  string $strMessageID ID 
     * @param  array $arrAddLabelIds  ID ,     
     * @param  array $arrRemoveLabelIds  ID ,     
     * @return  object Message -  
     * @throws Exception
     */
    function modifyLabels(Google_Service_Gmail $service, string $strEmail, string $strMessageID, array $arrAddLabelIds = array(), array $arrRemoveLabelIds = array()){
        try{
            $objPostBody = new Google_Service_Gmail_ModifyMessageRequest();
            $objPostBody->setAddLabelIds($arrAddLabelIds);
            $objPostBody->setRemoveLabelIds($arrRemoveLabelIds);
            $objMessage = $service->users_messages->modify($strEmail,$strMessageID,$objPostBody);
            return $objMessage;
        }catch (Exception $e) {
            throw new \Exception('   modifyLabels: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

}




Chaque fois que nous interagissons avec Gmail, la première chose que nous faisons est d'appeler la fonction getService ($ strEmail) de la classe GmailAPI, qui renvoie un objet "autorisé" pour travailler avec la boîte aux lettres $ strEmail. De plus, cet objet est déjà passé à toute autre fonction pour effectuer directement les actions dont nous avons besoin. Toutes les autres fonctions de la classe GmailAPI exécutent déjà des tâches spécifiques:



  • listMessageIDs - trouve les messages selon les critères spécifiés et renvoie leur ID (la chaîne de recherche transmise à la fonction API Gmail listUsersMessages doit être similaire à la chaîne de recherche dans l'interface Web de la boîte aux lettres),
  • deleteMessages - supprime les messages avec des ID qui lui ont été transmis (la fonction Gmail de l'API batchDelete ne supprime pas plus de 1000 messages en un seul passage, j'ai donc dû diviser le tableau des ID passés dans la fonction en plusieurs tableaux de 999 lettres chacun et effectuer la suppression plusieurs fois),
  • getMessage - obtient toutes les informations sur le message avec l'ID qui lui est passé,
  • listLabels - renvoie une liste d'indicateurs dans la boîte aux lettres (je l'ai utilisée pour obtenir l'ID de l'indicateur qui a été créé à l'origine dans l'interface Web de la boîte aux lettres et est attribué aux messages souhaités)
  • modifyLabels - ajouter ou supprimer des indicateurs au message


Ensuite, nous avons la tâche de supprimer les anciennes lettres dans diverses boîtes aux lettres. Dans le même temps, nous considérons les anciennes lettres reçues il y a leur nombre de jours pour chaque boîte aux lettres. Pour accomplir cette tâche, nous écrivons le script suivant, qui est exécuté quotidiennement par cron:



Supprimer les anciens e-mails
<?php
/**
 *      Gmail
 *      
 */
require __DIR__ .'/../general/config/config.php'; //   
require __DIR__ .'/../vendor/autoload.php'; //   

//       
$arrMailBoxesForClean = array(
    'a@domain.com' => 30,
    'b@domain.com' => 30,
    'c@domain.com' => 7,
    'd@domain.com' => 7,
    'e@domain.com' => 7,
    'f@domain.com' => 1
);

$arrErrors = array(); //  
$objGmailAPI = new GmailAPI(); //     GMail

//     ,      
foreach ($arrMailBoxesForClean as $strEmail => $intDays) {
    try{
        //    
        $service = $objGmailAPI->getService($strEmail);
        //       
        $arrParams = array('before' => date('Y/m/d', (time() - 60 * 60 * 24 * $intDays)));
        //   ,   
        $arrIDs = $objGmailAPI->listMessageIDs($service,$strEmail,$arrParams);
        //     ID   $arrIDs
        if (count($arrIDs)) $objGmailAPI->deleteMessages($service,$strEmail,$arrIDs);
        //    
        unset($service,$arrIDs);
    }catch (Exception $e) {
        $arrErrors[] = $e->getMessage();
    }
}

if (count($arrErrors)){
    $strTo = 'my_email@domain.com';
    $strSubj = '       ';
    $strMessage = '         :'.
        '<ul><li>'.implode('</li><li>',$arrErrors).'</li></ul>'.
        '<br/>URL: '.filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL);
    $objMailSender = new mailSender();
    $objMailSender->sendMail($strTo,$strSubj,$strMessage);
}




Le script se connecte à chaque boîte aux lettres spécifiée, sélectionne les anciennes lettres et les supprime.



La tâche de génération de rapports pour le gestionnaire sur les courriels non livrés basés sur des rapports automatiques est résolue par le script suivant:



Filtrer et marquer les e-mails
<?php
/*
 *    a@domain.com
 *      ,     : : mailer-daemon@googlemail.com
 *      .        ,   b@domain.com
 *   
 */
require __DIR__ .'/../general/config/config.php'; //   
require __DIR__ .'/../vendor/autoload.php'; //   

$strEmail = 'a@domain.com';
$strLabelID = 'Label_2399611988534712153'; //  reportProcessed -    

//  
$arrParams = array(
    'from' => 'mailer-daemon@googlemail.com', //       
    'in' => 'inbox', //  
    'after' => date('Y/m/d', (time() - 60 * 60 * 24)), //   
    'has' => 'nouserlabels' //  
);

$arrErrors = array(); //  
$objGmailAPI = new GmailAPI(); //     GMail
$arrClientEmails = array(); //    ,      

try{
    //    
    $service = $objGmailAPI->getService($strEmail);
    //         ,    
    $arrIDs = $objGmailAPI->listMessageIDs($service,$strEmail, $arrParams);
    //      'X-Failed-Recipients',    ,      
    if (count($arrIDs)){
        foreach ($arrIDs as $strMessageID){
            //   
            $objMessage = $objGmailAPI->getMessage($service,$strEmail,$strMessageID,'metadata',array('X-Failed-Recipients'));
            //  
            $arrHeaders = $objMessage->getPayload()->getHeaders();
            //  
            foreach ($arrHeaders as $objMessagePartHeader){
                if ($objMessagePartHeader->getName() == 'X-Failed-Recipients'){
                    $strClientEmail = mb_strtolower(trim($objMessagePartHeader->getValue()), 'UTF-8');
                    if (!empty($strClientEmail)) {
                        if (!in_array($strClientEmail, $arrClientEmails)) $arrClientEmails[] = $strClientEmail;
                    }
                    //    reportProcessed,       
                    $objGmailAPI->modifyLabels($service,$strEmail,$strMessageID,array($strLabelID));
                }
            }
        }
    }
    unset($service,$arrIDs,$strMessageID);
}catch (Exception $e) {
    $arrErrors[] = $e->getMessage();
}

//     ,      ,    
if (count($arrClientEmails)) {
    $objClients = new clients();
    //   email  
    $arrAllClientsEmails = $objClients->getAllEmails();

    foreach ($arrClientEmails as $strClientEmail){
        $arrUsages = array();
        foreach ($arrAllClientsEmails as $arrRow){
            if (strpos($arrRow['email'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
            if (strpos($arrRow['email2'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
            if (strpos($arrRow['site_user_settings_contact_email'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
        }
        $intUsagesCnt = count($arrUsages);
        if ($intUsagesCnt > 0){
            $strMessage = '          <span style="color: #000099;">'.$strClientEmail.'</span><br/>
                  ';
            if ($intUsagesCnt == 1){
                $strMessage .= ' '.$arrUsages[0].'<br/>';
            }else{
                $strMessage .= ':<ul>';
                foreach ($arrUsages as $strUsage){
                    $strMessage .= '<li>'.$strUsage.'</li>';
                }
                $strMessage .= '</ul>';
            }
            $strMessage .= '<br/>,        .<br/><br/>
                    ,    ';
            if (empty($objMailSender)) $objMailSender = new mailSender();
            $objMailSender->sendMail('b@domain.com',' email ',$strMessage);
        }
    }
}

if (count($arrErrors)){
    $strTo = 'my_email@domain.com';
    $strSubj = '      ';
    $strMessage = '        :'.
        '<ul><li>'.implode('</li><li>',$arrErrors).'</li></ul>'.
        '<br/>URL: '.filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL);
    if (empty($objMailSender)) $objMailSender = new mailSender();
    $objMailSender->sendMail($strTo,$strSubj,$strMessage);
}




Ce script, tout comme le premier, se connecte à la boîte aux lettres spécifiée, y sélectionne les lettres nécessaires (rapports sur les messages non livrés) sans indicateur, trouve dans la lettre l'adresse e-mail à laquelle la lettre a été tentée d'être envoyée et marque cette lettre avec l'indicateur "Traitée" ... Ensuite, des manipulations sont effectuées avec l'adresse e-mail trouvée, à la suite de laquelle une lettre lisible par l'homme est formée à l'employé responsable.



Les sources sont disponibles sur GitHub .



C'est tout ce que je voulais dire dans cet article. Merci d'avoir lu! Si mon code pique dans vos yeux, roulez simplement le spoiler ou écrivez vos commentaires - je serai ravi des critiques constructives.



All Articles