*/ public static function sounds() { return array( '' => __( 'Device default', 'pingpush' ), 'pushover' => 'Pushover', 'bike' => 'Bike', 'bugle' => 'Bugle', 'cashregister' => 'Cash Register', 'classical' => 'Classical', 'cosmic' => 'Cosmic', 'falling' => 'Falling', 'gamelan' => 'Gamelan', 'incoming' => 'Incoming', 'intermission' => 'Intermission', 'magic' => 'Magic', 'mechanical' => 'Mechanical', 'pianobar' => 'Piano Bar', 'siren' => 'Siren', 'spacealarm' => 'Space Alarm', 'tugboat' => 'Tug Boat', 'alien' => 'Alien Alarm (long)', 'climb' => 'Climb (long)', 'persistent' => 'Persistent (long)', 'echo' => 'Pushover Echo (long)', 'updown' => 'Up Down (long)', 'vibrate' => __( 'Vibrate only', 'pingpush' ), 'none' => __( 'Silent', 'pingpush' ), ); } /** * The priorities PingPush supports. Settings, templates and send() all use this list. * * @return array */ public static function priorities() { return array( -2 => __( 'Lowest (no alert)', 'pingpush' ), -1 => __( 'Low (no sound)', 'pingpush' ), 0 => __( 'Normal', 'pingpush' ), 1 => __( 'High (bypasses quiet hours)', 'pingpush' ), ); } /** * Normalize a priority to one of the supported values (-2, -1, 0, 1); anything else becomes 0 (normal). * * @param mixed $priority Priority. * @return int */ public static function sanitize_priority( $priority ) { $priority = is_numeric( $priority ) ? (int) $priority : 0; return array_key_exists( $priority, self::priorities() ) ? $priority : 0; } /** * A Pushover token or user/group key: 30 chars, [A-Za-z0-9]. * * @param string $key Key. * @return bool */ public static function looks_like_key( $key ) { return (bool) preg_match( '/^[A-Za-z0-9]{30}$/', (string) $key ); } /** * Send a message. * * @param string $token Application API token. * @param string $user User or group key. * @param array $message title, message, url, url_title, priority (-2..1), sound, timestamp. Other keys are ignored. * @param int $timeout Request timeout in seconds. * @return array{ok:bool,retry:bool,code:int,error:string,remaining:?int} */ public static function send( $token, $user, array $message, $timeout = 15 ) { $body = array( 'token' => $token, 'user' => $user, 'message' => (string) ( $message['message'] ?? '' ), ); foreach ( array( 'title', 'url', 'url_title' ) as $field ) { if ( isset( $message[ $field ] ) && '' !== $message[ $field ] ) { $body[ $field ] = (string) $message[ $field ]; } } if ( isset( $message['sound'] ) && '' !== $message['sound'] && array_key_exists( (string) $message['sound'], self::sounds() ) ) { $body['sound'] = (string) $message['sound']; } if ( isset( $message['priority'] ) ) { $body['priority'] = self::sanitize_priority( $message['priority'] ); } if ( ! empty( $message['timestamp'] ) ) { $body['timestamp'] = (int) $message['timestamp']; } if ( '' === trim( $body['message'] ) ) { $body['message'] = '-'; } $response = wp_remote_post( self::MESSAGES_URL, array( 'timeout' => $timeout, 'body' => $body, 'user-agent' => 'PingPush/' . PINGPUSH_VERSION . '; ' . home_url( '/' ), ) ); return self::parse( $response ); } /** * Validate a user/group key against an app token. * * @param string $token Token. * @param string $user User key. * @return array{ok:bool,retry:bool,code:int,error:string,remaining:?int,devices:array} */ public static function validate( $token, $user ) { $response = wp_remote_post( self::VALIDATE_URL, array( 'timeout' => 15, 'body' => array( 'token' => $token, 'user' => $user, ), 'user-agent' => 'PingPush/' . PINGPUSH_VERSION . '; ' . home_url( '/' ), ) ); $result = self::parse( $response ); $result['devices'] = array(); if ( $result['ok'] && ! is_wp_error( $response ) ) { $data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( isset( $data['devices'] ) && is_array( $data['devices'] ) ) { $result['devices'] = array_map( 'strval', $data['devices'] ); } } return $result; } /** * Normalize an HTTP response. * * Per Pushover: 200 + status 1 = ok; 4xx = bad input, never retry; 5xx / no response = retry later. * * @param array|WP_Error $response Response. * @return array{ok:bool,retry:bool,code:int,error:string,remaining:?int} */ private static function parse( $response ) { if ( is_wp_error( $response ) ) { return array( 'ok' => false, 'retry' => true, 'code' => 0, 'error' => $response->get_error_message(), 'remaining' => null, ); } $code = (int) wp_remote_retrieve_response_code( $response ); $data = json_decode( wp_remote_retrieve_body( $response ), true ); $remaining = wp_remote_retrieve_header( $response, 'x-limit-app-remaining' ); $remaining = ( '' === $remaining || is_array( $remaining ) ) ? null : (int) $remaining; if ( 200 === $code && is_array( $data ) && isset( $data['status'] ) && 1 === (int) $data['status'] ) { return array( 'ok' => true, 'retry' => false, 'code' => $code, 'error' => '', 'remaining' => $remaining, ); } $error = ''; if ( is_array( $data ) && ! empty( $data['errors'] ) && is_array( $data['errors'] ) ) { $error = implode( '; ', array_map( 'strval', $data['errors'] ) ); } if ( 429 === $code ) { $error = __( 'Monthly Pushover message limit reached.', 'pingpush' ) . ( $error ? ' ' . $error : '' ); } if ( '' === $error ) { /* translators: %d: HTTP status code. */ $error = sprintf( __( 'Unexpected response from Pushover (HTTP %d).', 'pingpush' ), $code ); } return array( 'ok' => false, 'retry' => ( $code < 400 || $code >= 500 ), 'code' => $code, 'error' => $error, 'remaining' => $remaining, ); } }