Proyectos de Subversion LeadersLinked - Services

Rev

Rev 612 | Rev 614 | Ir a la última revisión | Mostrar el archivo completo | | | Autoría | Ultima modificación | Ver Log |

Rev 612 Rev 613
Línea 2... Línea 2...
2
declare(strict_types = 1);
2
declare(strict_types = 1);
3
namespace LeadersLinked\Command;
3
namespace LeadersLinked\Command;
Línea 4... Línea 4...
4
 
4
 
5
use Symfony\Component\Console\Command\Command;
5
use Symfony\Component\Console\Command\Command;
6
use Symfony\Component\Console\Input\InputInterface;
-
 
7
// use Symfony\Component\Console\Input\InputOption;
6
use Symfony\Component\Console\Input\InputInterface;
8
use Symfony\Component\Console\Output\OutputInterface;
7
use Symfony\Component\Console\Output\OutputInterface;
9
use Laminas\Db\Adapter\AdapterInterface;
8
use Laminas\Db\Adapter\AdapterInterface;
10
use Laminas\Log\LoggerInterface;
9
use Laminas\Log\LoggerInterface;
11
use LeadersLinked\Mapper\EmailMapper;
10
use LeadersLinked\Mapper\EmailMapper;
12
use PHPMailer\PHPMailer\PHPMailer;
11
use PHPMailer\PHPMailer\PHPMailer;
13
use LeadersLinked\Model\Email;
12
use LeadersLinked\Model\Email;
14
use Laminas\Mvc\I18n\Translator;
13
use Laminas\Mvc\I18n\Translator;
-
 
14
use LeadersLinked\Cache\CacheInterface;
Línea 15... Línea 15...
15
use LeadersLinked\Cache\CacheInterface;
15
use Laminas\Db\Adapter\Exception\InvalidQueryException; // Importar para manejar excepciones de BD
16
 
16
 
17
class ProcessQueueEmailCommand extends Command
-
 
18
{
17
class ProcessQueueEmailCommand extends Command
19
 
18
{
20
    /**
19
    /**
21
     *
20
     *
22
     * @var \Laminas\Db\Adapter\AdapterInterface
21
     * @var \Laminas\Db\Adapter\AdapterInterface
Línea 49... Línea 48...
49
 
48
 
50
    /**
49
    /**
51
     *
50
     *
52
     * @param \Laminas\Db\Adapter\AdapterInterface $adapter
51
     * @param \Laminas\Db\Adapter\AdapterInterface $adapter
53
     * @param \LeadersLinked\Cache\CacheInterface $cache
-
 
54
     * @param
52
     * @param \LeadersLinked\Cache\CacheInterface $cache
55
     *            \Laminas\Log\LoggerInterface
53
     * @param \Laminas\Log\LoggerInterface $logger
56
     * @param array $config
54
     * @param array $config
57
     * @param \Laminas\Mvc\I18n\Translator $translator
55
     * @param \Laminas\Mvc\I18n\Translator $translator
58
     */
56
     */
59
    public function __construct($adapter, $cache, $logger, $config, $translator)
57
    public function __construct($adapter, $cache, $logger, $config, $translator)
Línea 65... Línea 63...
65
        $this->translator = $translator;
63
        $this->translator = $translator;
Línea 66... Línea 64...
66
 
64
 
67
        parent::__construct();
65
        parent::__construct();
Línea 68... Línea 66...
68
    }
66
    }
69
 
67
 
70
    protected function execute(InputInterface $input, OutputInterface $output): int
68
    protected function execute($input, $output)
