time(), 'source' => isset( $payload['source'] ) ? Connection::truncate( (string) $payload['source'], 64 ) : '', 'source_form' => isset( $payload['source_form'] ) ? Connection::truncate( (string) $payload['source_form'], 200 ) : '', 'form' => isset( $payload['form'] ) ? Connection::truncate( (string) $payload['form'], 40 ) : '', 'outcome' => in_array( $outcome, self::OUTCOMES, true ) ? $outcome : self::FAILED, 'reason' => substr( (string) preg_replace( '/[^a-z0-9_]/', '', strtolower( (string) $reason ) ), 0, 64 ), 'status' => (int) $status, 'retried' => (bool) $retried, ); $entries = self::all(); array_unshift( $entries, $entry ); update_option( self::OPTION, array_slice( $entries, 0, self::LIMIT ), false ); } /** * Every entry, newest first. * * @return array */ public static function all() { $entries = get_option( self::OPTION, array() ); if ( ! is_array( $entries ) ) { return array(); } return array_values( array_filter( $entries, static function ( $entry ) { return is_array( $entry ) && isset( $entry['time'], $entry['outcome'] ); } ) ); } /** * What happened, in a sentence for the founder, in the past tense. * * @param array $entry An entry. * @return string */ public static function message( array $entry ) { $outcome = isset( $entry['outcome'] ) ? $entry['outcome'] : ''; if ( self::SENT === $outcome ) { return empty( $entry['retried'] ) ? __( 'Received by LaunchList.', 'launchlist' ) : __( 'Received by LaunchList on a later try.', 'launchlist' ); } if ( self::QUEUED === $outcome ) { return self::kept( isset( $entry['reason'] ) ? $entry['reason'] : '', isset( $entry['status'] ) ? (int) $entry['status'] : 0 ); } $status = self::status( $entry ); /* translators: 1: what happened, such as "Not added", 2: why. */ return sprintf( __( '%1$s: %2$s.', 'launchlist' ), $status['label'], self::reason( $entry ) ); } /** * A short status for the deliveries list, and its tone: `ok`, `wait`, `no` or `off`. * * @param array $entry An entry. * @return array{tone: string, label: string} */ public static function status( array $entry ) { $outcome = isset( $entry['outcome'] ) ? $entry['outcome'] : ''; switch ( $outcome ) { case self::SENT: return array( 'tone' => 'ok', 'label' => __( 'Sent', 'launchlist' ), ); case self::QUEUED: return array( 'tone' => 'wait', 'label' => __( 'Kept', 'launchlist' ), ); case self::DELETED: return array( 'tone' => 'off', 'label' => __( 'Deleted', 'launchlist' ), ); case self::REFUSED: if ( ! self::never_left( $entry ) ) { return array( 'tone' => 'no', 'label' => __( 'Not added', 'launchlist' ), ); } } return array( 'tone' => 'no', 'label' => __( 'Not sent', 'launchlist' ), ); } /** * Why a submission was not added, never left this site, or was deleted, as a clause to follow * its status. Empty for one that was sent or kept. * * @param array $entry An entry. * @return string */ public static function reason( array $entry ) { $outcome = isset( $entry['outcome'] ) ? $entry['outcome'] : ''; $reason = isset( $entry['reason'] ) ? $entry['reason'] : ''; $status = isset( $entry['status'] ) ? (int) $entry['status'] : 0; switch ( $outcome ) { case self::SENT: case self::QUEUED: return ''; case self::REFUSED: return self::refusal( $reason, $status ); case self::SKIPPED: return __( 'the submission had no filled-in fields', 'launchlist' ); case self::DELETED: return __( 'this site was disconnected from LaunchList', 'launchlist' ); } switch ( $reason ) { case 'expired': return __( 'this site was not connected to LaunchList for 24 hours after it was submitted', 'launchlist' ); case 'storage': return __( 'LaunchList could not take it, and this site could not keep it to try again', 'launchlist' ); case 'queue_full': return __( 'LaunchList could not take it, and this site was already keeping as many signups to retry as it allows', 'launchlist' ); } return __( 'LaunchList did not take it in 24 hours of tries', 'launchlist' ); } /** * Whether a refused submission never left this site. * * @param array $entry An entry. * @return bool */ private static function never_left( array $entry ) { $reason = isset( $entry['reason'] ) ? $entry['reason'] : ''; if ( in_array( $reason, array( 'not_connected', 'invalid_origin' ), true ) ) { return true; } return ! in_array( $reason, self::SERVER_REASONS, true ) && 0 === ( isset( $entry['status'] ) ? (int) $entry['status'] : 0 ); } /** * Why a submission was kept to send again. * * @param string $reason The reason. * @param int $status HTTP status. * @return string */ private static function kept( $reason, $status ) { switch ( $reason ) { case 'revoked': return __( 'This site was no longer connected to LaunchList, so it was kept for 24 hours in case you connect again.', 'launchlist' ); case 'held': return __( 'LaunchList could not take signups a moment earlier, so this site kept it to try again.', 'launchlist' ); case 'in_progress': return __( 'LaunchList was still handling this signup, so this site kept it to check again.', 'launchlist' ); case 'rate_limited': return __( 'LaunchList asked this site to slow down, so this site kept it to try again.', 'launchlist' ); case 'paused': return __( 'LaunchList was not taking signups for this form, so this site kept it to try again.', 'launchlist' ); case 'stale': return __( 'LaunchList refused it because this server\'s clock was off, so this site kept it to try again.', 'launchlist' ); case 'network': return __( 'LaunchList could not be reached, so this site kept it to try again.', 'launchlist' ); } if ( $status >= 500 ) { /* translators: %d: HTTP status code. */ return sprintf( __( 'LaunchList had an error (HTTP %d), so this site kept it to try again.', 'launchlist' ), $status ); } if ( $status > 0 ) { /* translators: %d: HTTP status code. */ return sprintf( __( 'An answer that did not come from LaunchList arrived (HTTP %d), so this site kept it to try again.', 'launchlist' ), $status ); } return __( 'LaunchList could not be reached, so this site kept it to try again.', 'launchlist' ); } /** * Why LaunchList did not take a submission, or why it never left this site, as a clause. * * @param string $reason The reason. * @param int $status HTTP status. * @return string */ private static function refusal( $reason, $status ) { switch ( $reason ) { case 'unknown_site': case 'invalid_signature': return __( 'LaunchList no longer recognized this site', 'launchlist' ); case 'unsigned': return __( 'LaunchList could not verify this site\'s request', 'launchlist' ); case 'malformed': return __( 'LaunchList could not read this submission', 'launchlist' ); case 'closed': return __( 'this LaunchList form is closed', 'launchlist' ); case 'unknown_form': return __( 'LaunchList does not have this form', 'launchlist' ); case 'validation': return __( 'a field LaunchList needs was missing or not valid', 'launchlist' ); case 'rejected': return __( 'LaunchList treated this signup as spam', 'launchlist' ); case 'not_connected': return __( 'this site was not connected to LaunchList', 'launchlist' ); case 'invalid_origin': return sprintf( /* translators: %s: the LAUNCHLIST_ORIGIN constant. */ __( 'the LaunchList address in %s is not a valid http or https address', 'launchlist' ), 'LAUNCHLIST_ORIGIN' ); } // Never left this site, so LaunchList refused nothing. if ( 0 === (int) $status ) { return __( 'this site could not prepare the submission', 'launchlist' ); } /* translators: %d: HTTP status code. */ return sprintf( __( 'LaunchList refused it (HTTP %d)', 'launchlist' ), $status ); } }