GoogleMapField   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 30
lcom 1
cbo 7
dl 0
loc 283
rs 10
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A getName() 0 9 1
A setupOptions() 0 13 4
A setupChildren() 0 43 2
A Field() 0 17 2
A requireDependencies() 0 12 2
A setValue() 0 15 1
A saveInto() 0 7 1
A getChildFields() 0 3 1
A childFieldName() 0 4 1
A recordFieldData() 0 4 2
A getDefaultValue() 0 4 2
A getLatData() 0 4 1
A getLngData() 0 4 1
A getOption() 0 19 5
A setOption() 0 18 3
1
<?php
2
3
/**
4
 * GoogleMapField
5
 * Lets you record a precise location using latitude/longitude fields to a
6
 * DataObject. Displays a map using the Google Maps API. The user may then
7
 * choose where to place the marker; the landing coordinates are then saved.
8
 * You can also search for locations using the search box, which uses the Google
9
 * Maps Geocoding API.
10
 * @author <@willmorgan>
11
 */
12
13
namespace BetterBrief;
14
15
use SilverStripe\Forms\FormField;
16
use SilverStripe\ORM\DataObject;
17
use SilverStripe\Forms\HiddenField;
18
use SilverStripe\Forms\TextField;
19
use SilverStripe\View\Requirements;
20
use SilverStripe\ORM\DataObjectInterface;
21
use SilverStripe\Forms\FieldList;
22
use SilverStripe\Core\Convert;
23
24
class GoogleMapField extends FormField {
25
26
	protected $data;
27
28
	/**
29
	 * @var FormField
30
	 */
31
	protected $latField;
32
33
	/**
34
	 * @var FormField
35
	 */
36
	protected $lngField;
37
38
	/**
39
	 * @var FormField
40
	 */
41
	protected $zoomField;
42
43
	/**
44
	 * @var FormField
45
	 */
46
	protected $boundsField;
47
48
	/**
49
	 * The merged version of the default and user specified options
50
	 * @var array
51
	 */
52
	protected $options = array();
53
54
	/**
55
	 * @param DataObject $data The controlling dataobject
56
	 * @param string $title The title of the field
57
	 * @param array $options Various settings for the field
58
	 */
59
	public function __construct(DataObject $data, $title, $options = array()) {
60
		$this->data = $data;
61
62
		// Set up fieldnames
63
		$this->setupOptions($options);
64
65
		$this->setupChildren();
66
67
		parent::__construct($this->getName(), $title);
68
	}
69
70
	// Auto generate a name
71
	public function getName() {
72
		$fieldNames = $this->getOption('field_names');
73
		return sprintf(
74
			'%s_%s_%s',
75
			$this->data->class,
76
			$fieldNames['Latitude'],
77
			$fieldNames['Longitude']
78
		);
79
	}
80
81
	/**
82
	 * Merge options preserving the first level of array keys
83
	 * @param array $options
84
	 */
85
	public function setupOptions(array $options) {
86
		$this->options = static::config()->default_options;
87
		foreach($this->options as $name => &$value) {
88
			if(isset($options[$name])) {
89
				if(is_array($value)) {
90
					$value = array_merge($value, $options[$name]);
91
				}
92
				else {
93
					$value = $options[$name];
94
				}
95
			}
96
		}
97
	}
98
99
	/**
100
	 * Set up child hidden fields, and optionally the search box.
101
	 * @return FieldList the children
102
	 */
103
	public function setupChildren() {
104
		$name = $this->getName();
105
106
		// Create the latitude/longitude hidden fields
107
		$this->latField = HiddenField::create(
108
			$name.'[Latitude]',
109
			'Lat',
110
			$this->recordFieldData('Latitude')
111
		)->addExtraClass('googlemapfield-latfield no-change-track');
112
113
		$this->lngField = HiddenField::create(
114
			$name.'[Longitude]',
115
			'Lng',
116
			$this->recordFieldData('Longitude')
117
		)->addExtraClass('googlemapfield-lngfield no-change-track');
118
119
		$this->zoomField = HiddenField::create(
120
			$name.'[Zoom]',
121
			'Zoom',
122
			$this->recordFieldData('Zoom')
123
		)->addExtraClass('googlemapfield-zoomfield no-change-track');
124
		$this->boundsField = HiddenField::create(
125
			$name.'[Bounds]',
126
			'Bounds',
127
			$this->recordFieldData('Bounds')
128
		)->addExtraClass('googlemapfield-boundsfield no-change-track');
129
		$this->children = new FieldList(
0 ignored issues
show
Documentation introduced by
The property children does not exist on object<BetterBrief\GoogleMapField>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
130
			$this->latField,
131
			$this->lngField,
132
			$this->zoomField,
133
			$this->boundsField
134
		);
135
136
		if($this->options['show_search_box']) {
137
			$this->children->push(
0 ignored issues
show
Documentation introduced by
The property children does not exist on object<BetterBrief\GoogleMapField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
138
				TextField::create('Search')
139
				->addExtraClass('googlemapfield-searchfield')
140
				->setAttribute('placeholder', 'Search for a location')
141
			);
142
		}
143
144
		return $this->children;
0 ignored issues
show
Documentation introduced by
The property children does not exist on object<BetterBrief\GoogleMapField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
145
	}
146
147
	/**
148
	 * @param array $properties
149
	 * @see https://developers.google.com/maps/documentation/javascript/reference
150
	 * {@inheritdoc}
151
	 */
152
	public function Field($properties = array()) {
153
		$jsOptions = array(
154
			'coords' => array(
155
				$this->recordFieldData('Latitude'),
156
				$this->recordFieldData('Longitude')
157
			),
158
			'map' => array(
159
				'zoom' => $this->recordFieldData('Zoom') ?: $this->getOption('map.zoom'),
160
				'mapTypeId' => 'ROADMAP',
161
			),
162
		);
163
164
		$jsOptions = array_replace_recursive($jsOptions, $this->options);
165
		$this->setAttribute('data-settings', Convert::array2json($jsOptions));
166
		$this->requireDependencies();
167
		return parent::Field($properties);
168
	}
169
170
	/**
171
	 * Set up and include any frontend requirements
172
	 * @return void
173
	 */
174
	protected function requireDependencies() {
175
		$gmapsParams = array(
176
			'callback' => 'googlemapfieldInit',
177
		);
178
		if($key = $this->getOption('api_key')) {
179
			$gmapsParams['key'] = $key;
180
		}
181
		$this->extend('updateGoogleMapsParams', $gmapsParams);
182
        Requirements::css('betterbrief/silverstripe-googlemapfield: client/css/GoogleMapField.css');
183
        Requirements::javascript('betterbrief/silverstripe-googlemapfield: client/js/GoogleMapField.js');
184
		Requirements::javascript('//maps.googleapis.com/maps/api/js?' . http_build_query($gmapsParams));
185
	}
186
187
	/**
188
	 * {@inheritdoc}
189
	 */
190
	public function setValue($record, $data = null) {
191
		$this->latField->setValue(
192
			$record['Latitude']
193
		);
194
		$this->lngField->setValue(
195
			$record['Longitude']
196
		);
197
		$this->zoomField->setValue(
198
			$record['Zoom']
199
		);
200
		$this->boundsField->setValue(
201
			$record['Bounds']
202
		);
203
		return $this;
204
	}
205
206
	/**
207
	 * Take the latitude/longitude fields and save them to the DataObject.
208
	 * {@inheritdoc}
209
	 */
210
	public function saveInto(DataObjectInterface $record) {
211
		$record->setCastedField($this->childFieldName('Latitude'), $this->latField->dataValue());
212
		$record->setCastedField($this->childFieldName('Longitude'), $this->lngField->dataValue());
213
		$record->setCastedField($this->childFieldName('Zoom'), $this->zoomField->dataValue());
214
		$record->setCastedField($this->childFieldName('Bounds'), $this->boundsField->dataValue());
215
		return $this;
216
	}
217
218
	/**
219
	 * @return FieldList The Latitude/Longitude fields
220
	 */
221
	public function getChildFields() {
222
		return $this->children;
0 ignored issues
show
Documentation introduced by
The property children does not exist on object<BetterBrief\GoogleMapField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
223
	}
224
225
	protected function childFieldName($name) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
226
		$fieldNames = $this->getOption('field_names');
227
		return $fieldNames[$name];
228
	}
