`/`
`: the digest supplies the row around it.
*
* `abj404_digest_sent` (action): fires once per digest that `wp_mail()`
* accepted, after the cooldown timestamp is stored, with that timestamp (int,
* Unix seconds) as its only argument. It never fires for a skipped or failed
* send.
*
* Both hooks are third-party code running inside the plugin's own cron, so a
* callback that throws is logged as a warning and otherwise ignored: an
* optional section must not stop the digest, and a subscriber must not turn a
* delivered digest into a reported failure.
*
* These names and argument lists are a public contract. Extend them only
* additively.
*/
class ABJ_404_Solution_EmailDigestExtensionPoints {
public const FILTER_SECTIONS = 'abj404_digest_sections';
public const ACTION_SENT = 'abj404_digest_sent';
/** @var ABJ_404_Solution_Logging */
private $logger;
/**
* @param ABJ_404_Solution_Logging $logger Receives the warning when a third-party callback throws.
*/
public function __construct(ABJ_404_Solution_Logging $logger) {
$this->logger = $logger;
}
/**
* Apply the `abj404_digest_sections` filter and render the result as
* digest rows.
*
* @param string $dateRange The digest's date-range label, passed to the filter.
* @return string Table rows to place in the digest body, or '' when no
* plugin contributed a section.
*/
public function renderSections(string $dateRange): string {
try {
$sections = apply_filters(self::FILTER_SECTIONS, array(), $dateRange);
} catch (\Throwable $e) {
$this->warnCallbackThrew(self::FILTER_SECTIONS, $e);
return '';
}
if (!is_array($sections)) {
$this->logger->debugMessage(self::FILTER_SECTIONS . ' must return an array of HTML strings, got '
. gettype($sections) . '; ignoring it.');
return '';
}
$rowTemplate = '';
$rows = '';
foreach ($sections as $section) {
if (!is_string($section)) {
continue;
}
$safeHtml = wp_kses_post($section);
if (trim($safeHtml) === '') {
continue;
}
if ($rowTemplate === '') {
$rowTemplate = ABJ_404_Solution_FileSystemService::readFileContents(
dirname(__DIR__) . '/html/emailDigestExtraSection.html',
false
);
}
$rows .= str_replace('{sectionHtml}', $safeHtml, $rowTemplate);
}
return $rows;
}
/**
* Fire `abj404_digest_sent`.
*
* @param int $sentAt Unix timestamp stored as the digest's last-sent time.
* @return void
*/
public function notifySent(int $sentAt): void {
try {
do_action(self::ACTION_SENT, $sentAt);
} catch (\Throwable $e) {
$this->warnCallbackThrew(self::ACTION_SENT, $e);
}
}
/**
* A third-party callback failing is a warning, not a plugin bug, so it is
* logged below the level that triggers a developer error-report email.
*/
private function warnCallbackThrew(string $hook, \Throwable $e): void {
$this->logger->warn('A callback on the ' . $hook . ' hook threw ' . get_class($e) . ': '
. $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . '). The digest is unaffected.');
}
}
|