| 1 | #! /usr/bin/env php |
|---|
| 2 | <?php |
|---|
| 3 | |
|---|
| 4 | require_once(dirname($argv[0]) . "/utilities.php"); |
|---|
| 5 | require_once(IA_ROOT_DIR . 'common/external_libs/class.phpmailer.php'); |
|---|
| 6 | require_once(IA_ROOT_DIR."common/db/user.php"); |
|---|
| 7 | require_once(IA_ROOT_DIR."common/db/textblock.php"); |
|---|
| 8 | require_once(IA_ROOT_DIR."common/user.php"); |
|---|
| 9 | require_once(IA_ROOT_DIR."common/rating.php"); |
|---|
| 10 | require_once(IA_ROOT_DIR."common/email.php"); |
|---|
| 11 | require_once(IA_ROOT_DIR."common/newsletter.php"); |
|---|
| 12 | require_once(IA_ROOT_DIR."www/url.php"); |
|---|
| 13 | db_connect(); |
|---|
| 14 | |
|---|
| 15 | // Configuration section. |
|---|
| 16 | |
|---|
| 17 | define("IA_NEWSLETTER_FROM", "newsletter@infoarena.ro"); |
|---|
| 18 | define("IA_NEWSLETTER_FROM_NAME", "Newsletter Infoarena"); |
|---|
| 19 | // how many emails to send at once (with no delay) |
|---|
| 20 | define("IA_BURST_LENGTH", 25); |
|---|
| 21 | // pause (milliseconds i.e. 10^-3) between bursts. |
|---|
| 22 | define("IA_BURST_DELAY", 500); |
|---|
| 23 | // place where we store newsletter logs |
|---|
| 24 | define("IA_NEWSLETTER_LOGDIR", IA_ROOT_DIR."scripts/newsletter-logs/"); |
|---|
| 25 | // application to use for previewing the HTML newsletter |
|---|
| 26 | define("IA_NEWSLETTER_PREVIEW_APP", "elinks"); |
|---|
| 27 | |
|---|
| 28 | // Create PHPMailer object from newsletter textblock, and for given recipient. |
|---|
| 29 | function phpmail_create($textblock, $recipient) { |
|---|
| 30 | log_assert_valid(user_validate($recipient)); |
|---|
| 31 | log_assert_valid(textblock_validate($textblock)); |
|---|
| 32 | $phpmail = new PHPMailer(); |
|---|
| 33 | phpmail_config($phpmail); |
|---|
| 34 | $phpmail->Subject = newsletter_subject($textblock, $recipient); |
|---|
| 35 | $phpmail->AltBody = newsletter_body_alternate($textblock, $recipient); |
|---|
| 36 | $phpmail->Body = newsletter_body_html($textblock, $recipient); |
|---|
| 37 | $phpmail->AddAddress($recipient['email'], $recipient['full_name']); |
|---|
| 38 | return $phpmail; |
|---|
| 39 | } |
|---|
| 40 | |
|---|
| 41 | // Configure a PHPMailer object. |
|---|
| 42 | function phpmail_config(&$phpmail) { |
|---|
| 43 | if (IA_SMTP_ENABLED) { |
|---|
| 44 | $phpmail->IsSMTP(); |
|---|
| 45 | $phpmail->Host = IA_SMTP_HOST; |
|---|
| 46 | $phpmail->Port = IA_SMTP_PORT; |
|---|
| 47 | } else { |
|---|
| 48 | // Use PHP mail(...) function. |
|---|
| 49 | $phpmail->IsMail(); |
|---|
| 50 | } |
|---|
| 51 | $phpmail->From = IA_NEWSLETTER_FROM; |
|---|
| 52 | $phpmail->FromName = IA_NEWSLETTER_FROM_NAME; |
|---|
| 53 | $phpmail->CharSet = "UTF-8"; |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | // return array with valid subscriber list identifiers |
|---|
| 57 | function list_valid_ids() { |
|---|
| 58 | $valid = array( |
|---|
| 59 | 'all', 'admins', 'rated', 'test', 'review' |
|---|
| 60 | ); |
|---|
| 61 | return $valid; |
|---|
| 62 | } |
|---|
| 63 | |
|---|
| 64 | function list_get($list_id) { |
|---|
| 65 | switch ($list_id) { |
|---|
| 66 | case 'all': |
|---|
| 67 | // all subscribers that agree to receive newsletters |
|---|
| 68 | $query = "SELECT * FROM ia_user WHERE newsletter = 1"; |
|---|
| 69 | return db_fetch_all($query); |
|---|
| 70 | |
|---|
| 71 | case 'admins': |
|---|
| 72 | // all administrators |
|---|
| 73 | $query = "SELECT * FROM ia_user |
|---|
| 74 | WHERE 'admin' = security_level AND newsletter = 1 |
|---|
| 75 | ORDER BY full_name"; |
|---|
| 76 | return db_fetch_all($query); |
|---|
| 77 | |
|---|
| 78 | case 'rated': |
|---|
| 79 | // all rated users that agree to receive newsletters |
|---|
| 80 | $query = "SELECT * FROM ia_user |
|---|
| 81 | WHERE 0 < rating_cache AND newsletter = 1 |
|---|
| 82 | ORDER BY rating_cache DESC"; |
|---|
| 83 | return db_fetch_all($query); |
|---|
| 84 | |
|---|
| 85 | case 'review': |
|---|
| 86 | // a small set of users. meant for reviewing |
|---|
| 87 | $usernames = array('wickedman', 'domino', 'silviug', 'Cosmin'); |
|---|
| 88 | $query = "SELECT * FROM ia_user |
|---|
| 89 | WHERE username IN ('".join("', '", $usernames)."') |
|---|
| 90 | ORDER BY full_name"; |
|---|
| 91 | return db_fetch_all($query); |
|---|
| 92 | |
|---|
| 93 | case 'test': |
|---|
| 94 | // test the newsletter in various email clients |
|---|
| 95 | $usernames = array('gmail_test', 'yahoo_test', 'hotmail_test', |
|---|
| 96 | 'k_test'); |
|---|
| 97 | $query = "SELECT * FROM ia_user |
|---|
| 98 | WHERE username IN ('".join("', '", $usernames)."') |
|---|
| 99 | ORDER BY full_name"; |
|---|
| 100 | return db_fetch_all($query); |
|---|
| 101 | |
|---|
| 102 | default: |
|---|
| 103 | log_error("Invalid list id!"); |
|---|
| 104 | } |
|---|
| 105 | } |
|---|
| 106 | |
|---|
| 107 | // Preview email for a given recipient |
|---|
| 108 | // When recipient is null, no tags are replaced |
|---|
| 109 | function preview_email($textblock, $recipient, $use_external_app = true) { |
|---|
| 110 | log_assert_valid(user_validate($recipient)); |
|---|
| 111 | log_assert_valid(textblock_validate($textblock)); |
|---|
| 112 | $phpmail = phpmail_create($textblock, $recipient); |
|---|
| 113 | if (function_exists('sys_get_temp_dir')) { |
|---|
| 114 | $temp_dir = sys_get_temp_dir(); |
|---|
| 115 | } else { |
|---|
| 116 | $temp_dir = '/tmp'; |
|---|
| 117 | } |
|---|
| 118 | $temp_filename = tempnam($temp_dir, 'ia-newsletter-preview-'); |
|---|
| 119 | file_put_contents($temp_filename, $phpmail->Body); |
|---|
| 120 | |
|---|
| 121 | echo "\n\n=== e-mail ========================================\n"; |
|---|
| 122 | echo "From : {$phpmail->From}\n"; |
|---|
| 123 | if ($recipient) { |
|---|
| 124 | echo "To : ".$recipient['email']."\n"; |
|---|
| 125 | } |
|---|
| 126 | echo "Subject: {$phpmail->Subject}\n"; |
|---|
| 127 | echo "--- alternate text --------------------------------\n"; |
|---|
| 128 | echo wordwrap($phpmail->AltBody, IA_EMAIL_WORDRAP)."\n"; |
|---|
| 129 | echo "--- /alternate text --------------------------------\n"; |
|---|
| 130 | echo "=== /e-mail ========================================\n\n"; |
|---|
| 131 | |
|---|
| 132 | // warn users if word-wrapping is needed |
|---|
| 133 | |
|---|
| 134 | if (wordwrap($phpmail->AltBody, IA_EMAIL_WORDRAP) != $phpmail->AltBody) { |
|---|
| 135 | log_print("WARNING: Please word-wrap the text to ".IA_EMAIL_WORDRAP |
|---|
| 136 | ." characters per line!\n"); |
|---|
| 137 | } |
|---|
| 138 | |
|---|
| 139 | // display HTML body in preview app |
|---|
| 140 | |
|---|
| 141 | if ($use_external_app) { |
|---|
| 142 | system(IA_NEWSLETTER_PREVIEW_APP." ".$temp_filename); |
|---|
| 143 | unlink($temp_filename); |
|---|
| 144 | } |
|---|
| 145 | } |
|---|
| 146 | |
|---|
| 147 | // Format recipient to display it nicely on screen / log |
|---|
| 148 | function recipient_str($recipient) { |
|---|
| 149 | log_assert_valid(user_validate($recipient)); |
|---|
| 150 | return '<'.$recipient['email'].'> ['.$recipient['username'] |
|---|
| 151 | .'] '.$recipient['full_name']; |
|---|
| 152 | } |
|---|
| 153 | |
|---|
| 154 | // each newsletter has its own log |
|---|
| 155 | // put $msg into $page_name's log |
|---|
| 156 | function nlog($newsletter_id, $msg) { |
|---|
| 157 | $fd = fopen(IA_NEWSLETTER_LOGDIR.$newsletter_id, "a"); |
|---|
| 158 | log_assert($fd); |
|---|
| 159 | fputs($fd, $msg."\n"); |
|---|
| 160 | fclose($fd); |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | // Tells whether a newsletter log exists |
|---|
| 164 | function nlog_exists($newsletter_id) { |
|---|
| 165 | $fname = IA_NEWSLETTER_LOGDIR.$newsletter_id; |
|---|
| 166 | return file_exists($fname); |
|---|
| 167 | } |
|---|
| 168 | |
|---|
| 169 | // Display newsletter log |
|---|
| 170 | function nlog_view($newsletter_id) { |
|---|
| 171 | $fname = IA_NEWSLETTER_LOGDIR.$newsletter_id; |
|---|
| 172 | log_assert(file_exists($fname)); |
|---|
| 173 | $buffer = file_get_contents($fname); |
|---|
| 174 | echo $buffer; |
|---|
| 175 | } |
|---|
| 176 | |
|---|
| 177 | function log_putch($char) { |
|---|
| 178 | log_assert(1 == strlen($char)); |
|---|
| 179 | static $lcount = 0; |
|---|
| 180 | |
|---|
| 181 | $lcount++; |
|---|
| 182 | echo $char; |
|---|
| 183 | if (0 == $lcount % 79) { |
|---|
| 184 | echo "\n"; |
|---|
| 185 | } |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | /// entry point |
|---|
| 189 | // --------------------------------------------------------------------- |
|---|
| 190 | |
|---|
| 191 | // read page name |
|---|
| 192 | if (2 != $argc) { |
|---|
| 193 | log_error("usage: ./send-newsletter wiki-page-name"); |
|---|
| 194 | } |
|---|
| 195 | $page_name = $argv[1]; |
|---|
| 196 | |
|---|
| 197 | // validate $page_name |
|---|
| 198 | $prefix = 'newsletter/'; |
|---|
| 199 | if ($prefix != substr($page_name, 0, strlen($prefix))) { |
|---|
| 200 | log_error("Newsletter page names should start with $prefix"); |
|---|
| 201 | } |
|---|
| 202 | $textblock = textblock_get_revision($page_name); |
|---|
| 203 | if (!$textblock) { |
|---|
| 204 | log_error("Invalid page name!"); |
|---|
| 205 | } |
|---|
| 206 | $newsletter_id = substr($page_name, strlen($prefix)); |
|---|
| 207 | |
|---|
| 208 | // small preview before entering main menu |
|---|
| 209 | preview_email($textblock, newsletter_anonymous_user(), false); |
|---|
| 210 | |
|---|
| 211 | // check log |
|---|
| 212 | // It is possible that script was interrupted so warn user to resume |
|---|
| 213 | if (nlog_exists($newsletter_id)) { |
|---|
| 214 | log_print("WARNING: This newsletter already has a log! It is " |
|---|
| 215 | ."possible that a prior mass mail process failed.\n"); |
|---|
| 216 | if (read_bool("Would you like to inspect the log?", true)) { |
|---|
| 217 | nlog_view($newsletter_id); |
|---|
| 218 | } |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | // main menu |
|---|
| 222 | while (true) { |
|---|
| 223 | $cmd = read_line("newsletter>"); |
|---|
| 224 | $elems = explode(" ", $cmd); |
|---|
| 225 | if (1 < count($elems)) { |
|---|
| 226 | $cmd = $elems[0]; |
|---|
| 227 | array_shift($elems); |
|---|
| 228 | $param = join(" ", $elems); |
|---|
| 229 | } |
|---|
| 230 | else { |
|---|
| 231 | $param = null; |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | switch ($cmd) { |
|---|
| 235 | case "quit": |
|---|
| 236 | // guess what? |
|---|
| 237 | log_print("Bye!"); |
|---|
| 238 | die(); |
|---|
| 239 | break; |
|---|
| 240 | |
|---|
| 241 | case "preview": |
|---|
| 242 | // preview email as it would be received by a user |
|---|
| 243 | if ($param) { |
|---|
| 244 | $user = user_get_by_username($param); |
|---|
| 245 | if (!$user) { |
|---|
| 246 | log_print("No such user!"); |
|---|
| 247 | break; |
|---|
| 248 | } |
|---|
| 249 | } |
|---|
| 250 | else { |
|---|
| 251 | $user = newsletter_anonymous_user(); |
|---|
| 252 | } |
|---|
| 253 | preview_email($textblock, $user); |
|---|
| 254 | break; |
|---|
| 255 | |
|---|
| 256 | case "count": |
|---|
| 257 | // count subscribers in a given list |
|---|
| 258 | if (!$param) { |
|---|
| 259 | log_print('Forgot to specify subscriber list'); |
|---|
| 260 | break; |
|---|
| 261 | } |
|---|
| 262 | if (!in_array($param, list_valid_ids())) { |
|---|
| 263 | log_print("No such list!"); |
|---|
| 264 | } |
|---|
| 265 | $list = list_get($param); |
|---|
| 266 | log_print(count($list)." subscribers in this list\n"); |
|---|
| 267 | if (!$param) { |
|---|
| 268 | log_print('Forgot to specify subscriber list'); |
|---|
| 269 | break; |
|---|
| 270 | } |
|---|
| 271 | if (!in_array($param, list_valid_ids())) { |
|---|
| 272 | log_print("No such list!"); |
|---|
| 273 | } |
|---|
| 274 | $list = list_get($param); |
|---|
| 275 | $i = 0; |
|---|
| 276 | foreach ($list as $recipient) { |
|---|
| 277 | if (!is_valid_email($recipient['email'])) { |
|---|
| 278 | $status = 'INVALID'; |
|---|
| 279 | } |
|---|
| 280 | else { |
|---|
| 281 | $status = 'ok '; |
|---|
| 282 | } |
|---|
| 283 | log_print($i."\t".$status."\t".recipient_str($recipient)); |
|---|
| 284 | $i++; |
|---|
| 285 | } |
|---|
| 286 | log_print("\n".count($list)." subscribers in this list\n"); |
|---|
| 287 | break; |
|---|
| 288 | |
|---|
| 289 | case "list": |
|---|
| 290 | // list subscribers in a given list |
|---|
| 291 | if (!$param) { |
|---|
| 292 | log_print('Forgot to specify subscriber list'); |
|---|
| 293 | break; |
|---|
| 294 | } |
|---|
| 295 | if (!in_array($param, list_valid_ids())) { |
|---|
| 296 | log_print("No such list!"); |
|---|
| 297 | } |
|---|
| 298 | $list = list_get($param); |
|---|
| 299 | $i = 0; |
|---|
| 300 | foreach ($list as $recipient) { |
|---|
| 301 | if (!is_valid_email($recipient['email'])) { |
|---|
| 302 | $status = 'INVALID'; |
|---|
| 303 | } |
|---|
| 304 | else { |
|---|
| 305 | $status = 'ok '; |
|---|
| 306 | } |
|---|
| 307 | log_print($i."\t".$status."\t".recipient_str($recipient)); |
|---|
| 308 | $i++; |
|---|
| 309 | } |
|---|
| 310 | log_print("\n".count($list)." subscribers in this list\n"); |
|---|
| 311 | break; |
|---|
| 312 | |
|---|
| 313 | case "log": |
|---|
| 314 | // view newsletter log |
|---|
| 315 | if (nlog_exists($newsletter_id)) { |
|---|
| 316 | nlog_view($newsletter_id); |
|---|
| 317 | } |
|---|
| 318 | else { |
|---|
| 319 | log_print("Newsletter has no log."); |
|---|
| 320 | } |
|---|
| 321 | break; |
|---|
| 322 | |
|---|
| 323 | case "send": |
|---|
| 324 | // read list |
|---|
| 325 | $list_id = read_line('Subscriber list?'); |
|---|
| 326 | if (!in_array($list_id, list_valid_ids())) { |
|---|
| 327 | log_print("No such list!"); |
|---|
| 328 | break; |
|---|
| 329 | } |
|---|
| 330 | $list = list_get($list_id); |
|---|
| 331 | log_print("\n".count($list)." subscribers in this list\n"); |
|---|
| 332 | |
|---|
| 333 | // resume sending emails? |
|---|
| 334 | $skip = read_line("Enter next recipient-index to send email to " |
|---|
| 335 | ."(0 means from the beginning):", 0); |
|---|
| 336 | log_assert(is_numeric($skip) && (0 <= $skip) |
|---|
| 337 | && ($skip <= count($list)), "Invalid recipient index"); |
|---|
| 338 | $skip = (int)$skip; |
|---|
| 339 | if (0 < $skip) { |
|---|
| 340 | $left = count($list) - $skip; |
|---|
| 341 | log_print("Skipping {$skip} recipients. There are {$left} " |
|---|
| 342 | ."left."); |
|---|
| 343 | log_print("Last skipped recipient: " |
|---|
| 344 | .recipient_str($list[$skip - 1])); |
|---|
| 345 | log_print("Next recipient is: ".recipient_str($list[$skip])); |
|---|
| 346 | } |
|---|
| 347 | |
|---|
| 348 | echo "\n\n"; |
|---|
| 349 | |
|---|
| 350 | // confirm |
|---|
| 351 | if (!read_bool("This is the final warning! " |
|---|
| 352 | ."Should I start sending emails?", false)) { |
|---|
| 353 | log_print("Aborted by user"); |
|---|
| 354 | break; |
|---|
| 355 | } |
|---|
| 356 | |
|---|
| 357 | // start sending letters |
|---|
| 358 | $i = $skip - 1; |
|---|
| 359 | $count_ok = 0; |
|---|
| 360 | $count_error = 0; |
|---|
| 361 | $used_destinations = array(); |
|---|
| 362 | foreach ($list as $recipient) { |
|---|
| 363 | // skip some recipients |
|---|
| 364 | if (0 < $skip) { |
|---|
| 365 | $skip--; |
|---|
| 366 | continue; |
|---|
| 367 | } |
|---|
| 368 | |
|---|
| 369 | $i++; |
|---|
| 370 | log_assert_valid(user_validate($recipient)); |
|---|
| 371 | |
|---|
| 372 | // log invalid email addresses |
|---|
| 373 | if (!is_valid_email($recipient['email'])) { |
|---|
| 374 | nlog($newsletter_id, |
|---|
| 375 | $i."\tINVALID\t".recipient_str($recipient)); |
|---|
| 376 | log_putch('i'); |
|---|
| 377 | $count_error++; |
|---|
| 378 | continue; |
|---|
| 379 | } |
|---|
| 380 | |
|---|
| 381 | // avoid emailing the same destination, even for |
|---|
| 382 | // different accounts |
|---|
| 383 | if (isset($used_destinations[$recipient['email']])) { |
|---|
| 384 | nlog($newsletter_id, |
|---|
| 385 | $i."\tDUPLICATE\t".recipient_str($recipient)); |
|---|
| 386 | log_putch('D'); |
|---|
| 387 | $count_error++; |
|---|
| 388 | continue; |
|---|
| 389 | } else { |
|---|
| 390 | $used_destinations[$recipient['email']] = true; |
|---|
| 391 | } |
|---|
| 392 | |
|---|
| 393 | // send email |
|---|
| 394 | $phpmail = phpmail_create($textblock, $recipient); |
|---|
| 395 | $success = $phpmail->Send(); |
|---|
| 396 | |
|---|
| 397 | if ($success) { |
|---|
| 398 | log_putch('.'); |
|---|
| 399 | nlog($newsletter_id, $i."\tok\t".recipient_str($recipient)); |
|---|
| 400 | $count_ok++; |
|---|
| 401 | } |
|---|
| 402 | else { |
|---|
| 403 | log_putch('e'); |
|---|
| 404 | nlog($newsletter_id, $i."\tERROR\t". |
|---|
| 405 | recipient_str($recipient)."\t". |
|---|
| 406 | $phpmail->ErrorInfo); |
|---|
| 407 | $count_error++; |
|---|
| 408 | } |
|---|
| 409 | |
|---|
| 410 | // take a break from time to time |
|---|
| 411 | if (0 == ($i + 1) % IA_BURST_LENGTH) { |
|---|
| 412 | usleep(IA_BURST_DELAY * 1000); |
|---|
| 413 | } |
|---|
| 414 | } |
|---|
| 415 | |
|---|
| 416 | log_putch("\n"); |
|---|
| 417 | log_print("\nTask completed!"); |
|---|
| 418 | log_print("{$count_ok} ok; {$count_error} errors; ".count($list) |
|---|
| 419 | ." total (total includes skipped)"); |
|---|
| 420 | break; |
|---|
| 421 | |
|---|
| 422 | default: |
|---|
| 423 | log_print("Invalid command"); |
|---|
| 424 | echo <<<EOS |
|---|
| 425 | |
|---|
| 426 | Valid commands: |
|---|
| 427 | quit |
|---|
| 428 | preview [<username>] |
|---|
| 429 | list <list-id> |
|---|
| 430 | count <list-id> |
|---|
| 431 | send |
|---|
| 432 | log |
|---|
| 433 | EOS; |
|---|
| 434 | // valid subscriber lists |
|---|
| 435 | echo "\nValid subscriber lists: ".join(', ', list_valid_ids()) |
|---|
| 436 | ."\n\n\n"; |
|---|
| 437 | |
|---|
| 438 | } |
|---|
| 439 | } |
|---|
| 440 | |
|---|
| 441 | ?> |
|---|