1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 
<?php

/**
 * This file is all about mail, how we love it so. In particular it handles the admin side of
 * mail configuration, as well as reviewing the mail queue - if enabled.
 *
 * @todo refactor as controller-model.
 *
 * Simple Machines Forum (SMF)
 *
 * @package SMF
 * @author Simple Machines http://www.simplemachines.org
 * @copyright 2019 Simple Machines and individual contributors
 * @license http://www.simplemachines.org/about/smf/license.php BSD
 *
 * @version 2.1 RC1
 */

if (!defined('SMF'))
    die('No direct access...');

/**
 * Main dispatcher. This function checks permissions and passes control through to the relevant section.
 */
function ManageMail()
{
    global $context, $txt, $sourcedir;

    // You need to be an admin to edit settings!
    isAllowedTo('admin_forum');

    loadLanguage('Help');
    loadLanguage('ManageMail');

    // We'll need the utility functions from here.
    require_once($sourcedir . '/ManageServer.php');

    $context['page_title'] = $txt['mailqueue_title'];
    $context['sub_template'] = 'show_settings';

    $subActions = array(
        'browse' => 'BrowseMailQueue',
        'clear' => 'ClearMailQueue',
        'settings' => 'ModifyMailSettings',
        'test' => 'TestMailSend',
    );

    // By default we want to browse
    $_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'browse';
    $context['sub_action'] = $_REQUEST['sa'];

    // Load up all the tabs...
    $context[$context['admin_menu_name']]['tab_data'] = array(
        'title' => $txt['mailqueue_title'],
        'help' => '',
        'description' => $txt['mailqueue_desc'],
    );

    call_integration_hook('integrate_manage_mail', array(&$subActions));

    // Call the right function for this sub-action.
    call_helper($subActions[$_REQUEST['sa']]);
}

/**
 * Display the mail queue...
 */
