mirror of
https://github.com/itflow-org/itflow
synced 2026-11-11 15:07:24 +00:00
196 lines
13 KiB
PHP
196 lines
13 KiB
PHP
<?php
|
|
|
|
/*
|
|
* Admin > Text Messaging - the texting service ITFlow texts through (functions/sms.php), one
|
|
* number for every client. Each row says whether it is on, the number, the address its texts
|
|
* come back to (pasted into the service's portal), and its texts in the last 30 days. Send a
|
|
* test text tries it; Opted out and blocked lists the numbers that replied STOP or were
|
|
* marked spam. Add and Edit are one window (modals/sms_provider/sms_provider_edit.php); the
|
|
* rest are admin/post/sms_provider.php.
|
|
*/
|
|
|
|
// Default Column Sortby Filter
|
|
$sort = "sms_provider_name";
|
|
$order = "ASC";
|
|
|
|
require_once "includes/inc_all_admin.php";
|
|
|
|
// The tables arrive with the 3.6.0 database update
|
|
try {
|
|
mysqli_query($mysqli, "SELECT 1 FROM sms_providers LIMIT 1");
|
|
} catch (Throwable $e) { ?>
|
|
<div class="alert alert-warning"><i class="fas fa-fw fa-exclamation-triangle me-2"></i>Text messaging arrives with a database update - run it in <a href="update.php">Maintenance > Update</a> first.</div>
|
|
<?php
|
|
require_once "../includes/footer.php";
|
|
exit;
|
|
}
|
|
|
|
// Only these sort - an unknown column would take the page down
|
|
$sortable_columns = [
|
|
'sms_provider_name' => "sms_provider_name $order",
|
|
'sms_provider_number' => "sms_provider_number $order, sms_provider_name ASC",
|
|
'sms_provider_last_inbound_at' => "sms_provider_last_inbound_at IS NULL, sms_provider_last_inbound_at $order, sms_provider_name ASC",
|
|
];
|
|
if (!isset($sortable_columns[$sort])) {
|
|
$sort = 'sms_provider_name';
|
|
}
|
|
|
|
$sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS * FROM sms_providers
|
|
WHERE (sms_provider_name LIKE '%$q%' OR sms_provider_description LIKE '%$q%' OR sms_provider_number LIKE '%$phone_query%')
|
|
ORDER BY {$sortable_columns[$sort]}, sms_provider_id ASC
|
|
LIMIT $record_from, $record_to");
|
|
|
|
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
|
|
|
|
$set_up = intval(mysqli_fetch_row(mysqli_query($mysqli, "SELECT COUNT(DISTINCT sms_provider_name) FROM sms_providers"))[0]);
|
|
$can_add = $set_up < count(smsProviderKinds());
|
|
$activity = smsProviderActivity();
|
|
$numbers = mysqli_fetch_row(mysqli_query($mysqli, "SELECT COALESCE(SUM(sms_number_opted_out_at IS NOT NULL), 0), COALESCE(SUM(sms_number_blocked_at IS NOT NULL), 0) FROM sms_numbers"));
|
|
$opted_out = intval($numbers[0]);
|
|
$blocked = intval($numbers[1]);
|
|
|
|
$sort_link = function ($column, $label) use ($url_query_strings_sort, $sort, $disp, $order_icon) {
|
|
return '<a class="text-reset text-decoration-none" href="?' . escapeHtml($url_query_strings_sort) . '&sort=' . $column . '&order=' . $disp . '">'
|
|
. $label . ($sort === $column ? " $order_icon" : '') . '</a>';
|
|
};
|
|
|
|
$add_url = 'modals/sms_provider/sms_provider_add.php';
|
|
$csrf = $_SESSION['csrf_token'];
|
|
|
|
?>
|
|
|
|
<div class="card">
|
|
<div class="card-header bg-dark py-2">
|
|
<h3 class="card-title mt-2"><i class="fas fa-fw fa-sms me-2"></i>Text Messaging</h3>
|
|
<div class="card-tools">
|
|
<a class="btn btn-outline-light me-1 ajax-modal" href="#" data-modal-url="modals/sms_provider/sms_numbers.php" title="Numbers that replied STOP, and numbers blocked as spam">
|
|
<i class="fas fa-fw fa-ban"></i><span class="d-none d-lg-inline ms-2">Opted out and blocked</span><?php if ($opted_out + $blocked) { ?> <span class="badge text-bg-light ms-1"><?= $opted_out + $blocked ?></span><?php } ?>
|
|
</a>
|
|
<a class="btn btn-outline-light me-1" href="text_messages.php"><i class="fas fa-fw fa-list"></i><span class="d-none d-lg-inline ms-2">Text Messages</span></a>
|
|
<?php if ($can_add) { ?>
|
|
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="<?= $add_url ?>"><i class="fas fa-plus"></i><span class="d-none d-lg-inline ms-2">Add Provider</span></button>
|
|
<?php } ?>
|
|
</div>
|
|
</div>
|
|
<div class="card-header py-3">
|
|
<form autocomplete="off">
|
|
<div class="row g-2 align-items-center">
|
|
<div class="col-lg-5 col-md-6">
|
|
<div class="input-group">
|
|
<input type="search" class="form-control" name="q" value="<?php if (isset($q)) { echo stripslashes(escapeHtml($q)); } ?>" placeholder="Search texting providers..." title="Name, description or number">
|
|
<button class="btn btn-dark" title="Search"><i class="fa fa-search"></i></button>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-7 col-md-6 small text-body-secondary text-md-end" data-sms-activity>
|
|
In the last 30 days: <?= $activity['sent'] ?> sent<?php if ($activity['failed']) { ?>, <a class="text-danger-emphasis" href="text_messages.php?show=failed"><?= $activity['failed'] ?> failed</a><?php } ?>, <?= $activity['received'] ?> received
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="table-light text-nowrap <?php if ($num_rows[0] == 0) { echo "d-none"; } ?>">
|
|
<tr>
|
|
<th class="ps-3"><?= $sort_link('sms_provider_name', 'Provider') ?></th>
|
|
<th class="d-none d-md-table-cell"><?= $sort_link('sms_provider_number', 'Number') ?></th>
|
|
<th class="d-none d-lg-table-cell">Texts come back to</th>
|
|
<th class="d-none d-md-table-cell"><?= $sort_link('sms_provider_last_inbound_at', 'Last text in') ?></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php while ($row = mysqli_fetch_assoc($sql)) {
|
|
$provider_id = intval($row['sms_provider_id']);
|
|
$name = (string) $row['sms_provider_name'];
|
|
$kind = smsProviderKinds()[$name] ?? null;
|
|
$on = intval($row['sms_provider_active']) === 1;
|
|
$edit_url = "modals/sms_provider/sms_provider_edit.php?id=$provider_id";
|
|
$inbound_url = smsInboundUrl($row);
|
|
?>
|
|
<tr data-sms-provider-id="<?= $provider_id ?>" class="<?= $on ? '' : 'text-body-secondary' ?>">
|
|
<td class="ps-3">
|
|
<div class="d-flex align-items-center gap-3">
|
|
<span class="d-none d-sm-inline-flex itflow-asset-icon itflow-asset-tone-other" aria-hidden="true"><i class="<?= escapeHtml($kind['icon'] ?? 'fas fa-sms') ?>"></i></span>
|
|
<div class="min-w-0">
|
|
<a class="fw-semibold text-body ajax-modal" href="#" data-modal-url="<?= $edit_url ?>"><?= escapeHtml($name) ?></a>
|
|
<?php if (!$on) { ?>
|
|
<span class="badge text-bg-secondary ms-1" data-sms-provider-state="off" title="No texts go out, and texts sent to the number aren't taken in">Off</span>
|
|
<?php } ?>
|
|
<?php if (trim((string) $row['sms_provider_description']) !== '') { ?>
|
|
<div class="small text-body-secondary"><?= escapeHtml($row['sms_provider_description']) ?></div>
|
|
<?php } ?>
|
|
<!-- On smaller screens the number comes along under the name -->
|
|
<div class="d-md-none small text-body-secondary"><?= escapeHtml(smsNumberText($row['sms_provider_number'])) ?></div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="d-none d-md-table-cell text-nowrap"><?= escapeHtml(smsNumberText($row['sms_provider_number'])) ?></td>
|
|
<td class="d-none d-lg-table-cell small" style="max-width: 28rem;">
|
|
<div class="input-group input-group-sm">
|
|
<input type="text" class="form-control font-monospace" readonly value="<?= escapeHtml($inbound_url) ?>" id="smsInbound<?= $provider_id ?>" aria-label="Where texts come back to">
|
|
<button type="button" class="btn btn-outline-secondary clipboardjs" data-clipboard-target="#smsInbound<?= $provider_id ?>" title="Copy"><i class="far fa-fw fa-copy"></i></button>
|
|
</div>
|
|
<div class="text-body-secondary mt-1">In <?= escapeHtml($name) ?>'s portal: the number > SMS > Callback URL, by POST.</div>
|
|
</td>
|
|
<td class="d-none d-md-table-cell small text-nowrap" data-sms-last-in>
|
|
<?php if ($row['sms_provider_last_inbound_at']) { ?>
|
|
<span title="<?= escapeHtml($row['sms_provider_last_inbound_at']) ?>"><?= escapeHtml(timeAgo($row['sms_provider_last_inbound_at'])) ?></span>
|
|
<?php } else { ?>
|
|
<span class="text-body-secondary">None yet - text the number to try it</span>
|
|
<?php } ?>
|
|
</td>
|
|
<td class="text-end pe-3">
|
|
<div class="dropdown dropstart">
|
|
<button class="btn btn-sm btn-light" title="More actions" type="button" data-bs-toggle="dropdown"><i class="fas fa-fw fa-ellipsis-v"></i></button>
|
|
<div class="dropdown-menu">
|
|
<a class="dropdown-item ajax-modal" href="#" data-modal-url="<?= $edit_url ?>">
|
|
<i class="fas fa-fw fa-edit text-secondary me-2"></i>Edit
|
|
</a>
|
|
<?php if ($on) { ?>
|
|
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/sms_provider/sms_test.php?id=<?= $provider_id ?>">
|
|
<i class="fas fa-fw fa-vial text-secondary me-2"></i>Send a test text
|
|
</a>
|
|
<?php } ?>
|
|
<a class="dropdown-item confirm-link" href="post.php?new_sms_inbound_key=<?= $provider_id ?>&csrf_token=<?= $csrf ?>"
|
|
data-confirm-title="Make a new address for texts to come back to?" data-confirm-button="Make a new one"
|
|
data-confirm-text="The one <?= escapeHtml($name) ?> has stops working - paste the new one into its portal, or texts to the number won't reach ITFlow.">
|
|
<i class="fas fa-fw fa-sync-alt text-secondary me-2"></i>New inbound address
|
|
</a>
|
|
<?php if ($on) { ?>
|
|
<a class="dropdown-item confirm-link" href="post.php?turn_sms_provider_off=<?= $provider_id ?>&csrf_token=<?= $csrf ?>"
|
|
data-confirm-title="Turn <?= escapeHtml($name) ?> off?" data-confirm-text="No texts go out, and texts to the number aren't taken in until it is on again. Waiting texts wait." data-confirm-button="Turn off">
|
|
<i class="fas fa-fw fa-toggle-off text-secondary me-2"></i>Turn off
|
|
</a>
|
|
<?php } else { ?>
|
|
<a class="dropdown-item" href="post.php?turn_sms_provider_on=<?= $provider_id ?>&csrf_token=<?= $csrf ?>">
|
|
<i class="fas fa-fw fa-toggle-on text-secondary me-2"></i>Turn on
|
|
</a>
|
|
<?php } ?>
|
|
<div class="dropdown-divider"></div>
|
|
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_sms_provider=<?= $provider_id ?>&csrf_token=<?= $csrf ?>"
|
|
data-confirm-title="Delete <?= escapeHtml($name) ?>?" data-confirm-button="Delete"
|
|
data-confirm-text="Nothing more is texted, and texts to the number stop reaching ITFlow. The texts already sent and received stay in the log and on their tickets.">
|
|
<i class="fas fa-fw fa-trash me-2"></i>Delete
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php } ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php
|
|
$empty_state = [
|
|
'thing' => 'texting providers',
|
|
'icon' => 'fa-sms',
|
|
'text' => 'Set up Skyetel and your agents can reply to tickets by text, texts from your clients open tickets, and verification codes texted to your number land in Texts.',
|
|
'actions' => $can_add ? [['label' => 'Add Provider', 'modal' => $add_url]] : [],
|
|
];
|
|
?>
|
|
<?php require_once "../includes/filter_footer.php"; ?>
|
|
</div>
|
|
|
|
<?php
|
|
require_once "../includes/footer.php";
|