71
    {
69
    {
72
        $sandbox = $this->config['leaderslinked.runmode.sandbox'];
70
        $sandbox = $this->config['leaderslinked.runmode.sandbox'];
73
        if ($sandbox) {
71
        if ($sandbox) {
Línea 86... Línea 84...
86
            $port = $this->config['leaderslinked.email.production_port'];
84
            $port = $this->config['leaderslinked.email.production_port'];
87
            $username = $this->config['leaderslinked.email.production_username'];
85
            $username = $this->config['leaderslinked.email.production_username'];
88
            $password = $this->config['leaderslinked.email.production_password'];
86
            $password = $this->config['leaderslinked.email.production_password'];
89
        }
87
        }
Línea 90... Línea -...
90
 
-
 
91
        echo 'Username : ' . $username . PHP_EOL;
-
 
92
        echo 'Password : ' . $password . PHP_EOL;
-
 
93
        echo 'Host : ' . $host . PHP_EOL;
-
 
94
        echo 'Port : ' . $port . PHP_EOL;
-
 
95
 
88
 
-
 
89
        $this->logger->info('Inicio del proceso de la cola de Email');
Línea 96... Línea 90...
96
        $this->logger->info('Inicio del proceso de la cola de Email');
90
        $output->writeln('<info>Iniciando el procesamiento de la cola de emails...</info>');
-
 
91
 
97
 
92
        $emailCompleted = 0;
Línea 98... Línea 93...
98
        $emailCompleted = 0;
93
        $emailError = 0; // Para correos con errores que se descartan
99
        $emailError = 0;
94
        $emailRetried = 0; // Para correos que se reintentan
Línea 100... Línea 95...
100
 
95
 
-
 
96
        $emailMapper = EmailMapper::getInstance($this->adapter);
-
 
97
        $emails = $emailMapper->fetchBatch($batch_size);
-
 
98
 
-
 
99
        if (!$emails) {
-
 
100
            $this->logger->info('No hay emails en la cola');
101
        $emailMapper = EmailMapper::getInstance($this->adapter);
101
            $output->writeln('<comment>No hay emails en la cola para procesar.</comment>');
-
 
102
            return Command::SUCCESS;
-
 
103
        }
-
 
104
 
102
        $emails = $emailMapper->fetchBatch($batch_size);
105
        foreach ($emails as $email) {
Línea -... Línea 106...
-
 
106
            // Iniciar transacción para cada email para asegurar atomicidad
-
 
107
            $this->adapter->getDriver()->getConnection()->beginTransaction();
-
 
108
            try {
-
 
109
                $content = json_decode($email->content, true);
-
 
110
 
-
 
111
                // Validación básica de los datos del email
-
 
112
                if (json_last_error() !== JSON_ERROR_NONE) {
-
 
113
                    throw new \RuntimeException('Error al decodificar el contenido JSON del email: ' . json_last_error_msg());
103
 
114
                }
104
        if ($emails) {
115
                if (!isset($content['to_address'], $content['subject'], $content['message'])) {
105
            foreach ($emails as $email) {
116
                    throw new \RuntimeException('Contenido del email JSON incompleto: faltan datos esenciales.');
106
                $content = json_decode($email->content, true);
117
                }
107
 
118
 
108
                $to_address = $content['to_address'];
119
                $to_address = $content['to_address'];
Línea 109... Línea -...
109
                $to_name = $content['to_name'];
-
 
110
                $cc = $content['cc'];
120
                $to_name = $content['to_name'] ?? ''; // Valor por defecto
111
                $bcc = $content['bcc'];
121
                $cc = $content['cc'] ?? []; // Valor por defecto
112
                $subject = $content['subject'];
-
 
113
                $message = $content['message'];
-
 
114
 
122
                $bcc = $content['bcc'] ?? []; // Valor por defecto
115
                $encoding = mb_detect_encoding($subject);
-
 
116
                if ($encoding != 'UTF-8') {
-
 
117
                    $subject = mb_convert_encoding($subject, 'UTF-8', $encoding);
-
 
Línea 118... Línea 123...
118
                }
123
                $subject = $content['subject'];
119
 
124
                $message = $content['message'];
-
 
125
 
-
 
126
                // Normalización de codificación
-
 
127
                $subject = $this->normalizeEncoding($subject);
-
 
128
                $message = $this->normalizeEncoding($message);
-
 
129
 
-
 
130
                $phpMailer = new PHPMailer(true); // true para habilitar excepciones
-
 
131
                $phpMailer->isSMTP();
-
 
132
                $phpMailer->SMTPDebug = 0; // 0 para no depurar, 2 para mensajes detallados en desarrollo
Línea -... Línea 133...
-
 
133
                $phpMailer->Host = $host;
120
                $encoding = mb_detect_encoding($message);
134
                $phpMailer->SMTPAuth = true;
121
                if ($encoding != 'UTF-8') {
-
 
-
 
135
                $phpMailer->SMTPSecure = 'tls'; // o 'ssl' si el puerto es 465
122
                    $message = mb_convert_encoding($message, 'UTF-8', $encoding);
136
                $phpMailer->Username = $username;
123
                }
137
                $phpMailer->Password = $password;
124
 
-
 
125
                $phpMailer = new PHPMailer();
138
                $phpMailer->Port = $port;
126
                $phpMailer->isSMTP();
-
 
127
 
139
                $phpMailer->CharSet = 'UTF-8';
128
                $phpMailer->addAddress($to_address, $to_name);
140
 
129
                if ($cc) {
-
 
130
                    foreach ($cc as $address => $name) {
141
                $phpMailer->setFrom($from_address, $from_name);
Línea 131... Línea -...
131
                        $phpMailer->addCC($address, $name);
-
 
132
                    }
-
 
133
                }
-
 
134
                if ($bcc) {
-
 
135
                    foreach ($bcc as $address => $name) {
142
                $phpMailer->addAddress($to_address, $to_name);
136
                        $phpMailer->addBCC($address, $name);
-
 
137
                    }
-
 
138
                }
-
 
139
 
-
 
140
                $phpMailer->setFrom($from_address, $from_name);
-
 
141
                $phpMailer->SMTPDebug = false;
143
 
142
                $phpMailer->Host = $host;
144
                foreach ($cc as $address => $name) {
143
                $phpMailer->Port = $port;
145
                    $phpMailer->addCC($address, $name);
144
                $phpMailer->IsHTML(true);
-
 
Línea 145... Línea 146...
145
                $phpMailer->SMTPAuth = true;
146
                }
-
 
147
                foreach ($bcc as $address => $name) {
146
                $phpMailer->SMTPSecure = 'tls';
148
                    $phpMailer->addBCC($address, $name);
147
                $phpMailer->SMTPAuth = true;
149
                }
-
 
150
 
148
                $phpMailer->Username = $username;
151
                $phpMailer->IsHTML(true);
149
                $phpMailer->Password = $password;
152
                $phpMailer->Subject = $subject;
150
                $phpMailer->Subject = $subject;
153
                $phpMailer->Body = $message;
Línea -... Línea 154...
-
 
154
                $phpMailer->AltBody = strip_tags($message); // Versión de texto plano
-
 
155
 
-
 
156
                $phpMailer->send();
-
 
157
 
151
                $phpMailer->Body = $message;
158
                // Si el envío es exitoso
-
 
159
                $emailCompleted++;
-
 
160
                $email->status = Email::STATUS_COMPLETED;
-
 
161
                $email->tried = $email->tried + 1; // Aunque exitoso, es bueno registrar el intento
-
 
162
                $this->logger->info('Email enviado correctamente: ' . $email->id);
-
 
163
                $output->writeln("  <info>✔ Email #{$email->id} enviado a {$to_address}</info>");
-
 
164
 
-
 
165
                // Actualizar el estado del email en la BD y confirmar la transacción
152
                $phpMailer->AltBody = $message;
166
                $emailMapper->update($email);
153
                $phpMailer->CharSet = 'UTF-8';
167
                $this->adapter->getDriver()->getConnection()->commit();
154
 
168
 
-
 
169
            } catch (\Exception $e) {
155
                try{
170
                $error_message = 'Error de PHPMailer al enviar email ' . $email->id . ': ' . $e->getMessage() . ' Debug: ' . $phpMailer->ErrorInfo;
156
                    $result = $phpMailer->send();
-
 
157
                } catch (\Exception $e) {
171
                $this->logger->error($error_message);
158
                    $this->logger->info('Error al enviar el email: ' . $e->getMessage());
172
                $output->writeln("  <error>✗ Error al enviar email #{$email->id}: {$e->getMessage()}</error>");
159
                    $result = false;
173
           
-
 
174
                $email->tried = $email->tried + 1;
-
 
175
 
160
                }
176
                if ($email->tried >= $this->config['leaderslinked.email.max_retries']) { // Usar una configuración para el número máximo de reintentos
161
 
177
                    $emailError++;
-
 
178
                    $email->status = Email::STATUS_ERROR;
-
 
179
                    $this->logger->info('Email descartado después de ' . $email->tried . ' intentos fallidos: ' . $email->id);
-
 
180
                    $output->writeln("    <error>Email #{$email->id} descartado (máx. reintentos alcanzado).</error>");
-
 
181
                } else {
-
 
182
                    $emailRetried++;
162
                if ($result) {
183
                    $email->status = Email::STATUS_PENDING; // Mantener como pendiente para reintentar
Línea -... Línea 184...
-
 
184
                    $this->logger->warning('Email ' . $email->id . ' falló. Intentos: ' . $email->tried . '. Reintentando más tarde.');
-
 
185
                }
-
 
186
                // Actualizar el estado del email en la BD y revertir si algo sale mal con la actualización (raro en este punto)
-
 
187
                try {
-
 
188
                    $emailMapper->update($email);
-
 
189
                    $this->adapter->getDriver()->getConnection()->commit(); // Confirmar la transacción
-
 
190
                } catch (\Exception $dbE) {
-
 
191
                    $this->adapter->getDriver()->getConnection()->rollback();
-
 
192
                    $this->logger->critical('Error CRÍTICO al actualizar el estado del email #'.$email->id.' en la BD después de fallo de envío: ' . $dbE->getMessage());
-
 
193
                    $output->writeln("<error>    ERROR CRÍTICO: No se pudo actualizar el estado del email #{$email->id} en la BD.</error>");
-
 
194
                }
163
                    $emailCompleted ++;
195
 
-
 
196
            } catch (\RuntimeException $e) {
-
 
197
                // Errores de lógica de negocio o validación (ej. JSON malformado)
-
 
198
                $error_message = 'Error de lógica/validación para email ' . $email->id . ': ' . $e->getMessage();
-
 
199
                $this->logger->error($error_message);
-
 
200
                $output->writeln("  <error>✗ Error de procesamiento interno para email #{$email->id}: {$e->getMessage()}</error>");
-
 
201
 
-
 
202
                $emailError++; // Generalmente estos errores son irrecuperables por reintentos
-
 
203
                $email->status = Email::STATUS_ERROR;
-
 
204
                $email->tried = $email->tried + 1; // Registrar el intento fallido
-
 
205
 
-
 
206
                try {
-
 
207
                    $emailMapper->update($email);
-
 
208
                    $this->adapter->getDriver()->getConnection()->commit(); // Confirmar la transacción
-
 
209
                } catch (\Exception $dbE) {
-
 
210
                    $this->adapter->getDriver()->getConnection()->rollback();
-
 
211
                    $this->logger->critical('Error CRÍTICO al actualizar el estado del email #'.$email->id.' en la BD después de error de procesamiento: ' . $dbE->getMessage());
-
 
212
                    $output->writeln("<error>    ERROR CRÍTICO: No se pudo actualizar el estado del email #{$email->id} en la BD.</error>");
-
 
213
                }
-
 
214
            }
-
 
215
             catch (\Exception $e) {
-
 
216
                // Cualquier otra excepción no capturada
-
 
217
                $error_message = 'Error desconocido al procesar email ' . $email->id . ': ' . $e->getMessage();
-
 
218
                $this->logger->critical($error_message);
-
 
219
                $output->writeln("  <error>✗ Error inesperado al procesar email #{$email->id}: {$e->getMessage()}</error>");
-
 
220
 
164
                    $email->status = Email::STATUS_COMPLETED;
221
                $emailError++;
165
                    $this->logger->info('Email enviado correctamente: ' . $email->id . ' Resultado: ' . $result);
222
                $email->status = Email::STATUS_ERROR; // Marcar como error irrecuperable
Línea 166... Línea 223...
166
                } else {
223
                $email->tried = $email->tried + 1;
-
 
224
 
167
                    if ($email->tried == 2) {
225
                try {
168
                        $emailError ++;
-
 
169
                        $email->status = Email::STATUS_ERROR;
226
                    $emailMapper->update($email);
Línea -... Línea 227...
-
 
227
                    $this->adapter->getDriver()->getConnection()->commit(); // Confirmar la transacción
-
 
228
                } catch (\Exception $dbE) {
-
 
229
                    $this->adapter->getDriver()->getConnection()->rollback();
-
 
230
                    $this->logger->critical('Error CRÍTICO al actualizar el estado del email #'.$email->id.' en la BD después de error desconocido: ' . $dbE->getMessage());
-
 
231
                    $output->writeln("<error>    ERROR CRÍTICO: No se pudo actualizar el estado del email #{$email->id} en la BD.</error>");
-
 
232
                }
-
 
233
            }
-
 
234
        }
-
 
235
 
-
 
236
        $this->logger->info('Email con Errores descartados: ' . $emailError);
-
 
237
        $this->logger->info('Email pendientes para reintento: ' . $emailRetried);
-
 
238
        $this->logger->info('Email enviados correctamente: ' . $emailCompleted);
-
 
239
        $this->logger->info('Fin del proceso de la cola de Email');
-
 
240
 
-
 
241
        $output->writeln('');
-
 
242
        $output->writeln("<info>Resumen de la ejecución:</info>");
-
 
243
        $output->writeln("  - Correos enviados: <info>{$emailCompleted}</info>");
-
 
244
        $output->writeln("  - Correos con error y descartados: <error>{$emailError}</error>");
-
 
245
        $output->writeln("  - Correos a reintentar: <comment>{$emailRetried}</comment>");
-
 
246
        $output->writeln('<info>Proceso de la cola de emails finalizado.</info>');
-
 
247
 
-
 
248
        return Command::SUCCESS; // Usar constantes de Symfony para el código de salida
170
                        $this->logger->info('Email descartado: ' . $email->id . ' Resultado: ' . $result);
249
    }
171
                    }
250
 
172
                    $email->tried ++;
251
    /**
173
                }
252
     * Normaliza la codificación de una cadena a UTF-8.