function BrowseMailQueue()
{
    global $scripturl, $context, $txt, $smcFunc;
    global $sourcedir, $modSettings;

    // First, are we deleting something from the queue?
    if (isset($_REQUEST['delete']))
    {
        checkSession();

        $smcFunc['db_query']('', '
            DELETE FROM {db_prefix}mail_queue
            WHERE id_mail IN ({array_int:mail_ids})',
            array(
                'mail_ids' => $_REQUEST['delete'],
            )
        );
    }

    // How many items do we have?
    $request = $smcFunc['db_query']('', '
        SELECT COUNT(*) AS queue_size, MIN(time_sent) AS oldest
        FROM {db_prefix}mail_queue',
        array(
        )
    );
    list ($mailQueueSize, $mailOldest) = $smcFunc['db_fetch_row']($request);
    $smcFunc['db_free_result']($request);

    $context['oldest_mail'] = empty($mailOldest) ? $txt['mailqueue_oldest_not_available'] : time_since(time() - $mailOldest);
    $context['mail_queue_size'] = comma_format($mailQueueSize);

    $listOptions = array(
        'id' => 'mail_queue',
        'title' => $txt['mailqueue_browse'],
        'items_per_page' => $modSettings['defaultMaxListItems'],
        'base_href' => $scripturl . '?action=admin;area=mailqueue',
        'default_sort_col' => 'age',
        'no_items_label' => $txt['mailqueue_no_items'],
        'get_items' => array(
            'function' => 'list_getMailQueue',
        ),
        'get_count' => array(
            'function' => 'list_getMailQueueSize',
        ),
        'columns' => array(
            'subject' => array(
                'header' => array(
                    'value' => $txt['mailqueue_subject'],
                ),
                'data' => array(
                    'function' => function($rowData) use ($smcFunc)
                    {
                        return $smcFunc['strlen']($rowData['subject']) > 50 ? sprintf('%1$s...', $smcFunc['htmlspecialchars']($smcFunc['substr']($rowData['subject'], 0, 47))) : $smcFunc['htmlspecialchars']($rowData['subject']);
                    },
                    'class' => 'smalltext',
                ),
                'sort' => array(
                    'default' => 'subject',
                    'reverse' => 'subject DESC',
                ),
            ),
            'recipient' => array(
                'header' => array(
                    'value' => $txt['mailqueue_recipient'],
                ),
                'data' => array(
                    'sprintf' => array(
                        'format' => '<a href="mailto:%1$s">%1$s</a>',
                        'params' => array(
                            'recipient' => true,
                        ),
                    ),
                    'class' => 'smalltext',
                ),
                'sort' => array(
                    'default' => 'recipient',
                    'reverse' => 'recipient DESC',
                ),
            ),
            'priority' => array(
                'header' => array(
                    'value' => $txt['mailqueue_priority'],
                ),
                'data' => array(
                    'function' => function($rowData) use ($txt)
                    {
                        // We probably have a text label with your priority.
                        $txtKey = sprintf('mq_mpriority_%1$s', $rowData['priority']);

                        // But if not, revert to priority 0.
                        return isset($txt[$txtKey]) ? $txt[$txtKey] : $txt['mq_mpriority_1'];
                    },
                    'class' => 'smalltext',
                ),
                'sort' => array(
                    'default' => 'priority',
                    'reverse' => 'priority DESC',
                ),
            ),
            'age' => array(
                'header' => array(
                    'value' => $txt['mailqueue_age'],
                ),
                'data' => array(
                    'function' => function($rowData)
                    {
                        return time_since(time() - $rowData['time_sent']);
                    },
                    'class' => 'smalltext',
                ),
                'sort' => array(
                    'default' => 'time_sent',
                    'reverse' => 'time_sent DESC',
                ),
            ),
            'check' => array(
                'header' => array(
                    'value' => '<input type="checkbox" onclick="invertAll(this, this.form);">',
                ),
                'data' => array(
                    'function' => function($rowData)
                    {
                        return '<input type="checkbox" name="delete[]" value="' . $rowData['id_mail'] . '">';
                    },
                    'class' => 'smalltext',
                ),
            ),
        ),
        'form' => array(
            'href' => $scripturl . '?action=admin;area=mailqueue',
            'include_start' => true,
            'include_sort' => true,
        ),
        'additional_rows' => array(
            array(
                'position' => 'top_of_list',
                'value' => '<input type="submit" name="delete_redirects" value="' . $txt['quickmod_delete_selected'] . '" data-confirm="' . $txt['quickmod_confirm'] . '" class="button you_sure"><a class="button you_sure" href="' . $scripturl . '?action=admin;area=mailqueue;sa=clear;' . $context['session_var'] . '=' . $context['session_id'] . '" data-confirm="' . $txt['mailqueue_clear_list_warning'] . '">' . $txt['mailqueue_clear_list'] . '</a> ',
            ),
            array(
                'position' => 'bottom_of_list',
                'value' => '<input type="submit" name="delete_redirects" value="' . $txt['quickmod_delete_selected'] . '" data-confirm="' . $txt['quickmod_confirm'] . '" class="button you_sure"><a class="button you_sure" href="' . $scripturl . '?action=admin;area=mailqueue;sa=clear;' . $context['session_var'] . '=' . $context['session_id'] . '" data-confirm="' . $txt['mailqueue_clear_list_warning'] . '">' . $txt['mailqueue_clear_list'] . '</a> ',
            ),
        ),
    );

    require_once($sourcedir . '/Subs-List.php');
    createList($listOptions);

    loadTemplate('ManageMail');
    $context['sub_template'] = 'browse';
}

/**
 * This function grabs the mail queue items from the database, according to the params given.
 * Callback for $listOptions['get_items'] in BrowseMailQueue()
 *
 * @param int $start The item to start with (for pagination purposes)
 * @param int $items_per_page How many items to show on each page
 * @param string $sort A string indicating how to sort the results
 * @return array An array with info about the mail queue items
 */
function list_getMailQueue($start, $items_per_page, $sort)
{
    global $smcFunc, $txt;

    $request = $smcFunc['db_query']('', '
        SELECT
            id_mail, time_sent, recipient, priority, private, subject
        FROM {db_prefix}mail_queue
        ORDER BY {raw:sort}
        LIMIT {int:start}, {int:items_per_page}',
        array(
            'start' => $start,
            'sort' => $sort,
            'items_per_page' => $items_per_page,
        )
    );
    $mails = array();
    while ($row = $smcFunc['db_fetch_assoc']($request))
    {
        // Private PM/email subjects and similar shouldn't be shown in the mailbox area.
        if (!empty($row['private']))
            $row['subject'] = $txt['personal_message'];

        $mails[] = $row;
    }
    $smcFunc['db_free_result']($request);

    return $mails;
}

/**
 * Returns the total count of items in the mail queue.
 * Callback for $listOptions['get_count'] in BrowseMailQueue
 *
 * @return int The total number of mail queue items
 */
