Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
30.51% covered (danger)
30.51%
18 / 59
25.00% covered (danger)
25.00%
2 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Account
31.58% covered (danger)
31.58%
18 / 57
25.00% covered (danger)
25.00%
2 / 8
318.28
0.00% covered (danger)
0.00%
0 / 1
 get
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 save
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 update
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 delete
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 to_public
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 relative_host
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
4.03
 from_provision
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
72
 clean_records
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2/**
3 * The site's provisioned MailChannels sub-account.
4 *
5 * @package automattic/jetpack-send
6 */
7
8namespace Automattic\Jetpack\Send;
9
10use WP_Error;
11
12if ( ! defined( 'ABSPATH' ) ) {
13    exit( 0 );
14}
15
16/**
17 * Reads and writes the `jetpack_send_account` option.
18 */
19class Account {
20
21    /**
22     * Option name.
23     *
24     * @var string
25     */
26    const OPTION = 'jetpack_send_account';
27
28    /**
29     * The stored account, or null when the site isn't set up.
30     *
31     * @return array|null
32     */
33    public static function get() {
34        $account = get_option( self::OPTION );
35        return is_array( $account ) && ! empty( $account['api_key'] ) ? $account : null;
36    }
37
38    /**
39     * Replace the stored account.
40     *
41     * @param array $account Account.
42     * @return void
43     */
44    public static function save( array $account ) {
45        update_option( self::OPTION, $account, false );
46    }
47
48    /**
49     * Merge changes into the stored account, if there is one.
50     *
51     * @param array $changes Keys to change.
52     * @return void
53     */
54    public static function update( array $changes ) {
55        $account = self::get();
56        if ( $account ) {
57            self::save( array_merge( $account, $changes ) );
58        }
59    }
60
61    /**
62     * Delete the option.
63     *
64     * @return void
65     */
66    public static function delete() {
67        delete_option( self::OPTION );
68    }
69
70    /**
71     * The account without its API key, for REST responses.
72     *
73     * @param array|null $account Account.
74     * @return array|null
75     */
76    public static function to_public( $account ) {
77        if ( ! is_array( $account ) ) {
78            return null;
79        }
80        unset( $account['api_key'] );
81
82        $zone = (string) ( $account['dns_host']['apex'] ?? '' );
83        if ( '' === $zone ) {
84            $zone = (string) ( $account['domain'] ?? '' );
85        }
86        foreach ( (array) ( $account['dns_records'] ?? array() ) as $i => $record ) {
87            $account['dns_records'][ $i ]['host'] = self::relative_host( (string) ( $record['name'] ?? '' ), $zone );
88        }
89        return $account;
90    }
91
92    /**
93     * A record name relative to its DNS zone, as most providers' host fields expect.
94     *
95     * @param string $name Full record name.
96     * @param string $zone Zone root, e.g. `example.com`.
97     * @return string The short host, `@` for the zone root, or the name unchanged when outside the zone.
98     */
99    public static function relative_host( $name, $zone ) {
100        $name = strtolower( rtrim( $name, '.' ) );
101        $zone = strtolower( rtrim( $zone, '.' ) );
102        if ( '' === $zone ) {
103            return $name;
104        }
105        if ( $name === $zone ) {
106            return '@';
107        }
108        $suffix = '.' . $zone;
109        return substr( $name, -strlen( $suffix ) ) === $suffix ? substr( $name, 0, -strlen( $suffix ) ) : $name;
110    }
111
112    /**
113     * Validate a provisioner result and shape it into an account.
114     *
115     * @param array  $result   Provisioner::provision() result.
116     * @param string $domain   Sending domain.
117     * @param string $provider Provisioner type.
118     * @return array|WP_Error
119     */
120    public static function from_provision( array $result, $domain, $provider ) {
121        foreach ( array( 'handle', 'api_key', 'lockdown_auth_id' ) as $key ) {
122            if ( empty( $result[ $key ] ) || ! is_string( $result[ $key ] ) ) {
123                return new WP_Error( 'jetpack_send_bad_provision', __( 'The sending account could not be created. Please try again.', 'jetpack-send-pkg' ), array( 'status' => 502 ) );
124            }
125        }
126
127        $records       = self::clean_records( $result['dns_records'] ?? array() );
128        $dkim_selector = '';
129        foreach ( $records as $record ) {
130            if ( 'dkim' === $record['purpose'] && false !== strpos( $record['name'], '._domainkey.' ) ) {
131                $dkim_selector = (string) strstr( $record['name'], '._domainkey.', true );
132            }
133        }
134
135        return array(
136            'handle'           => $result['handle'],
137            'api_key'          => $result['api_key'],
138            'lockdown_auth_id' => $result['lockdown_auth_id'],
139            'domain'           => $domain,
140            'dkim_selector'    => $dkim_selector,
141            'monthly_limit'    => isset( $result['monthly_limit'] ) ? (int) $result['monthly_limit'] : -1,
142            'dns_records'      => $records,
143            'zone_records'     => self::clean_records( $result['zone_records'] ?? array() ),
144            'provider'         => $provider,
145            'provisioned_at'   => gmdate( 'c' ),
146            'dns_check'        => null,
147        );
148    }
149
150    /**
151     * Keep well-formed DNS records, cast to strings.
152     *
153     * @param mixed $records Records from a provisioner.
154     * @return array[] `{type, name, value, purpose, optional}`.
155     */
156    private static function clean_records( $records ) {
157        $clean = array();
158        foreach ( (array) $records as $record ) {
159            if ( ! is_array( $record ) || ! isset( $record['type'] ) || ! isset( $record['name'] ) || ! isset( $record['value'] ) || ! isset( $record['purpose'] ) ) {
160                continue;
161            }
162            $clean[] = array(
163                'type'     => (string) $record['type'],
164                'name'     => (string) $record['name'],
165                'value'    => (string) $record['value'],
166                'purpose'  => (string) $record['purpose'],
167                'optional' => ! empty( $record['optional'] ),
168            );
169        }
170        return $clean;
171    }
172}