Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
36.67% covered (danger)
36.67%
11 / 30
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Data_Loader
36.67% covered (danger)
36.67%
11 / 30
50.00% covered (danger)
50.00%
2 / 4
48.58
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_data_dir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_implemented_county
42.86% covered (danger)
42.86%
3 / 7
0.00% covered (danger)
0.00%
0 / 1
4.68
 get_data_for_country
28.57% covered (danger)
28.57%
6 / 21
0.00% covered (danger)
0.00%
0 / 1
24.86
1<?php
2/**
3 * Data for countries should be loaded from file and stored in wp_options.
4 *
5 * Country codes are conventionally uppercase except when used in filenames.
6 *
7 * @package brianhenryie/bh-wc-postcode-address-autofill
8 */
9
10namespace BrianHenryIE\WC_Postcode_Address_Autofill\API;
11
12use BrianHenryIE\WC_Postcode_Address_Autofill\Settings_Interface;
13use Throwable;
14
15/**
16 * Reads .json from plugin WP_PLUGIN_DIR filepath /data and returns `Country_Data` object.
17 */
18class Data_Loader {
19
20    /**
21     * Used for the plugin basename which is used to determine the plugin folder.
22     */
23    protected Settings_Interface $settings;
24
25    /**
26     * Constructor.
27     *
28     * @param Settings_Interface $settings The plugin settings.
29     */
30    public function __construct( Settings_Interface $settings ) {
31        $this->settings = $settings;
32    }
33
34    /**
35     * Get the filesystem path to the data directory, optionally appending filename.
36     *
37     * @param string $appending Optional path to append to data directory.
38     *
39     * @return string
40     */
41    protected function get_data_dir( string $appending = '' ): string {
42        return constant( 'WP_PLUGIN_DIR' ) . '/' . plugin_dir_path( $this->settings->get_plugin_basename() ) . 'data/' . $appending;
43    }
44
45    /**
46     * Determine is a postcode lookup list available for a specific country.
47     *
48     * Checks the `available-countries.php` file which is autogenerated by `data-parser.php` build tool and
49     * returns an array of countries which `.json` datasets are available in the `/data/` directory.
50     *
51     * @param string $country Two -character country code (ISO 3166-1 alpha-2).
52     */
53    protected function is_implemented_county( string $country ): bool {
54        $filename = $this->get_data_dir( 'available-countries.php' );
55        if ( ! is_readable( $filename ) ) {
56            return false;
57        }
58        $available_countries = include $filename;
59        if ( ! is_array( $available_countries ) ) {
60            return false;
61        }
62        return in_array( strtoupper( $country ), $available_countries, true );
63    }
64
65    /**
66     * Load the data for a requested country from memcache/db/disk.
67     *
68     * We cache to save the time loading from disk and parsing from JSON to an object.
69     *
70     * @param string $country Two-character country code (ISO 3166-1 alpha-2).
71     *
72     * @return ?Country_Data
73     */
74    public function get_data_for_country( string $country ): ?Country_Data {
75
76        $cached_value = wp_cache_get( $country, 'bh-wc-postcode-address-autofill' );
77        if ( $cached_value instanceof Country_Data ) {
78            return $cached_value;
79        }
80        if ( false !== $cached_value ) {
81            wp_cache_delete( $country, 'bh-wc-postcode-address-autofill' );
82        }
83
84        if ( ! $this->is_implemented_county( $country ) ) {
85            return null;
86        }
87
88        $file_path = $this->get_data_dir( strtolower( $country ) . '.json' );
89        if ( ! is_readable( $file_path ) ) {
90            // "Country_Data for $country not readable at expected $filename"
91            return null;
92        }
93
94        try {
95            ob_start();
96            /**
97             * Exclusion reason: Filename is validated against allow-list in `available-countries.php` via `is_implemented_county()`.
98             * nosemgrep audit.php.lang.security.file.inclusion-arg
99             */
100            include $file_path;
101            $file_data = ob_get_clean();
102            if ( empty( $file_data ) ) {
103                return null;
104            }
105            $json_country_data = json_decode( $file_data, false, 512, JSON_THROW_ON_ERROR );
106        } catch ( Throwable $t ) {
107            return null;
108        }
109
110        $country_data = new Country_Data( $json_country_data );
111
112        wp_cache_set( $country, $country_data, 'bh-wc-postcode-address-autofill' );
113
114        return $country_data;
115    }
116}