function list_getMailQueueSize()
{
    global $smcFunc;

    // How many items do we have?
    $request = $smcFunc['db_query']('', '
        SELECT COUNT(*) AS queue_size
        FROM {db_prefix}mail_queue',
        array(
        )
    );
    list ($mailQueueSize) = $smcFunc['db_fetch_row']($request);
    $smcFunc['db_free_result']($request);

    return $mailQueueSize;
}

/**
 * Allows to view and modify the mail settings.
 *
 * @param bool $return_config Whether to return the $config_vars array (used for admin search)
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
 */
function ModifyMailSettings($return_config = false)
{
    global $txt, $scripturl, $context, $modSettings, $txtBirthdayEmails;

    loadLanguage('EmailTemplates');

    $body = $txtBirthdayEmails[(empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']) . '_body'];
    $subject = $txtBirthdayEmails[(empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']) . '_subject'];

    $emails = array();
    $processedBirthdayEmails = array();
    foreach ($txtBirthdayEmails as $key => $value)
    {
        $index = substr($key, 0, strrpos($key, '_'));
        $element = substr($key, strrpos($key, '_') + 1);
        $processedBirthdayEmails[$index][$element] = $value;
    }
    foreach ($processedBirthdayEmails as $index => $dummy)
        $emails[$index] = $index;

    $config_vars = array(
        // Mail queue stuff, this rocks ;)
        array('int', 'mail_limit', 'subtext' => $txt['zero_to_disable']),
        array('int', 'mail_quantity'),
        '',

        // SMTP stuff.
        array('select', 'mail_type', array($txt['mail_type_default'], 'SMTP', 'SMTP - STARTTLS')),
        array('text', 'smtp_host'),
        array('text', 'smtp_port'),
        array('text', 'smtp_username'),
        array('password', 'smtp_password'),
        '',

        array('select', 'birthday_email', $emails, 'value' => array('subject' => $subject, 'body' => $body), 'javascript' => 'onchange="fetch_birthday_preview()"'),
        'birthday_subject' => array('var_message', 'birthday_subject', 'var_message' => $processedBirthdayEmails[empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']]['subject'], 'disabled' => true, 'size' => strlen($subject) + 3),
        'birthday_body' => array('var_message', 'birthday_body', 'var_message' => nl2br($body), 'disabled' => true, 'size' => ceil(strlen($body) / 25)),
    );

    call_integration_hook('integrate_modify_mail_settings', array(&$config_vars));

    if ($return_config)
        return $config_vars;

    // Saving?
    if (isset($_GET['save']))
    {
        // Make the SMTP password a little harder to see in a backup etc.
        if (!empty($_POST['smtp_password'][1]))
        {
            $_POST['smtp_password'][0] = base64_encode($_POST['smtp_password'][0]);
            $_POST['smtp_password'][1] = base64_encode($_POST['smtp_password'][1]);
        }
        checkSession();

        // We don't want to save the subject and body previews.
        unset($config_vars['birthday_subject'], $config_vars['birthday_body']);
        call_integration_hook('integrate_save_mail_settings');

        saveDBSettings($config_vars);
        redirectexit('action=admin;area=mailqueue;sa=settings');
    }

    $context['post_url'] = $scripturl . '?action=admin;area=mailqueue;save;sa=settings';
    $context['settings_title'] = $txt['mailqueue_settings'];

    prepareDBSettingContext($config_vars);

    $context['settings_insert_above'] = '
    <script>
        var bDay = {';

    $i = 0;
    foreach ($processedBirthdayEmails as $index => $email)
    {
        $is_last = ++$i == count($processedBirthdayEmails);
        $context['settings_insert_above'] .= '
            ' . $index . ': {
                subject: ' . JavaScriptEscape($email['subject']) . ',
                body: ' . JavaScriptEscape(nl2br($email['body'])) . '
            }' . (!$is_last ? ',' : '');
    }
    $context['settings_insert_above'] .= '
        };
        function fetch_birthday_preview()
        {
            var index = document.getElementById(\'birthday_email\').value;
            document.getElementById(\'birthday_subject\').innerHTML = bDay[index].subject;
            document.getElementById(\'birthday_body\').innerHTML = bDay[index].body;
        }
    </script>';
}

/**
 * This function clears the mail queue of all emails, and at the end redirects to browse.
 */
function ClearMailQueue()
{
    global $sourcedir, $smcFunc;

    checkSession('get');

    // This is certainly needed!
    require_once($sourcedir . '/ScheduledTasks.php');

    // If we don't yet have the total to clear, find it.
    if (!isset($_GET['te']))
    {
        // How many items do we have?
        $request = $smcFunc['db_query']('', '
            SELECT COUNT(*) AS queue_size
            FROM {db_prefix}mail_queue',
            array(
            )
        );
        list ($_GET['te']) = $smcFunc['db_fetch_row']($request);
        $smcFunc['db_free_result']($request);
    }
    else
        $_GET['te'] = (int) $_GET['te'];

    $_GET['sent'] = isset($_GET['sent']) ? (int) $_GET['sent'] : 0;

    // Send 50 at a time, then go for a break...
    while (ReduceMailQueue(50, true, true) === true)
    {
        // Sent another 50.
        $_GET['sent'] += 50;
        pauseMailQueueClear();
    }

    return BrowseMailQueue();
}

/**
 * Used for pausing the mail queue.
 */
function pauseMailQueueClear()
{
    global $context, $txt, $time_start;

    // Try get more time...
    @set_time_limit(600);
    if (function_exists('apache_reset_timeout'))
        @apache_reset_timeout();

    // Have we already used our maximum time?
    if ((time() - $time_start) < 5)
        return;

    $context['continue_get_data'] = '?action=admin;area=mailqueue;sa=clear;te=' . $_GET['te'] . ';sent=' . $_GET['sent'] . ';' . $context['session_var'] . '=' . $context['session_id'];
    $context['page_title'] = $txt['not_done_title'];
    $context['continue_post_data'] = '';
    $context['continue_countdown'] = '2';
    $context['sub_template'] = 'not_done';

    // Keep browse selected.
    $context['selected'] = 'browse';

    // What percent through are we?
    $context['continue_percent'] = round(($_GET['sent'] / $_GET['te']) * 100, 1);

    // Never more than 100%!
    $context['continue_percent'] = min($context['continue_percent'], 100);

    obExit();
}

/**
 * Test mail sending ability.
 *
 */
function TestMailSend()
{
    global $scripturl, $context, $sourcedir, $user_info, $smcFunc;

    loadLanguage('ManageMail');
    loadTemplate('ManageMail');
    $context['sub_template'] = 'mailtest';
    $context['base_url'] = $scripturl . '?action=admin;area=mailqueue;sa=test';
    $context['post_url'] = $context['base_url'] . ';save';

    // Sending the test message now.
    if (isset($_GET['save']))
    {
        require_once($sourcedir . '/Subs-Post.php');

        // Send to the current user, no options.
        $to = $user_info['email'];
        $subject = $smcFunc['htmlspecialchars']($_POST['subject']);
        $message = $smcFunc['htmlspecialchars']($_POST['message']);

        $result = sendmail($to, $subject, $message, null, null, false, 0);
        redirectexit($context['base_url'] . ';result=' . ($result ? 'success' : 'failure'));
    }

    // The result.
    if (isset($_GET['result']))
        $context['result'] = ($_GET['result'] == 'success' ? 'success' : 'failure');
}

/**
 * Little utility function to calculate how long ago a time was.
 *
 * @param int $time_diff The time difference, in seconds
 * @return string A string indicating how many days, hours, minutes or seconds (depending on $time_diff)
 */
function time_since($time_diff)
{
    global $txt;

    if ($time_diff < 0)
        $time_diff = 0;

    // Just do a bit of an if fest...
    if ($time_diff > 86400)
    {
        $days = round($time_diff / 86400, 1);
        return sprintf($days == 1 ? $txt['mq_day'] : $txt['mq_days'], $time_diff / 86400);
    }
    // Hours?
    elseif ($time_diff > 3600)
    {
        $hours = round($time_diff / 3600, 1);
        return sprintf($hours == 1 ? $txt['mq_hour'] : $txt['mq_hours'], $hours);
    }
    // Minutes?
    elseif ($time_diff > 60)
    {
        $minutes = (int) ($time_diff / 60);
        return sprintf($minutes == 1 ? $txt['mq_minute'] : $txt['mq_minutes'], $minutes);
    }
    // Otherwise must be second
    else
        return sprintf($time_diff == 1 ? $txt['mq_second'] : $txt['mq_seconds'], $time_diff);
}

?>