Proyectos de Subversion LeadersLinked - Services

Rev

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

Rev 613 Rev 614
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;
6
use Symfony\Component\Console\Input\InputInterface;
7
// use Symfony\Component\Console\Input\InputOption;
7
use Symfony\Component\Console\Output\OutputInterface;
8
use Symfony\Component\Console\Output\OutputInterface;
8
use Laminas\Db\Adapter\AdapterInterface;
9
use Laminas\Db\Adapter\AdapterInterface;
9
use Laminas\Log\LoggerInterface;
10
use Laminas\Log\LoggerInterface;
10
use LeadersLinked\Mapper\EmailMapper;
11
use LeadersLinked\Mapper\EmailMapper;
11
use PHPMailer\PHPMailer\PHPMailer;
12
use PHPMailer\PHPMailer\PHPMailer;
12
use LeadersLinked\Model\Email;
13
use LeadersLinked\Model\Email;
13
use Laminas\Mvc\I18n\Translator;
14
use Laminas\Mvc\I18n\Translator;
14
use LeadersLinked\Cache\CacheInterface;
-
 
Línea 15... Línea 15...
15
use Laminas\Db\Adapter\Exception\InvalidQueryException; // Importar para manejar excepciones de BD
15
use LeadersLinked\Cache\CacheInterface;
16
 
16
 
-
 
17
class ProcessQueueEmailCommand extends Command
17
class ProcessQueueEmailCommand extends Command
18
{
18
{
19
 
19
    /**
20
    /**
20
     *
21
     *
21
     * @var \Laminas\Db\Adapter\AdapterInterface
22
     * @var \Laminas\Db\Adapter\AdapterInterface
Línea 48... Línea 49...
48
 
49
 
49
    /**
50
    /**
50
     *
51
     *
51
     * @param \Laminas\Db\Adapter\AdapterInterface $adapter
52
     * @param \Laminas\Db\Adapter\AdapterInterface $adapter
-
 
53
     * @param \LeadersLinked\Cache\CacheInterface $cache
52
     * @param \LeadersLinked\Cache\CacheInterface $cache
54
     * @param
53
     * @param \Laminas\Log\LoggerInterface $logger
55
     *            \Laminas\Log\LoggerInterface
54
     * @param array $config
56
     * @param array $config
55
     * @param \Laminas\Mvc\I18n\Translator $translator
57
     * @param \Laminas\Mvc\I18n\Translator $translator
56
     */
58
     */
57
    public function __construct($adapter, $cache, $logger, $config, $translator)
59
    public function __construct($adapter, $cache, $logger, $config, $translator)
Línea 63... Línea 65...
63
        $this->translator = $translator;
65
        $this->translator = $translator;
Línea 64... Línea 66...
64
 
66
 
65
        parent::__construct();
67
        parent::__construct();
Línea 66... Línea 68...
66
    }
68
    }
67
 
69
 
68
    protected function execute($input, $output)
70
    protected function execute(InputInterface $input, OutputInterface $output): int
69
    {
71
    {
70
        $sandbox = $this->config['leaderslinked.runmode.sandbox'];
72
        $sandbox = $this->config['leaderslinked.runmode.sandbox'];
71
        if ($sandbox) {
73
        if ($sandbox) {
Línea 84... Línea 86...
84
            $port = $this->config['leaderslinked.email.production_port'];
86
            $port = $this->config['leaderslinked.email.production_port'];
85
            $username = $this->config['leaderslinked.email.production_username'];
87
            $username = $this->config['leaderslinked.email.production_username'];
86
            $password = $this->config['leaderslinked.email.production_password'];
88
            $password = $this->config['leaderslinked.email.production_password'];
87
        }
89
        }
Línea -... Línea 90...
-
 
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;
88
 
95
 
89
        $this->logger->info('Inicio del proceso de la cola de Email');
-
 
Línea 90... Línea 96...
90
        $output->writeln('<info>Iniciando el procesamiento de la cola de emails...</info>');
96
        $this->logger->info('Inicio del proceso de la cola de Email');
91
 
-
 
92
        $emailCompleted = 0;
97
 
Línea 93... Línea 98...
93
        $emailError = 0; // Para correos con errores que se descartan
98
        $emailCompleted = 0;
94
        $emailRetried = 0; // Para correos que se reintentan
99
        $emailError = 0;
Línea 95... Línea 100...
95
 
100
 
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
            $output->writeln('<comment>No hay emails en la cola para procesar.</comment>');
101
        $emailMapper = EmailMapper::getInstance($this->adapter);
102
            return Command::SUCCESS;
-
 
103
        }
-
 
104
 
-
 
105
        foreach ($emails as $email) {
102
        $emails = $emailMapper->fetchBatch($batch_size);
Línea 106... Línea -...
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());
-
 
114
                }