229
230
	protected function recordFieldData($name) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
231
		$fieldName = $this->childFieldName($name);
232
		return $this->data->$fieldName ?: $this->getDefaultValue($name);
233
	}
234
235
	public function getDefaultValue($name) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
236
		$fieldValues = $this->getOption('default_field_values');
237
		return isset($fieldValues[$name]) ? $fieldValues[$name] : null;
238
	}
239
240
	/**
241
	 * @return string The VALUE of the Latitude field
242
	 */
243
	public function getLatData() {
244
		$fieldNames = $this->getOption('field_names');
245
		return $this->data->$fieldNames['Latitude'];
246
	}
247
248
	/**
249
	 * @return string The VALUE of the Longitude field
250
	 */
251
	public function getLngData() {
252
		$fieldNames = $this->getOption('field_names');
253
		return $this->data->$fieldNames['Longitude'];
254
	}
255
256
	/**
257
	 * Get the merged option that was set on __construct
258
	 * @param string $name The name of the option
259
	 * @return mixed
260
	 */
261
	public function getOption($name) {
262
		// Quicker execution path for "."-free names
263
		if (strpos($name, '.') === false) {
264
			if (isset($this->options[$name])) return $this->options[$name];
265
		} else {
266
			$names = explode('.', $name);
267
268
			$var = $this->options;
269
270
			foreach($names as $n) {
271
				if(!isset($var[$n])) {
272
					return null;
273
				}
274
				$var = $var[$n];
275
			}
276
277
			return $var;
278
		}
279
	}
280
281
	/**
282
	 * Set an option for this field
283
	 * @param string $name The name of the option to set
284
	 * @param mixed $val The value of said option
285
	 * @return $this
286
	 */
287
	public function setOption($name, $val) {
288
		// Quicker execution path for "."-free names
289
		if(strpos($name,'.') === false) {
290
			$this->options[$name] = $val;
291
		} else {
292
			$names = explode('.', $name);
293
294
			// We still want to do this even if we have strict path checking for legacy code
295
			$var = &$this->options;
296
297
			foreach($names as $n) {
298
				$var = &$var[$n];
299
			}
300
301
			$var = $val;
302
		}
303
		return $this;
304
	}
305
306
}
307