*/
private static function choices( array $opt ) {
$choices = $opt['choices'] ?? array();
if ( is_callable( $choices ) ) {
$choices = call_user_func( $choices );
}
return is_array( $choices ) ? $choices : array();
}
/**
* Add a settings error when the admin API is loaded (the option can also be updated
* from WP-CLI or front-end code, where add_settings_error() does not exist).
*
* @param string $code Code.
* @param string $message Message.
*/
private static function settings_error( $code, $message ) {
if ( function_exists( 'add_settings_error' ) ) {
add_settings_error( PINGPUSH_OPTION, $code, $message );
}
}
/**
* Sanitize the whole option.
*
* @param mixed $input Raw input.
* @return array
*/
public function sanitize( $input ) {
$old = self::get();
if ( ! is_array( $input ) ) {
return $old;
}
if ( ! current_user_can( 'manage_options' ) && ! ( defined( 'WP_CLI' ) && WP_CLI ) ) {
return $old;
}
$out = self::defaults();
foreach ( array( 'token', 'user_key' ) as $key ) {
$value = isset( $input[ $key ] ) ? preg_replace( '/\s+/', '', sanitize_text_field( (string) $input[ $key ] ) ) : '';
if ( '' !== $value && ! PingPush_API::looks_like_key( $value ) ) {
self::settings_error(
'pingpush_' . $key,
'token' === $key
? __( 'The API token must be exactly 30 letters and numbers, so it was not saved and the previous value was kept. If your browser or password manager filled in a password, paste the token from pushover.net instead.', 'pingpush' )
: __( 'The user key must be exactly 30 letters and numbers, so it was not saved and the previous value was kept. If your browser or password manager filled in a password, paste the key from pushover.net instead.', 'pingpush' )
);
$value = $old[ $key ];
}
$out[ $key ] = $value;
}
$priority = isset( $input['default_priority'] ) ? (int) $input['default_priority'] : 0;
$out['default_priority'] = PingPush_API::sanitize_priority( $priority );
$out['include_site_name'] = empty( $input['include_site_name'] ) ? 0 : 1;
$out['log_enabled'] = empty( $input['log_enabled'] ) ? 0 : 1;
$out['log_size'] = max( 10, min( 1000, (int) ( $input['log_size'] ?? 100 ) ) );
// Events. Keep saved settings for events whose plugin is currently inactive.
$events = is_array( $old['events'] ) ? $old['events'] : array();
$raw_events = isset( $input['events'] ) && is_array( $input['events'] ) ? $input['events'] : array();
$sounds = PingPush_API::sounds();
foreach ( PingPush_Events::all() as $id => $def ) {
if ( 'custom' === $def['source'] ) {
continue;
}
$raw = isset( $raw_events[ $id ] ) && is_array( $raw_events[ $id ] ) ? $raw_events[ $id ] : array();
$cur = PingPush_Events::settings_for( $id );
$priority = $raw['priority'] ?? $cur['priority'];
$priority = ( 'default' === $priority ) ? 'default' : PingPush_API::sanitize_priority( $priority );
$sound = (string) ( $raw['sound'] ?? $cur['sound'] );
$row = array(
'enabled' => empty( $raw['enabled'] ) ? 0 : 1,
'priority' => $priority,
'sound' => array_key_exists( $sound, $sounds ) ? $sound : '',
'title' => sanitize_text_field( (string) ( $raw['title'] ?? '' ) ),
'message' => sanitize_textarea_field( (string) ( $raw['message'] ?? '' ) ),
);
// Store blank when equal to default so future default improvements apply.
if ( $row['title'] === $def['title'] ) {
$row['title'] = '';
}
if ( $row['message'] === $def['message'] ) {
$row['message'] = '';
}
foreach ( $def['options'] as $key => $opt ) {
$type = $opt['type'] ?? 'text';
$choices = self::choices( $opt );
if ( 'checkboxes' === $type ) {
$picked = isset( $raw[ $key ] ) && is_array( $raw[ $key ] ) ? array_map( 'strval', $raw[ $key ] ) : array();
$row[ $key ] = array_values( array_intersect( $picked, array_map( 'strval', array_keys( $choices ) ) ) );
} elseif ( 'select' === $type ) {
$value = (string) ( $raw[ $key ] ?? '' );
$row[ $key ] = array_key_exists( $value, $choices ) ? $value : (string) ( $opt['default'] ?? '' );
} else {
$row[ $key ] = sanitize_text_field( (string) ( $raw[ $key ] ?? '' ) );
}
}
$events[ $id ] = $row;
}
$out['events'] = $events;
// Custom hooks.
$hooks = array();
$seen = array();
$raw = isset( $input['custom_hooks'] ) && is_array( $input['custom_hooks'] ) ? $input['custom_hooks'] : array();
foreach ( $raw as $row ) {
if ( ! is_array( $row ) ) {
continue;
}
$hook = trim( sanitize_text_field( (string) ( $row['hook'] ?? '' ) ) );
if ( '' === $hook ) {
continue;
}
$valid = PingPush_Event_Custom_Hook::validate( $hook );
if ( true !== $valid ) {
/* translators: 1: hook name, 2: reason. */
self::settings_error( 'pingpush_hook_' . md5( $hook ), sprintf( __( 'Custom hook "%1$s" was not saved: %2$s', 'pingpush' ), $hook, $valid ) );
continue;
}
if ( isset( $seen[ $hook ] ) || count( $hooks ) >= PingPush_Event_Custom_Hook::MAX_HOOKS ) {
continue;
}
$seen[ $hook ] = true;
$sound = (string) ( $row['sound'] ?? '' );
$hooks[] = array(
'hook' => $hook,
'enabled' => empty( $row['enabled'] ) ? 0 : 1,
'title' => sanitize_text_field( (string) ( $row['title'] ?? '' ) ),
'message' => sanitize_textarea_field( (string) ( $row['message'] ?? '' ) ),
'priority' => PingPush_API::sanitize_priority( $row['priority'] ?? 0 ),
'sound' => array_key_exists( $sound, $sounds ) ? $sound : '',
);
}
$out['custom_hooks'] = $hooks;
return $out;
}
/* ---------- AJAX ---------- */
/**
* Keys from the request (so they can be tested before saving), falling back to saved ones.
*
* @return array{0:string,1:string}
*/
private function request_keys() {
check_ajax_referer( self::NONCE, 'nonce' );
$settings = self::get();
$token = isset( $_POST['token'] ) ? preg_replace( '/\s+/', '', sanitize_text_field( wp_unslash( $_POST['token'] ) ) ) : '';
$user = isset( $_POST['user_key'] ) ? preg_replace( '/\s+/', '', sanitize_text_field( wp_unslash( $_POST['user_key'] ) ) ) : '';
return array(
'' !== $token ? $token : (string) $settings['token'],
'' !== $user ? $user : (string) $settings['user_key'],
);
}
/**
* Check keys format.
*
* @param string $token Token.
* @param string $user User.
*/
private function require_keys( $token, $user ) {
if ( ! PingPush_API::looks_like_key( $token ) ) {
wp_send_json_error( array( 'message' => __( 'The Application API token must be exactly 30 letters and numbers. Paste it from your application page on pushover.net (if a password manager filled the field, clear it first).', 'pingpush' ) ) );
}
if ( ! PingPush_API::looks_like_key( $user ) ) {
wp_send_json_error( array( 'message' => __( 'The User or Group key must be exactly 30 letters and numbers. Paste it from your pushover.net dashboard (if a password manager filled the field, clear it first).', 'pingpush' ) ) );
}
}
/**
* Validate keys.
*/
public function ajax_validate() {
check_ajax_referer( self::NONCE, 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'You are not allowed to do this.', 'pingpush' ) ), 403 );
}
list( $token, $user ) = $this->request_keys();
$this->require_keys( $token, $user );
$result = PingPush_API::validate( $token, $user );
if ( ! $result['ok'] ) {
wp_send_json_error( array( 'message' => $result['error'] ) );
}
$message = __( 'Keys are valid.', 'pingpush' );
if ( ! empty( $result['devices'] ) ) {
/* translators: %s: device names. */
$message .= ' ' . sprintf( __( 'Active devices: %s.', 'pingpush' ), implode( ', ', $result['devices'] ) );
}
wp_send_json_success( array( 'message' => $message ) );
}
/**
* Send a test notification (synchronously, so the result shows right away).
*/
public function ajax_test() {
check_ajax_referer( self::NONCE, 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'You are not allowed to do this.', 'pingpush' ) ), 403 );
}
list( $token, $user ) = $this->request_keys();
$this->require_keys( $token, $user );
$site = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
$title = __( 'PingPush test', 'pingpush' );
$result = PingPush_API::send(
$token,
$user,
array(
'title' => $title,
/* translators: %s: site name. */
'message' => sprintf( __( 'Notifications from %s are working.', 'pingpush' ), $site ),
'url' => self::url(),
'url_title' => __( 'PingPush settings', 'pingpush' ),
'priority' => 0,
)
);
PingPush_Logger::add( 'test', $title, $result['ok'] ? 'sent' : 'failed', $result['code'], $result['error'] );
if ( ! $result['ok'] ) {
wp_send_json_error( array( 'message' => $result['error'] ) );
}
wp_send_json_success( array( 'message' => __( 'Test notification sent. Check your device.', 'pingpush' ) ) );
}
/**
* Clear the log.
*/
public function clear_log() {
check_admin_referer( 'pingpush_clear_log' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to do this.', 'pingpush' ), 403 );
}
PingPush_Logger::clear();
self::flash( __( 'Log cleared.', 'pingpush' ) );
wp_safe_redirect( self::url( 'log' ) );
exit;
}
/* ---------- Rendering ---------- */
/**
* Show a one-time admin message to the current user on the next PingPush page load.
* Used after redirects instead of a URL parameter.
*
* @param string $message Message.
* @param string $type success|error|warning|info.
*/
public static function flash( $message, $type = 'success' ) {
set_transient(
'pingpush_notice_' . get_current_user_id(),
array(
'message' => (string) $message,
'type' => (string) $type,
),
MINUTE_IN_SECONDS * 5
);
}
/**
* Print and clear the current user's one-time message, if any.
*/
private function render_flash() {
$key = 'pingpush_notice_' . get_current_user_id();
$notice = get_transient( $key );
if ( ! is_array( $notice ) || empty( $notice['message'] ) ) {
return;
}
delete_transient( $key );
$type = in_array( $notice['type'] ?? '', array( 'success', 'error', 'warning', 'info' ), true ) ? $notice['type'] : 'success';
echo '' . esc_html( $notice['message'] ) . '
';
}
/**
* Settings page.
*/
public function render_settings_page() {
$this->render( 'settings' );
}
/**
* Log page.
*/
public function render_log_page() {
$this->render( 'log' );
}
/**
* Page wrapper.
*
* @param string $tab settings|log.
*/
private function render( $tab ) {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
render_flash(); ?>
render_log();
} else {
$this->render_settings();
}
$this->render_footer();
?>
>
-
pushover.net'
);
?>
-
pushover.net/apps/build'
);
?>
priority_select( $base . '[priority]', (string) $cur['priority'], true, $dom . '-priority' ); ?>
sound_select( $base . '[sound]', (string) $cur['sound'], $dom . '-sound' ); ?>
$opt ) : ?>
render_option( $base, $dom, $key, $opt, $cur[ $key ] ?? ( $opt['default'] ?? '' ) ); ?>
{' . esc_html( $ph ) . '} ';
}
?>
' . esc_html( $opt['label'] ?? $key ) . '';
foreach ( $choices as $ck => $cl ) {
printf(
' ',
esc_attr( $base ),
esc_attr( $key ),
esc_attr( (string) $ck ),
checked( in_array( (string) $ck, array_map( 'strval', $value ), true ), true, false ),
esc_html( $cl )
);
}
echo '
';
return;
}
echo '';
if ( 'select' === $type ) {
echo '';
} else {
echo '';
}
echo '
';
}
/**
* Priority select.
*
* @param string $name Name.
* @param string $value Value.
* @param bool $with_default Include "Use default".
* @param string $id DOM id.
*/
private function priority_select( $name, $value, $with_default, $id = '' ) {
echo '';
}
/**
* Sound select.
*
* @param string $name Name.
* @param string $value Value.
* @param string $id DOM id.
*/
private function sound_select( $name, $value, $id = '' ) {
echo '';
}
/**
* Custom hooks section.
*
* @param array $s Settings.
*/
private function render_custom_hooks( array $s ) {
$rows = is_array( $s['custom_hooks'] ) ? $s['custom_hooks'] : array();
?>
$row ) {
$this->render_hook_row( (string) $i, $row );
}
?>
render_hook_row(
'__i__',
array(
'hook' => '',
'enabled' => 1,
'title' => '',
'message' => '',
'priority' => 0,
'sound' => '',
)
);
?>
__( 'Sent', 'pingpush' ),
'failed' => __( 'Failed', 'pingpush' ),
'retrying' => __( 'Retrying', 'pingpush' ),
);
if ( null !== $quota && false !== $quota ) {
/* translators: %s: number of messages. */
echo '' . esc_html( sprintf( __( 'Pushover messages left this month: %s', 'pingpush' ), number_format_i18n( (int) $quota ) ) ) . '
';
}
if ( empty( self::get()['log_enabled'] ) ) {
echo '' . esc_html__( 'Logging is turned off in Settings.', 'pingpush' ) . '
';
}
?>
|
|
|
|
|
|
event );
$event = $def ? $def['label'] : ( 'test' === $row->event ? __( 'Test notification', 'pingpush' ) : $row->event );
$time = get_date_from_gmt( $row->created_at, get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) );
?>
|
|
title ); ?> |
status ] ?? $row->status ); ?> |
code ? 'HTTP ' . $row->code . ' ' : '' ) . (string) $row->detail ) ); ?> |