103
 
115
                if (!isset($content['to_address'], $content['subject'], $content['message'])) {
104
        if ($emails) {
116
                    throw new \RuntimeException('Contenido del email JSON incompleto: faltan datos esenciales.');
105
            foreach ($emails as $email) {
117
                }
106
                $content = json_decode($email->content, true);
118
 
107
 
119
                $to_address = $content['to_address'];
108
                $to_address = $content['to_address'];
Línea -... Línea 109...
-
 
109
                $to_name = $content['to_name'];
120
                $to_name = $content['to_name'] ?? ''; // Valor por defecto
110
                $cc = $content['cc'];
121
                $cc = $content['cc'] ?? []; // Valor por defecto
111
                $bcc = $content['bcc'];
-
 
112
                $subject = $content['subject'];
-
 
113
                $message = $content['message'];
122
                $bcc = $content['bcc'] ?? []; // Valor por defecto
114
 
-
 
115
                $encoding = mb_detect_encoding($subject);
-
 
116
                if ($encoding != 'UTF-8') {
-
 
117
                    $subject = mb_convert_encoding($subject, 'UTF-8', $encoding);
-
 
118
                }
-
 
119
 
-
 
120
                $encoding = mb_detect_encoding($message);
Línea 123... Línea 121...
123
                $subject = $content['subject'];
121
                if ($encoding != 'UTF-8') {
124
                $message = $content['message'];
122
                    $message = mb_convert_encoding($message, 'UTF-8', $encoding);
125
 
-
 
126
                // Normalización de codificación
123
                }
127
                $subject = $this->normalizeEncoding($subject);
124
 
128
                $message = $this->normalizeEncoding($message);
-
 
129
 
125
                try{
130
                $phpMailer = new PHPMailer(true); // true para habilitar excepciones
126
                $phpMailer = new PHPMailer(true);
-
 
127
 
131
                $phpMailer->isSMTP();
128
                $phpMailer->SMTPDebug = false;
132
                $phpMailer->SMTPDebug = 0; // 0 para no depurar, 2 para mensajes detallados en desarrollo
-
 
133
                $phpMailer->Host = $host;
129
                $phpMailer->isSMTP();
134
                $phpMailer->SMTPAuth = true;
130
                $phpMailer->Host = $host;
135
                $phpMailer->SMTPSecure = 'tls'; // o 'ssl' si el puerto es 465
131
                $phpMailer->SMTPAuth = true;
136
                $phpMailer->Username = $username;
132
                $phpMailer->Username = $username;
-
 
133
                $phpMailer->Password = $password;
137
                $phpMailer->Password = $password;
134
                $phpMailer->SMTPSecure = 'tls';
138
                $phpMailer->Port = $port;
135
                $phpMailer->Port = $port;
-
 
136
              
-
 
137
                $phpMailer->setFrom($from_address, $from_name);
-
 
138
                $phpMailer->addAddress($to_address, $to_name);
-
 
139
                
-
 
140
                if ($cc) {
-
 
141
                    foreach ($cc as $address => $name) {
139
                $phpMailer->CharSet = 'UTF-8';
142
                        $phpMailer->addCC($address, $name);
140
 
-
 
141
                $phpMailer->setFrom($from_address, $from_name);
-
 
142
                $phpMailer->addAddress($to_address, $to_name);
-
 
143
 
143
                    }
144
                foreach ($cc as $address => $name) {
144
                }
145
                    $phpMailer->addCC($address, $name);
145
                if ($bcc) {
146
                }
146
                    foreach ($bcc as $address => $name) {
-
 
147
                        $phpMailer->addBCC($address, $name);
147
                foreach ($bcc as $address => $name) {
148
                    }
Línea 148... Línea 149...
148
                    $phpMailer->addBCC($address, $name);
149
                }
Línea 149... Línea -...
149
                }
-
 
150
 
150
                
151
                $phpMailer->IsHTML(true);
151
                $phpMailer->IsHTML(true);
152
                $phpMailer->Subject = $subject;
-
 
153
                $phpMailer->Body = $message;
152
                $phpMailer->Subject = $subject;
154
                $phpMailer->AltBody = strip_tags($message); // Versión de texto plano
-
 
155
 
-
 
156
                $phpMailer->send();
-
 
157
 
-
 
158
                // Si el envío es exitoso
-
 
159
                $emailCompleted++;
-
 
160
                $email->status = Email::STATUS_COMPLETED;
153
                $phpMailer->Body = $message;
161
                $email->tried = $email->tried + 1; // Aunque exitoso, es bueno registrar el intento
154
                $phpMailer->CharSet = 'UTF-8';
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
155
                $phpMailer->AltBody = $message;
166
                $emailMapper->update($email);
-
 
167
                $this->adapter->getDriver()->getConnection()->commit();
-
 
168
 
156
 
169
            } catch (\Exception $e) {
157
                $phpMailer->send();
170
                $error_message = 'Error de PHPMailer al enviar email ' . $email->id . ': ' . $e->getMessage() . ' Debug: ' . $phpMailer->ErrorInfo;
158
 
171
                $this->logger->error($error_message);
-
 
172
                $output->writeln("  <error>✗ Error al enviar email #{$email->id}: {$e->getMessage()}</error>");
159
                $emailCompleted ++;
173
           
160
                $email->status = Email::STATUS_COMPLETED;
174
                $email->tried = $email->tried + 1;
-
 
175
 
-
 
176
                if ($email->tried >= $this->config['leaderslinked.email.max_retries']) { // Usar una configuración para el número máximo de reintentos
161
                $this->logger->info('Email enviado correctamente: ' . $email->id);
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++;
-
 
183
                    $email->status = Email::STATUS_PENDING; // Mantener como pendiente para reintentar
-
 
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
                }
-
 
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
            }
-
 
Línea 215... Línea 162...
215
             catch (\Exception $e) {
162
                } catch (\Exception $e) {
216
                // Cualquier otra excepción no capturada
163
                    $this->logger->info('Error al enviar el email: ' . $e->getMessage() . ' - ' . $phpMailer->ErrorInfo);     
217
                $error_message = 'Error desconocido al procesar email ' . $email->id . ': ' . $e->getMessage();
-
 
218
                $this->logger->critical($error_message);
164
                    if ($email->tried == 2) {
219
                $output->writeln("  <error>✗ Error inesperado al procesar email #{$email->id}: {$e->getMessage()}</error>");
165
                        $emailError ++;
220
 
-
 
221
                $emailError++;
-
 
222
                $email->status = Email::STATUS_ERROR; // Marcar como error irrecuperable
166
                        $email->status = Email::STATUS_ERROR;
223
                $email->tried = $email->tried + 1;
167
                        $this->logger->info('Email descartado: ' . $email->id);
224
 
168
                    }
Línea 225... Línea 169...
225
                try {
169
                    $email->tried ++;
226
                    $emailMapper->update($email);
-
 
227
                    $this->adapter->getDriver()->getConnection()->commit(); // Confirmar la transacción
170
                }
228
                } catch (\Exception $dbE) {
171
 
Línea 229... Línea -...
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
-
 
249
    }
-
 
250
 
-
 
251
    /**
172
                try {
252
     * Normaliza la codificación de una cadena a UTF-8.
173
                    $emailMapper->update($email);
253
     *
174
                } catch (\Exception $e) {
254
     * @param string $string
175
                    $this->logger->info('Error al actualizar el email: ' . $e->getMessage());