├── templates
└── Symbiote
│ └── Addressable
│ ├── Address.ss
│ └── AddressMap.ss
├── CHANGELOG.md
├── .upgrade.yml
├── .gitattributes
├── _config.php
├── _config
└── config.yml
├── .editorconfig
├── src
├── GeocodeServiceInterface.php
├── GeocodeServiceException.php
├── Forms
│ └── RegexTextField.php
├── MapboxGeocodeService.php
├── GoogleGeocodeService.php
├── Geocodable.php
└── Addressable.php
├── phpunit.xml.dist
├── composer.json
├── phpcs.xml.dist
└── .scrutinizer.yml
/templates/Symbiote/Addressable/Address.ss:
--------------------------------------------------------------------------------
1 |
2 | $Address
3 | $Suburb
4 | $State $Postcode
5 | $CountryName
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | For more information on changes and releases, please visit [Releases](https://github.com/symbiote/silverstripe-addressable/releases).
4 |
5 | This project adheres to [Semantic Versioning](http://semver.org/).
6 |
--------------------------------------------------------------------------------
/.upgrade.yml:
--------------------------------------------------------------------------------
1 | mappings:
2 | GoogleGeocoding: Symbiote\Addressable\GeocodeService
3 | Addressable: Symbiote\Addressable\Addressable
4 | Geocodable: Symbiote\Addressable\Geocodable
5 | RegexTextField: Symbiote\Addressable\Forms\RegexTextField
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /tests export-ignore
2 | /docs export-ignore
3 | /.travis.yml export-ignore
4 | /.phpunit.xml.dist export-ignore
5 | /.phpcs.xml.dist export-ignore
6 | README.md export-ignore
7 | LICENSE.md export-ignore
8 | CONTRIBUTING.md export-ignore
9 |
--------------------------------------------------------------------------------
/_config.php:
--------------------------------------------------------------------------------
1 |
2 |
7 | <% else %>
8 |
9 |
/{$Lng},{$Lat},15/{$Width}x{$Height}?access_token={$Key})
10 |
11 | <% end_if %>
12 | $Gugus
13 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | .
7 |
8 |
9 | tests/
10 |
11 |
12 |
13 |
14 | tests
15 |
16 |
17 |
18 |
19 | sanitychecks
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/GeocodeServiceException.php:
--------------------------------------------------------------------------------
1 | responseBody = $responseBody;
28 |
29 | $xml = new SimpleXMLElement($responseBody);
30 | if (isset($xml->status)) {
31 | $this->status = (string)$xml->status;
32 | }
33 | }
34 |
35 | public function getResponse()
36 | {
37 | return $this->responseBody;
38 | }
39 |
40 | /**
41 | * Return "status" values:
42 | * - ZERO_RESULTS
43 | * - OVER_QUERY_LIMIT
44 | *
45 | * @return string
46 | */
47 | public function getStatus()
48 | {
49 | return $this->status;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Forms/RegexTextField.php:
--------------------------------------------------------------------------------
1 | regex;
28 | }
29 |
30 | /**
31 | * @param string $regex
32 | */
33 | public function setRegex($regex)
34 | {
35 | $this->regex = $regex;
36 | }
37 |
38 | public function validate($validator)
39 | {
40 | if ($this->value && $this->regex) {
41 | if (!preg_match($this->regex, (string) $this->value)) {
42 | $name = $this->Title() ?: $this->name;
43 | $message = _t('RegexTextField.VALIDATE', 'Please enter a valid format for "%s".');
44 | $validator->validationError($this->name, sprintf($message, $name), 'validation');
45 | return false;
46 | }
47 | }
48 |
49 | return true;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "symbiote/silverstripe-addressable",
3 | "description": "SilverStripe addressable and geocoding module",
4 | "type": "silverstripe-vendormodule",
5 | "keywords": [
6 | "silverstripe",
7 | "symbiote",
8 | "addressable",
9 | "geocoding"
10 | ],
11 | "license": "BSD-3-Clause",
12 | "authors": [
13 | {
14 | "name": "Symbiote",
15 | "homepage": "https://www.symbiote.com.au/"
16 | },
17 | {
18 | "name": "Marcus Nyeholt",
19 | "email": "nyeholt@symbiote.com.au"
20 | }
21 | ],
22 | "require": {
23 | "silverstripe/framework": "^4.12||^5",
24 | "guzzlehttp/guzzle": "^7"
25 | },
26 | "require-dev": {
27 | "squizlabs/php_codesniffer": "^3.5",
28 | "phpunit/phpunit": "^9.6"
29 | },
30 | "scripts": {
31 | "phpcbf": "phpcbf src/ src/ tests/",
32 | "phpcs": "phpcs src/ src/ tests/"
33 | },
34 | "autoload": {
35 | "psr-4": {
36 | "Symbiote\\Addressable\\": "src/",
37 | "Symbiote\\Addressable\\Tests\\": "tests/"
38 | }
39 | },
40 | "extra": {
41 | "branch-alias": {
42 | "dev-master": "5.2.x-dev"
43 | }
44 | },
45 | "replace": {
46 | "ajshort/silverstripe-addressable": "self.version",
47 | "silverstripe-australia/addressable": "self.version"
48 | },
49 | "prefer-stable": true,
50 | "minimum-stability": "dev"
51 | }
52 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 | CodeSniffer ruleset for SilverStripe coding conventions.
4 |
5 |
6 | */vendor/*
7 | */thirdparty/*
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | */SSTemplateParser.php$
43 | */_fakewebroot/*
44 | */fixtures/*
45 |
46 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | inherit: true
2 |
3 | checks:
4 | php:
5 | verify_property_names: true
6 | verify_argument_usable_as_reference: true
7 | verify_access_scope_valid: true
8 | useless_calls: true
9 | use_statement_alias_conflict: true
10 | variable_existence: true
11 | unused_variables: true
12 | unused_properties: true
13 | unused_parameters: true
14 | unused_methods: true
15 | unreachable_code: true
16 | too_many_arguments: true
17 | sql_injection_vulnerabilities: true
18 | simplify_boolean_return: true
19 | side_effects_or_types: true
20 | security_vulnerabilities: true
21 | return_doc_comments: true
22 | return_doc_comment_if_not_inferrable: true
23 | require_scope_for_properties: true
24 | require_scope_for_methods: true
25 | require_php_tag_first: true
26 | psr2_switch_declaration: true
27 | psr2_class_declaration: true
28 | property_assignments: true
29 | prefer_while_loop_over_for_loop: true
30 | precedence_mistakes: true
31 | precedence_in_conditions: true
32 | phpunit_assertions: true
33 | php5_style_constructor: true
34 | parse_doc_comments: true
35 | parameter_non_unique: true
36 | parameter_doc_comments: true
37 | param_doc_comment_if_not_inferrable: true
38 | optional_parameters_at_the_end: true
39 | one_class_per_file: true
40 | no_unnecessary_if: true
41 | no_trailing_whitespace: true
42 | no_property_on_interface: true
43 | no_non_implemented_abstract_methods: true
44 | no_error_suppression: true
45 | no_duplicate_arguments: true
46 | no_commented_out_code: true
47 | newline_at_end_of_file: true
48 | missing_arguments: true
49 | method_calls_on_non_object: true
50 | instanceof_class_exists: true
51 | foreach_traversable: true
52 | fix_line_ending: true
53 | fix_doc_comments: true
54 | duplication: true
55 | deprecated_code_usage: true
56 | deadlock_detection_in_loops: true
57 | code_rating: true
58 | closure_use_not_conflicting: true
59 | catch_class_exists: true
60 | blank_line_after_namespace_declaration: false
61 | avoid_multiple_statements_on_same_line: true
62 | avoid_duplicate_types: true
63 | avoid_conflicting_incrementers: true
64 | avoid_closing_tag: true
65 | assignment_of_null_return: true
66 | argument_type_checks: true
67 |
68 | filter:
69 | paths: [code/*, tests/*]
70 |
--------------------------------------------------------------------------------
/src/MapboxGeocodeService.php:
--------------------------------------------------------------------------------
1 | get(self::class, 'mapbox_api_url');
40 | $key = Config::inst()->get(self::class, 'mapbox_api_key');
41 |
42 | if (!$url) {
43 | // If no URL configured. Stop.
44 | throw new Exception('No mapbox_api_url configured. This is not allowed.');
45 | }
46 |
47 | if (!$key) {
48 | // If no KEY configured. Stop.
49 | throw new Exception('No mapbox_api_key configured. This is not allowed.');
50 | }
51 |
52 | // Add params
53 | $queryVars = [
54 | 'access_token' => $key,
55 | 'type' => 'address',
56 | ];
57 | if ($region) {
58 | $queryVars['country'] = $region;
59 | }
60 | $url .= urlencode($address) . '.json?' . http_build_query($queryVars);
61 |
62 | $client = new Client();
63 | $response = $client->get($url);
64 | if (!$response) {
65 | throw new GeocodeServiceException('No response.', 0, '');
66 | }
67 | $statusCode = $response->getStatusCode();
68 | if ($statusCode !== 200) {
69 | throw new GeocodeServiceException('Unexpected status code:' . $statusCode, $statusCode, '');
70 | }
71 | $responseBody = json_decode((string)$response->getBody());
72 |
73 | // Error handling
74 | if ($responseBody) {
75 | if (isset($responseBody->message)) {
76 | throw new GeocodeServiceException('Error message: ' . $responseBody->message, $statusCode, $responseBody);
77 | }
78 | if (!isset($responseBody->features) || !count($responseBody->features) === 0) {
79 | throw new GeocodeServiceException('Zero results returned. Invalid status from response: ' . $status, $statusCode, $responseBody);
80 | }
81 | } else {
82 | // Fallback to full string dump
83 | $text = trim($response->getBody());
84 | throw new GeocodeServiceException('Invalid response: ' . $text, $statusCode, $responseBody);
85 | }
86 |
87 | // We take the first match, because that's most likely the best match
88 | $feature = reset($responseBody->features);
89 | [$lng, $lat] = $feature->geometry->coordinates;
90 |
91 | return [
92 | 'lat' => (float)$lat,
93 | 'lng' => (float)$lng
94 | ];
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/GoogleGeocodeService.php:
--------------------------------------------------------------------------------
1 | get(self::class, 'google_api_key');
44 | if (!$key) {
45 | $key = Config::inst()->get('Symbiote\\Addressable\\GeocodeService', 'google_api_key');
46 | }
47 | }
48 | if (!$key) {
49 | // Google Geocode API requires a key
50 | throw new \Exception('No google_api_key configured. This is not allowed.');
51 | }
52 | return $key;
53 | }
54 |
55 | /**
56 | * Retrieve the Google Geocoding API URL from config API
57 | * @throws \Exception
58 | */
59 | public function getApiUrl() : string
60 | {
61 | // Get the URL for the Google API (and check for legacy config)
62 | $url = Config::inst()->get(self::class, 'google_api_url');
63 | if (!$url) {
64 | $url = Config::inst()->get('Symbiote\\Addressable\\GeocodeService', 'google_api_url');
65 | }
66 |
67 | if (!$url) {
68 | // If no URL configured. Stop.
69 | throw new \Exception('No google_api_url configured. This is not allowed.');
70 | }
71 |
72 | return $url;
73 | }
74 |
75 | /**
76 | * Convert an address into a latitude and longitude.
77 | *
78 | * @param string $address The address to geocode.
79 | * @param string $region An optional two letter region code.
80 | * @return array An associative array with lat and lng keys.
81 | */
82 | public function addressToPoint($address, $region = '')
83 | {
84 | $url = $this->getApiUrl();
85 |
86 | $key = $this->getApiKey();
87 |
88 | // Add params
89 | $queryVars = [
90 | 'address' => $address,
91 | 'sensor' => 'false',
92 | ];
93 | if ($region) {
94 | $queryVars['region'] = $region;
95 | }
96 | if ($key) {
97 | $queryVars['key'] = $key;
98 | }
99 | $url .= '?' . http_build_query($queryVars);
100 |
101 | $client = new Client();
102 | $response = $client->get($url);
103 | if (!$response) {
104 | throw new GeocodeServiceException('No response.', 0, '');
105 | }
106 | $statusCode = $response->getStatusCode();
107 | if ($statusCode !== 200) {
108 | throw new GeocodeServiceException('Unexpected status code:' . $statusCode, $statusCode, '');
109 | }
110 | $responseBody = (string)$response->getBody();
111 | $xml = new SimpleXMLElement($responseBody);
112 | if (!isset($xml->result)) {
113 | // Error handling
114 | if (isset($xml->status)) {
115 | $status = (string)$xml->status;
116 | if ($status === self::ERROR_ZERO_RESULTS) {
117 | throw new GeocodeServiceException('Zero results returned. Invalid status from response: ' . $status, $statusCode, $responseBody);
118 | } else {
119 | throw new GeocodeServiceException('Unhandled status from response: ' . $status, $statusCode, $responseBody);
120 | }
121 | }
122 | // Fallback to full string dump
123 | $text = trim($response->getBody());
124 | throw new GeocodeServiceException('Invalid response: ' . $text, $responseBody);
125 | }
126 | $location = $xml->result->geometry->location;
127 | return [
128 | 'lat' => (float)$location->lat,
129 | 'lng' => (float)$location->lng
130 | ];
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/Geocodable.php:
--------------------------------------------------------------------------------
1 | 'Boolean',
44 | 'Lat' => 'Decimal(10,7)',
45 | 'Lng' => 'Decimal(10,7)'
46 | ];
47 |
48 | public function onBeforeWrite()
49 | {
50 | $record = $this->getOwner();
51 | // Reset last error
52 | $record->__geocodable_exception = null;
53 | if (!Config::inst()->get(self::class, 'is_geocodable')) {
54 | // Allow user-code to disable Geocodable. This was added
55 | // so that dev/tasks that write a *lot* of Geocodable records can
56 | // ignore this expensive logic.
57 | return;
58 | }
59 | if ($record->LatLngOverride) {
60 | // A CMS user disabled automatical retrieval of Lat/Lng
61 | // and most likely input their own values.
62 | return;
63 | }
64 | if ((!$record->hasMethod('isAddressChanged') || !$record->isAddressChanged())
65 | && $record->Lat
66 | && $record->Lng
67 | ) {
68 | // No address change, no geocoding
69 | return;
70 | }
71 |
72 | $address = $record->getFullAddress();
73 | $region = strtolower($record->Country);
74 |
75 | $point = [];
76 | try {
77 | $point = $this->geocoder->addressToPoint($address, $region);
78 | } catch (GeocodeServiceException $e) {
79 | // Default behaviour is to ignore errors like ZERO_RESULTS or this just failing.
80 | $record->__geocodable_exception = $e;
81 | return;
82 | }
83 | if (!$point) {
84 | return;
85 | }
86 |
87 | $record->Lat = $point['lat'];
88 | $record->Lng = $point['lng'];
89 | }
90 |
91 | /**
92 | * @return GeocodeServiceException|null
93 | */
94 | public function getLastGeocodableException()
95 | {
96 | return $this->owner->__geocodable_exception;
97 | }
98 |
99 | public function getGeocoder()
100 | {
101 | return $this->geocoder;
102 | }
103 |
104 | public function updateCMSFields(FieldList $fields)
105 | {
106 | $record = $this->getOwner();
107 | $fields->removeByName([
108 | 'LatLngOverride',
109 | 'Lat',
110 | 'Lng'
111 | ]);
112 |
113 | // Adds Lat/Lng fields for viewing in the CMS
114 | $compositeField = CompositeField::create();
115 | $compositeField->push($overrideField = CheckboxField::create('LatLngOverride', 'Override Latitude and Longitude?'));
116 | $overrideField->setDescription('Check this box and save to be able to edit the latitude and longitude manually.');
117 | if ($record->Lng && $record->Lat) {
118 | $googleMapURL = 'https://maps.google.com/?q=' . $record->Lat . ',' . $record->Lng;
119 | $googleMapDiv = '';
120 | $compositeField->push(LiteralField::create('MapURL_Readonly', $googleMapDiv));
121 | }
122 | if ($record->LatLngOverride) {
123 | $compositeField->push(TextField::create('Lat', 'Lat'));
124 | $compositeField->push(TextField::create('Lng', 'Lng'));
125 | } else {
126 | $compositeField->push(ReadonlyField::create('Lat_Readonly', 'Lat', $record->Lat));
127 | $compositeField->push(ReadonlyField::create('Lng_Readonly', 'Lng', $record->Lng));
128 | }
129 | if ($record->hasExtension(Addressable::class)) {
130 | // If using addressable, put the fields with it
131 | $fields->addFieldToTab('Root.Address', ToggleCompositeField::create('Coordinates', 'Coordinates', $compositeField));
132 | } elseif ($record instanceof SiteTree) {
133 | // If SIteTree but not using Addressable, put after 'Metadata' toggle composite field
134 | $fields->insertAfter('ExtraMeta', $compositeField);
135 | } else {
136 | $fields->addFieldToTab('Root.Main', ToggleCompositeField::create('Coordinates', 'Coordinates', $compositeField));
137 | }
138 | }
139 |
140 | public function updateFrontEndFields(FieldList $fields)
141 | {
142 | $fields->removeByName([
143 | 'LatLngOverride',
144 | 'Lat',
145 | 'Lng'
146 | ]);
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/Addressable.php:
--------------------------------------------------------------------------------
1 | 'Varchar(255)',
29 | 'Suburb' => 'Varchar(64)',
30 | 'State' => 'Varchar(64)',
31 | 'Postcode' => 'Varchar(10)',
32 | 'Country' => 'Varchar(2)'
33 | ];
34 |
35 | /**
36 | * Define an array of states that the user can select from.
37 | * If no states are defined, a user can type in any plain text for their state.
38 | * If only 1 state is defined, that will be the default populated value.
39 | *
40 | * @var array
41 | * @config
42 | */
43 | private static $allowed_states = [];
44 |
45 | /**
46 | * Define an array of countries that the user can select from.
47 | * If only 1 country is defined, that will be the default populated value.
48 | *
49 | * @var array
50 | * @config
51 | */
52 | private static $allowed_countries = [];
53 |
54 | /**
55 | * @var string
56 | * @config
57 | */
58 | private static $postcode_regex = '/^[0-9]+$/';
59 |
60 | public function __construct()
61 | {
62 | parent::__construct();
63 |
64 | // Throw exception for deprecated config
65 | if (Config::inst()->get('Addressable', 'set_postcode_regex') ||
66 | Config::inst()->get(self::class, 'set_postcode_regex')) {
67 | throw new Exception('Addressable config "set_postcode_regex" is deprecated in favour of using YML config "postcode_regex"');
68 | }
69 | }
70 |
71 | public function updateCMSFields(FieldList $fields)
72 | {
73 | if ($fields->hasTabSet()) {
74 | $fields->addFieldsToTab('Root.Address', $this->getAddressFields());
75 | } else {
76 | $newFields = $this->getAddressFields();
77 | foreach ($newFields as $field) {
78 | $fields->push($field);
79 | }
80 | }
81 | }
82 |
83 | public function updateFrontEndFields(FieldList $fields)
84 | {
85 | $fields->merge($this->getAddressFields());
86 | }
87 |
88 | public function populateDefaults()
89 | {
90 | $allowedStates = $this->owner->getAllowedStates();
91 | if (is_array($allowedStates) &&
92 | count($allowedStates) === 1) {
93 | $this->owner->State = array_key_first($allowedStates);
94 | }
95 |
96 | $allowedCountries = $this->owner->getAllowedCountries();
97 | if (is_array($allowedCountries) &&
98 | count($allowedCountries) === 1) {
99 | $this->owner->Country = array_key_first($allowedCountries);
100 | }
101 | }
102 |
103 | /**
104 | * @return bool
105 | */
106 | public function hasAddress()
107 | {
108 | return (
109 | $this->owner->Address
110 | && $this->owner->Suburb
111 | && $this->owner->State
112 | && $this->owner->Postcode
113 | && $this->owner->Country
114 | );
115 | }
116 |
117 | /**
118 | * Returns the full address as a simple string.
119 | *
120 | * @return string
121 | */
122 | public function getFullAddress()
123 | {
124 | $parts = [
125 | $this->owner->Address,
126 | $this->owner->Suburb,
127 | $this->owner->State,
128 | $this->owner->Postcode,
129 | $this->owner->getCountryName()
130 | ];
131 |
132 | return implode(', ', array_filter($parts));
133 | }
134 |
135 | /**
136 | * Returns the full address in a simple HTML template.
137 | *
138 | * @return DBHTMLText
139 | */
140 | public function getFullAddressHTML()
141 | {
142 | return $this->owner->renderWith('Symbiote/Addressable/Address');
143 | }
144 |
145 | /**
146 | * Returns a static google map of the address, linking out to the address.
147 | *
148 | * @param int $width (optional)
149 | * @param int $height (optional)
150 | * @param int $scale (optional)
151 | * @return DBHTMLText
152 | */
153 | public function AddressMap($width = 320, $height = 240, $scale = 1)
154 | {
155 | $data = [
156 | 'Type' => 'Google',
157 | 'Width' => $width,
158 | 'Height' => $height,
159 | 'Scale' => $scale,
160 | 'Address' => rawurlencode($this->getFullAddress()),
161 | 'Key' => Config::inst()->get(GoogleGeocodeService::class, 'google_api_key')
162 | ];
163 |
164 | if ($this->owner->hasExtension(Geocodable::class)) {
165 | $data['Lat'] = $this->owner->Lat;
166 | $data['Lng'] = $this->owner->Lng;
167 |
168 | if (is_a($this->owner->getGeocoder(), MapboxGeocodeService::class)) {
169 | $data['Type'] = 'Mapbox';
170 | $data['Key'] = Config::inst()->get(MapboxGeocodeService::class, 'mapbox_api_key');
171 | }
172 | }
173 |
174 | $object = $this->owner->customise($data);
175 | return $object->renderWith('Symbiote/Addressable/AddressMap');
176 | }
177 |
178 | /**
179 | * Returns the country name (not the 2 character code).
180 | *
181 | * @return string
182 | */
183 | public function getCountryName()
184 | {
185 | return IntlLocales::singleton()->countryName($this->owner->Country);
186 | }
187 |
188 | /**
189 | * Returns TRUE if any of the address fields have changed.
190 | *
191 | * @param int $level
192 | * @return bool
193 | */
194 | public function isAddressChanged($level = 1)
195 | {
196 | $fields = [
197 | 'Address',
198 | 'Suburb',
199 | 'State',
200 | 'Postcode',
201 | 'Country'
202 | ];
203 | $changed = $this->owner->getChangedFields(false, $level);
204 |
205 | foreach ($fields as $field) {
206 | if (array_key_exists($field, $changed)) {
207 | return true;
208 | }
209 | }
210 |
211 | return false;
212 | }
213 |
214 | /**
215 | * NOTE:
216 | *
217 | * This was made private as you should *probably* be using "updateAddressFields" to manipulate
218 | * these fields (if at all).
219 | *
220 | * If this doesn't end up being the case, feel free to make a PR and change this back to "public".
221 | *
222 | * @return array
223 | */
224 | private function getAddressFields($_params = [])
225 | {
226 | $params = array_merge(
227 | ['includeHeader' => true],
228 | (array) $_params
229 | );
230 |
231 | $fields = [
232 | TextField::create('Address', _t('Addressable.ADDRESS', 'Address')),
233 | TextField::create('Suburb', _t('Addressable.SUBURB', 'Suburb'))
234 | ];
235 |
236 | if ($params['includeHeader']) {
237 | array_unshift(
238 | $fields,
239 | HeaderField::create('AddressHeader', _t('Addressable.ADDRESSHEADER', 'Address'))
240 | );
241 | }
242 |
243 |
244 | // Get state field
245 | $label = _t('Addressable.STATE', 'State');
246 | $allowedStates = $this->owner->getAllowedStates();
247 | if (count($allowedStates) >= 1) {
248 | // If allowed states are restricted, only allow those
249 | $fields[] = DropdownField::create('State', $label, $allowedStates);
250 | } elseif (!$allowedStates) {
251 | // If no allowed states defined, allow the user to type anything
252 | $fields[] = TextField::create('State', $label);
253 | }
254 |
255 | // Get postcode field
256 | $postcode = RegexTextField::create('Postcode', _t('Addressable.POSTCODE', 'Postcode'));
257 | $postcode->setRegex($this->getPostcodeRegex());
258 | $fields[] = $postcode;
259 |
260 | // Get country field
261 | $fields[] = DropdownField::create(
262 | 'Country',
263 | _t('Addressable.COUNTRY', 'Country'),
264 | $this->owner->getAllowedCountries()
265 | );
266 |
267 | $this->owner->extend("updateAddressFields", $fields);
268 |
269 | return $fields;
270 | }
271 |
272 | /**
273 | * Get the allowed states for this object
274 | *
275 | * @return array
276 | */
277 | public function getAllowedStates()
278 | {
279 | // Get states from extending object. (ie. Page, DataObject)
280 | $allowedStates = $this->owner->config()->allowed_states;
281 | if (is_array($allowedStates) &&
282 | $allowedStates) {
283 | return $allowedStates;
284 | }
285 |
286 | // Get allowed states global. If there are no specific rules on a Page/DataObject
287 | // fallback to what is configured on this extension
288 | $allowedStates = Config::inst()->get(self::class, 'allowed_states');
289 | if (is_array($allowedStates) &&
290 | $allowedStates) {
291 | return $allowedStates;
292 | }
293 | return [];
294 | }
295 |
296 | /**
297 | * get the allowed countries for this object
298 | *
299 | * @return array
300 | */
301 | public function getAllowedCountries()
302 | {
303 | // Get allowed_countries from extending object. (ie. Page, DataObject)
304 | $allowedCountries = $this->owner->config()->allowed_countries;
305 | if (is_array($allowedCountries) &&
306 | $allowedCountries) {
307 | return $allowedCountries;
308 | }
309 |
310 | // Get allowed countries global. If there are no specific rules on a Page/DataObject
311 | // fallback to what is configured on this extension
312 | $allowedCountries = Config::inst()->get(self::class, 'allowed_countries');
313 | if (is_array($allowedCountries) &&
314 | $allowedCountries) {
315 | return $allowedCountries;
316 | }
317 |
318 | // Finally, fallback to a full list of countries
319 | return IntlLocales::singleton()->config()->get('countries');
320 | }
321 |
322 | /**
323 | * @return string
324 | */
325 | private function getPostcodeRegex()
326 | {
327 | // Get postcode regex from extending object. (ie. Page, DataObject)
328 | $regex = $this->owner->config()->postcode_regex;
329 | if ($regex) {
330 | return $regex;
331 | }
332 |
333 | // Get postcode regex global. If there are no specific rules on a Page/DataObject
334 | // fallback to what is configured on this extension
335 | $regex = Config::inst()->get(self::class, 'postcode_regex');
336 | if ($regex) {
337 | return $regex;
338 | }
339 |
340 | return '';
341 | }
342 | }
343 |
--------------------------------------------------------------------------------