PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/cli-sender.php

https://github.com/alugo/Goteo
PHP | 268 lines | 227 code | 13 blank | 28 comment | 2 complexity | 3b49f08b6a8b2576e99f60303bfa8c53 MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /*
  3. * Copyright (C) 2012 Platoniq y Fundación Fuentes Abiertas (see README for details)
  4. * This file is part of Goteo.
  5. *
  6. * Goteo is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Goteo is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with Goteo. If not, see <http://www.gnu.org/licenses/agpl.txt>.
  18. *
  19. */
  20. /**
  21. * Este es el proceso que va procesando envios masivos
  22. * version linea de comandos
  23. **/
  24. if (PHP_SAPI !== 'cli') {
  25. die("Acceso solo por linea de comandos!");
  26. }
  27. error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
  28. ini_set("display_errors",1);
  29. define("MAIL_MAX_RATE", 14); // envios por segundo máximos
  30. define("MAIL_MAX_CONCURRENCY", 50); //numero máximo de procesos simultaneos para enviar mail (pero no se llegará a esta cifra si el ratio de envios es mayor que MAIL_MAX_RATE)
  31. define("PHP_CLI", "/usr/bin/php"); //ruta al ejecutable PHP
  32. define("LOCK_FILE", sys_get_temp_dir() . '/' . basename(__FILE__) . '.lock');
  33. // set Lang
  34. define('LANG', 'es');
  35. // Comprueba que no se este ejecutando
  36. exec("ps x | grep " . escapeshellarg(escapeshellcmd(basename(__FILE__))) . " | grep -v grep | awk '{ print $1 }'", $commands);
  37. if (count($commands)>1) {
  38. //echo `ps x`;
  39. die("Ya existe una copia de " . basename(__FILE__) . " en ejecución!\n");
  40. }
  41. //system timezone
  42. date_default_timezone_set("Europe/Madrid");
  43. use Goteo\Core\Resource,
  44. Goteo\Core\Error,
  45. Goteo\Core\Redirection,
  46. Goteo\Core\Model,
  47. Goteo\Library\Feed,
  48. Goteo\Library\Mail,
  49. Goteo\Library\Sender;
  50. require_once 'config.php';
  51. require_once 'core/common.php';
  52. // Limite para sender, (deja margen para envios individuales)
  53. $LIMIT = (defined("GOTEO_MAIL_SENDER_QUOTA") ? GOTEO_MAIL_SENDER_QUOTA : 40000);
  54. // Autoloader
  55. spl_autoload_register(
  56. function ($cls) {
  57. $file = __DIR__ . '/' . implode('/', explode('\\', strtolower(substr($cls, 6)))) . '.php';
  58. $file = realpath($file);
  59. if ($file === false) {
  60. // Try in library
  61. $file = __DIR__ . '/library/' . strtolower($cls) . '.php';
  62. }
  63. if ($file !== false) {
  64. include $file;
  65. }
  66. }
  67. );
  68. $debug = true;
  69. $fail = false;
  70. // check the limit
  71. if (!Mail::checkLimit(null, false, $LIMIT)) {
  72. die("LIMIT REACHED\n");
  73. }
  74. $itime = microtime(true);
  75. $total_users = 0;
  76. // cogemos el siguiente envío a tratar
  77. $mailing = Sender::getSending();
  78. // si no está activa fin
  79. if (!$mailing->active) {
  80. if($debug) echo "INACTIVE\n";
  81. die;
  82. }
  83. if ($mailing->blocked) {
  84. if ($debug) echo "dbg: BLOQUEADO!\n";
  85. $fail = true;
  86. }
  87. // voy a parar aquí, antes del bloqueo
  88. if ($debug) echo "dbg: mailing:\n=====\n".print_r($mailing,1)."\n=====\n";
  89. if (!$fail) {
  90. if ($debug) echo "dbg: bloqueo este registro\n";
  91. Model::query('UPDATE mailer_content SET blocked = 1 WHERE id = ?', array($mailing->id));
  92. // cargamos los destinatarios
  93. $users = Sender::getRecipients($mailing->id, null); //sin limite de usuarios! los queremos todos, el script va por cli sin limite de tiempo
  94. $total_users = count($users);
  95. // si no quedan pendientes, grabamos el feed y desactivamos
  96. if (empty($users)) {
  97. if ($debug) echo "dbg: No hay destinatarios\n";
  98. // Desactivamos
  99. Model::query('UPDATE mailer_content SET active = 0 WHERE id = ?', array($mailing->id));
  100. // evento feed
  101. $log = new Feed();
  102. $log->populate('Envio masivo (cron)', '/admin/mailing/newsletter', 'Se ha completado el envio masivo con asunto "'.$mailing->subject.'"');
  103. $log->doAdmin('system');
  104. unset($log);
  105. if ($debug) echo 'dbg: Se ha completado el envio masivo '.$mailing->id."\n";
  106. } else {
  107. // destinatarios
  108. if ($debug) echo "dbg: Enviamos a $total_users usuarios \n";
  109. //limpiar logs
  110. for($i=0; $i<MAIL_MAX_CONCURRENCY; $i++) {
  111. @unlink(__DIR__ . "/logs/cli-sendmail-$i.log");
  112. }
  113. if ($debug) echo "dbg: Comienza a enviar\n";
  114. $current_rate = 0;
  115. $current_concurrency = $increment = 2;
  116. $i=0;
  117. while($i<$total_users) {
  118. // comprueba la quota para los envios que se van a hacer
  119. if (!Mail::checkLimit(null, false, $LIMIT)) {
  120. if ($debug) echo "dbg: Se ha alcanzado el límite máximo de ".GOTEO_MAIL_SENDER_QUOTA." de envíos diarios! Lo dejamos para mañana\n";
  121. $total_users = $i; //para los calculos posteriores
  122. break;
  123. }
  124. $pids = array();
  125. $stime = microtime(true);
  126. for($j=0; $j<$current_concurrency; $j++) {
  127. if($j + $i >= $total_users) break;
  128. $user = $users[$i + $j];
  129. //envio delegado
  130. $cmd = PHP_CLI;
  131. $config = get_cfg_var('cfg_file_path');
  132. if($config) $cmd .= " -c $config";
  133. $log = __DIR__ . "/logs/cli-sendmail-$j.log";
  134. //para descartar el mensaje:
  135. //$log = "/dev/null";
  136. $cmd .= " -f " . __DIR__ . "/cli-sendmail.php";
  137. $cmd .= " ".escapeshellarg($user->id);
  138. $cmd .= " >> $log 2>&1 & echo $!";
  139. // if($debug) echo "dbg: ejecutando comando:\n$cmd\n";
  140. $pid = trim(shell_exec($cmd));
  141. $pids[$pid] = $user->id;
  142. if($debug) echo "Proceso lanzado con el PID $pid para el envio a {$user->email}\n";
  143. }
  144. //Espera a que acaben los procesos de envio antes de continuar
  145. do {
  146. //espera un segundo
  147. sleep(1);
  148. $check_processes = false;
  149. $processing = array();
  150. foreach($pids as $pid => $user_id) {
  151. if(check_pid($user_id, $pid)) {
  152. $processing[] = $pid;
  153. $check_processes = true; //hay un proceso activo, habra que esperar
  154. }
  155. }
  156. if($processing) {
  157. echo "PIDs ".implode(",", $processing). " en proceso, esperamos...\n";
  158. }
  159. } while($check_processes);
  160. $process_time = microtime(true) - $stime;
  161. $current_rate = round($j / $process_time,2);
  162. //No hace falta incrementar la quota de envio pues ya se hace en Mail::Send()
  163. $rest = Mail::checkLimit(null, true, $LIMIT);
  164. if($debug) echo "Quota de envío restante para hoy: $rest emails, Quota diaria para mailing: ".GOTEO_MAIL_SENDER_QUOTA."\n";
  165. if($debug) echo "Envios por segundo: $current_rate - Ratio máximo: ".MAIL_MAX_RATE."\n";
  166. //aumentamos la concurrencia si el ratio es menor que el 75% de máximo
  167. if($current_rate < MAIL_MAX_RATE*0.75 && $current_concurrency < MAIL_MAX_CONCURRENCY) {
  168. $current_concurrency += 2;
  169. if($debug) echo "Ratio de envio actual menor a un 75% del máximo, aumentamos concurrencia a $current_concurrency\n";
  170. }
  171. //disminuimos la concurrencia si llegamos al 90% del ratio máximo
  172. if($current_rate > MAIL_MAX_RATE*0.9) {
  173. $wait_time = ceil($current_rate - MAIL_MAX_RATE*0.9);
  174. $current_concurrency--;
  175. if($debug) echo "Ratio de envio actual mayor a un 90% del máximo, esperamos $wait_time segundos, disminuimos concurrencia a $current_concurrency\n";
  176. sleep($wait_time);
  177. }
  178. $i += $increment;
  179. $increment = $current_concurrency;
  180. } //end while
  181. }
  182. if ($debug) echo "dbg: desbloqueo este registro\n";
  183. Model::query('UPDATE mailer_content SET blocked = 0 WHERE id = ?', array($mailing->id));
  184. } else {
  185. if ($debug) echo "dbg: FALLO\n";
  186. }
  187. if ($debug) echo "dbg: FIN, tiempo de ejecución total " . round(microtime(true) - $itime, 2) . " segundos para enviar $total_users emails, ratio medio " . round($total_users/(microtime(true) - $itime),2) . " emails/segundo\n";
  188. // limpiamos antiguos procesados
  189. Sender::cleanOld();
  190. /**
  191. * Comprueba si se está ejecutando un proceso cli-sendmail.php con un pid determinado
  192. * @param [type] $pid [description]
  193. * @return [type] [description]
  194. */
  195. function check_pid($args, $pid=null) {
  196. $filter = escapeshellarg(escapeshellcmd(__DIR__ . "/cli-sendmail.php $args"));
  197. $order = "ps x | grep $filter | grep -v grep | awk '{ print $1 }'";
  198. // $order = "pgrep -f $filter";
  199. $lines = shell_exec($order);
  200. if($lines) {
  201. // echo "[$pid] ";print_r($lines);print_r($order);
  202. if($pid) {
  203. $lines = array_map('trim', explode("\n", $lines));
  204. if(in_array($pid, $lines)) {
  205. return true;
  206. }
  207. }
  208. else {
  209. return true;
  210. }
  211. }
  212. }