'send',
];
$map = [
'to' => 'ao_email_to',
'cc' => 'ao_email_cc',
'bcc' => 'ao_email_bcc',
'subject' => 'ao_email_subject',
'context_type' => 'ao_email_context_type',
'context_id' => 'ao_email_context_id',
];
foreach ( $map as $arg_key => $query_key ) {
if ( isset( $args[ $arg_key ] ) && '' !== $args[ $arg_key ] ) {
$query_args[ $query_key ] = $args[ $arg_key ];
}
}
// Base64'd rather than passed as a plain query arg: esc_url() (run
// on this URL wherever it's eventually output) strips encoded line
// breaks and HTML delimiters from href values, which would mangle
// a multi-line or HTML composer body.
if ( isset( $args['message'] ) && '' !== $args['message'] ) {
$query_args['ao_email_message_b64'] = rtrim( strtr( base64_encode( $args['message'] ), '+/', '-_' ), '=' );
}
if ( ! empty( $args['html'] ) ) {
$query_args['ao_email_html'] = '1';
}
/**
* Optional SMTP profile ID to prefill the composer with. Free has
* no notion of SMTP profiles; this is a plain opaque string as far
* as this function is concerned — Pro's settings UI is what
* actually validates and offers it as a selectable option.
*/
if ( isset( $args['profile_id'] ) && '' !== $args['profile_id'] ) {
$query_args['ao_email_profile'] = $args['profile_id'];
}
// Kept as a trailing raw query arg, appended after get_module_admin_url()
// rather than through $map, so a nested return_url's own query
// string can never leak in and override an outer arg (e.g. `page`).
$url = \Yipresser\AdminOptimizer\Modules\SMTP_Email::get_module_admin_url( $query_args );
if ( ! empty( $args['return_url'] ) ) {
$url .= '&ao_email_return_url=' . rawurlencode( $args['return_url'] );
}
return $url;
}
}
if ( ! function_exists( 'adminoptim_email_compose_link' ) ) {
/**
* Build a safe admin button/link for the Email & SMTP composer.
*
* @param array $args Link and composer arguments.
*
* @return string
*/
function adminoptim_email_compose_link( array $args = [] ): string {
$label = isset( $args['label'] ) ? sanitize_text_field( adminoptim_email_string_value( $args['label'] ) ) : __( 'Email', 'admin-optimizer' );
$class = isset( $args['class'] ) ? sanitize_text_field( adminoptim_email_string_value( $args['class'] ) ) : 'button';
$target = isset( $args['target'] ) ? sanitize_key( adminoptim_email_string_value( $args['target'] ) ) : '';
$url = adminoptim_email_compose_url( $args );
$attrs = [
'href="' . esc_url( $url ) . '"',
'class="' . esc_attr( $class ) . '"',
];
if ( '' !== $target ) {
$attrs[] = 'target="' . esc_attr( $target ) . '"';
if ( '_blank' === $target ) {
$attrs[] = 'rel="noopener noreferrer"';
}
}
return '' . esc_html( $label ) . '';
}
}
if ( ! function_exists( 'adminoptim_email_send' ) ) {
/**
* Send email through the Admin Optimizer Email & SMTP module.
*
* @param array $args Send arguments.
*
* @return bool|\WP_Error
*/
function adminoptim_email_send( array $args = [] ): bool|\WP_Error {
$args = adminoptim_email_sanitize_send_args( apply_filters( 'adminoptim_email_send_args', $args ) );
$capability = apply_filters( 'adminoptim_email_compose_capability', 'manage_options', $args );
$capability = is_string( $capability ) && '' !== $capability ? $capability : 'manage_options';
if ( ! current_user_can( $capability ) ) {
return new \WP_Error( 'adminoptim_email_unauthorized', __( 'Unauthorized action.', 'admin-optimizer' ) );
}
if ( empty( $args['to'] ) ) {
return new \WP_Error( 'adminoptim_email_invalid_to', __( 'Please enter a valid To email address.', 'admin-optimizer' ) );
}
if ( '' === $args['subject'] ) {
return new \WP_Error( 'adminoptim_email_missing_subject', __( 'Please enter an email subject.', 'admin-optimizer' ) );
}
if ( '' === trim( wp_strip_all_tags( (string) $args['message'] ) ) ) {
return new \WP_Error( 'adminoptim_email_missing_message', __( 'Please enter an email message.', 'admin-optimizer' ) );
}
$headers = adminoptim_email_build_headers( $args );
$headers = apply_filters( 'adminoptim_email_send_headers', $headers, $args );
$headers = adminoptim_email_sanitize_headers( $headers );
do_action( 'adminoptim_email_before_send', $args );
$send = static function () use ( $args, $headers ) {
return wp_mail( $args['to'], $args['subject'], $args['message'], $headers, $args['attachments'] );
};
// Free has no notion of SMTP profiles or routing; these are no-op
// passthroughs unless Pro hooks the push/pop actions they call.
if ( '' !== $args['profile_id'] ) {
$send = static function () use ( $args, $send ) {
return adminoptim_smtp_with_profile( $args['profile_id'], $send );
};
}
if ( '' !== $args['route'] ) {
$send = static function () use ( $args, $send ) {
return adminoptim_smtp_with_route( $args['route'], $send );
};
}
$sent = (bool) $send();
$result = $sent ? true : new \WP_Error( 'adminoptim_email_delivery_failed', __( 'Email delivery failed.', 'admin-optimizer' ) );
$result = apply_filters( 'adminoptim_email_send_result', $result, $args );
do_action( 'adminoptim_email_after_send', $args, $result );
if ( is_wp_error( $result ) ) {
return $result;
}
return true === $result ? true : new \WP_Error( 'adminoptim_email_delivery_failed', __( 'Email delivery failed.', 'admin-optimizer' ) );
}
}
if ( ! function_exists( 'adminoptim_email_sanitize_compose_args' ) ) {
/**
* Sanitize composer arguments.
*
* @param mixed $args Raw args.
*
* @return array
*/
function adminoptim_email_sanitize_compose_args( $args ): array {
$args = is_array( $args ) ? $args : [];
$html = ! empty( $args['html'] );
$clean = [
'to' => adminoptim_email_sanitize_email_list_text( $args['to'] ?? '' ),
'cc' => adminoptim_email_sanitize_email_list_text( $args['cc'] ?? '' ),
'bcc' => adminoptim_email_sanitize_email_list_text( $args['bcc'] ?? '' ),
'subject' => isset( $args['subject'] ) ? sanitize_text_field( adminoptim_email_string_value( $args['subject'] ) ) : '',
'message' => isset( $args['message'] ) ? ( $html ? wp_kses_post( adminoptim_email_string_value( $args['message'] ) ) : sanitize_textarea_field( adminoptim_email_string_value( $args['message'] ) ) ) : '',
'html' => $html,
'context_type' => isset( $args['context_type'] ) ? sanitize_key( adminoptim_email_string_value( $args['context_type'] ) ) : '',
'context_id' => isset( $args['context_id'] ) ? sanitize_text_field( adminoptim_email_string_value( $args['context_id'] ) ) : '',
'return_url' => '',
'profile_id' => isset( $args['profile_id'] ) ? sanitize_key( adminoptim_email_string_value( $args['profile_id'] ) ) : '',
];
if ( isset( $args['return_url'] ) ) {
$return_url = esc_url_raw( adminoptim_email_string_value( $args['return_url'] ) );
$clean['return_url'] = '' !== $return_url ? wp_validate_redirect( $return_url, '' ) : '';
}
return $clean;
}
}
if ( ! function_exists( 'adminoptim_email_sanitize_send_args' ) ) {
/**
* Sanitize direct-send arguments.
*
* @param mixed $args Raw args.
*
* @return array
*/
function adminoptim_email_sanitize_send_args( $args ): array {
$args = is_array( $args ) ? $args : [];
$html = ! empty( $args['html'] ) || ! empty( $args['send_as_html'] );
return [
'to' => adminoptim_email_sanitize_email_list( $args['to'] ?? '' ),
'cc' => adminoptim_email_sanitize_email_list( $args['cc'] ?? '' ),
'bcc' => adminoptim_email_sanitize_email_list( $args['bcc'] ?? '' ),
'subject' => isset( $args['subject'] ) ? sanitize_text_field( adminoptim_email_string_value( $args['subject'] ) ) : '',
'message' => isset( $args['message'] ) ? ( $html ? wp_kses_post( adminoptim_email_string_value( $args['message'] ) ) : wp_strip_all_tags( adminoptim_email_string_value( $args['message'] ) ) ) : '',
'html' => $html,
'attachments' => adminoptim_email_sanitize_attachments( $args['attachments'] ?? [] ),
'headers' => adminoptim_email_sanitize_headers( $args['headers'] ?? [] ),
'context_type' => isset( $args['context_type'] ) ? sanitize_key( adminoptim_email_string_value( $args['context_type'] ) ) : '',
'context_id' => isset( $args['context_id'] ) ? sanitize_text_field( adminoptim_email_string_value( $args['context_id'] ) ) : '',
// Only resolved to a real profile ID (falling back to 'primary')
// when the caller explicitly asked to route this send — an
// absent key must stay '' so adminoptim_email_send() below
// knows not to touch routing for this send at all.
'profile_id' => array_key_exists( 'profile_id', $args ) ? adminoptim_email_sanitize_profile_id( $args['profile_id'] ) : '',
'route' => isset( $args['route'] ) ? adminoptim_email_sanitize_route_key( $args['route'] ) : '',
];
}
}
if ( ! function_exists( 'adminoptim_email_sanitize_email_list' ) ) {
/**
* Sanitize a recipient list.
*
* @param mixed $value Raw value.
*
* @return array
*/
function adminoptim_email_sanitize_email_list( $value ): array {
if ( is_array( $value ) ) {
$value = implode( ',', array_map( 'adminoptim_email_string_value', $value ) );
}
$emails = preg_split( '/[\s,;]+/', (string) $value );
$clean = [];
if ( ! is_array( $emails ) ) {
return [];
}
foreach ( $emails as $email ) {
$email = sanitize_email( $email );
if ( is_email( $email ) ) {
$clean[] = $email;
}
}
return array_values( array_unique( $clean ) );
}
}
if ( ! function_exists( 'adminoptim_email_sanitize_email_list_text' ) ) {
/**
* Sanitize a recipient list for form/query display.
*
* @param mixed $value Raw value.
*
* @return string
*/
function adminoptim_email_sanitize_email_list_text( $value ): string {
return implode( ', ', adminoptim_email_sanitize_email_list( $value ) );
}
}
if ( ! function_exists( 'adminoptim_email_build_headers' ) ) {
/**
* Build headers for Cc, Bcc, HTML, and custom headers.
*
* @param array $args Sanitized args.
*
* @return array
*/
function adminoptim_email_build_headers( array $args ): array {
$headers = is_array( $args['headers'] ?? [] ) ? $args['headers'] : [];
foreach ( (array) ( $args['cc'] ?? [] ) as $email ) {
$headers[] = 'Cc: ' . $email;
}
foreach ( (array) ( $args['bcc'] ?? [] ) as $email ) {
$headers[] = 'Bcc: ' . $email;
}
if ( ! empty( $args['html'] ) ) {
$headers[] = 'Content-Type: text/html; charset=' . get_bloginfo( 'charset' );
}
return $headers;
}
}
if ( ! function_exists( 'adminoptim_email_sanitize_headers' ) ) {
/**
* Sanitize mail headers.
*
* @param mixed $headers Raw headers.
*
* @return array
*/
function adminoptim_email_sanitize_headers( $headers ): array {
if ( is_string( $headers ) ) {
$headers = preg_split( '/\r\n|\r|\n/', $headers );
}
$headers = is_array( $headers ) ? $headers : [];
$clean = [];
foreach ( $headers as $header ) {
$header = sanitize_text_field( adminoptim_email_string_value( $header ) );
if ( '' !== $header ) {
$clean[] = $header;
}
}
return $clean;
}
}
if ( ! function_exists( 'adminoptim_email_sanitize_attachments' ) ) {
/**
* Sanitize attachment paths.
*
* @param mixed $attachments Raw attachments.
*
* @return array
*/
function adminoptim_email_sanitize_attachments( $attachments ): array {
if ( is_string( $attachments ) ) {
$attachments = [ $attachments ];
}
$attachments = is_array( $attachments ) ? $attachments : [];
$clean = [];
foreach ( $attachments as $attachment ) {
$attachment = sanitize_text_field( adminoptim_email_string_value( $attachment ) );
if ( '' !== $attachment ) {
$clean[] = $attachment;
}
}
return $clean;
}
}
if ( ! function_exists( 'adminoptim_smtp_with_profile' ) ) {
/**
* Run a callback with a specific SMTP profile ID pinned for its
* duration.
*
* Free has exactly one profile and no routing of its own; this is a
* plain pass-through call to $callback() unless Pro hooks the two
* actions below (which it does, onto SMTP_Router::push_profile_id() /
* pop_profile_id()) to actually apply that profile's transport.
*
* @param string $profile_id Profile ID.
* @param callable $callback Callback that sends email.
*
* @return mixed
*/
function adminoptim_smtp_with_profile( string $profile_id, callable $callback ) {
do_action( 'adminoptim_smtp_push_profile', $profile_id );
try {
return $callback();
} finally {
do_action( 'adminoptim_smtp_pop_profile' );
}
}
}
if ( ! function_exists( 'adminoptim_smtp_with_route' ) ) {
/**
* Run a callback while tagging outgoing mail with a route key (e.g.
* `transactional`, `password_reset`) for its duration.
*
* Free has no routing rules of its own; this is a no-op pass-through
* unless Pro hooks the two actions below to resolve the route to a
* profile.
*
* @param string $route_key Route key.
* @param callable $callback Callback that sends email.
*
* @return mixed
*/
function adminoptim_smtp_with_route( string $route_key, callable $callback ) {
do_action( 'adminoptim_smtp_push_route', $route_key );
try {
return $callback();
} finally {
do_action( 'adminoptim_smtp_pop_route' );
}
}
}
if ( ! function_exists( 'adminoptim_smtp_with_source' ) ) {
/**
* Run a callback while tagging outgoing mail with a source context
* (e.g. `woocommerce`, `form_builder`) for its duration.
*
* Free never logs deliveries at all; this is a no-op pass-through
* unless Pro hooks the two actions below onto its delivery logger.
*
* @param string $source Source key.
* @param callable $callback Callback that sends email.
*
* @return mixed
*/
function adminoptim_smtp_with_source( string $source, callable $callback ) {
do_action( 'adminoptim_smtp_push_source', $source );
try {
return $callback();
} finally {
do_action( 'adminoptim_smtp_pop_source' );
}
}
}
if ( ! function_exists( 'adminoptim_email_sanitize_profile_id' ) ) {
/**
* Sanitize an SMTP profile ID.
*
* Free has no per-profile validation of its own; this is a generic
* key sanitizer with a 'primary' fallback, matching the format Pro's
* profiles use.
*
* @param mixed $profile_id Raw profile ID.
*
* @return string
*/
function adminoptim_email_sanitize_profile_id( $profile_id ): string {
$profile_id = sanitize_key( str_replace( '-', '_', adminoptim_email_string_value( $profile_id ) ) );
return '' !== $profile_id ? $profile_id : 'primary';
}
}
if ( ! function_exists( 'adminoptim_email_sanitize_route_key' ) ) {
/**
* Sanitize an SMTP route key.
*
* @param mixed $route_key Raw route key.
*
* @return string
*/
function adminoptim_email_sanitize_route_key( $route_key ): string {
return sanitize_key( str_replace( '-', '_', adminoptim_email_string_value( $route_key ) ) );
}
}
if ( ! function_exists( 'adminoptim_email_profile_exists' ) ) {
/**
* Check whether an SMTP profile ID exists and is enabled.
*
* Free only ever has the primary profile; this defers to Pro for
* anything else via a filter, since Free has no way to inspect Pro's
* saved profiles itself.
*
* @param string $profile_id Profile ID.
*
* @return bool
*/
function adminoptim_email_profile_exists( string $profile_id ): bool {
$profile_id = adminoptim_email_sanitize_profile_id( $profile_id );
if ( 'primary' === $profile_id ) {
return true;
}
/**
* Filters whether a non-primary SMTP profile ID exists and is
* enabled. Free has none; Pro hooks this to check its own saved
* additional profiles.
*
* @param bool $exists False by default.
* @param string $profile_id Sanitized profile ID.
*/
return (bool) apply_filters( 'adminoptim_smtp_profile_exists', false, $profile_id );
}
}
if ( ! function_exists( 'adminoptim_email_string_value' ) ) {
/**
* Convert scalar values to strings without array/object notices.
*
* @param mixed $value Raw value.
*
* @return string
*/
function adminoptim_email_string_value( $value ): string {
if ( is_scalar( $value ) || null === $value ) {
return (string) $value;
}
return '';
}
}