1
|
|
|
<?php |
2
|
|
|
namespace rtens\domin\delivery\web\fields; |
3
|
|
|
|
4
|
|
|
use rtens\domin\Parameter; |
5
|
|
|
use rtens\domin\parameters\Image; |
6
|
|
|
use rtens\domin\parameters\file\MemoryFile; |
7
|
|
|
use rtens\domin\delivery\web\Element; |
8
|
|
|
use rtens\domin\delivery\web\HeadElements; |
9
|
|
|
use watoki\reflect\type\ClassType; |
10
|
|
|
|
11
|
|
|
class ImageField extends FileField { |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* For available options see https://github.com/fengyuanchen/cropper/#options |
15
|
|
|
* @return array |
16
|
|
|
*/ |
17
|
|
|
protected function getCropperOptions() { |
18
|
|
|
return [ |
19
|
|
|
'autoCropArea' => 1, |
20
|
|
|
'minContainerHeight' => 400, |
21
|
|
|
'guides' => false, |
22
|
|
|
'strict' => false |
23
|
|
|
]; |
24
|
|
|
} |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @return array|array[] List of [nominator,denominator] pairs |
28
|
|
|
*/ |
29
|
|
|
protected function getAspectRatios() { |
30
|
|
|
return [ |
31
|
|
|
[16, 9], |
32
|
|
|
[4, 3], |
33
|
|
|
[1, 1], |
34
|
|
|
[2, 3] |
35
|
|
|
]; |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @return array|int[] |
40
|
|
|
*/ |
41
|
|
|
protected function getRotatingAngles() { |
42
|
|
|
return [15, 90]; |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* See https://github.com/jhuckaby/webcamjs#configuration |
47
|
|
|
* @return array |
48
|
|
|
*/ |
49
|
|
|
private function getWebcamOptions() { |
50
|
|
|
return [ |
51
|
|
|
'width' => 640, |
52
|
|
|
'height' => 480 |
53
|
|
|
]; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @param Parameter $parameter |
58
|
|
|
* @return bool |
59
|
|
|
*/ |
60
|
|
|
public function handles(Parameter $parameter) { |
61
|
|
|
return $parameter->getType() == new ClassType(Image::class); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @param Parameter $parameter |
66
|
|
|
* @param string[] $serialized |
67
|
|
|
* @return \rtens\domin\parameters\Image |
68
|
|
|
*/ |
69
|
|
|
public function inflate(Parameter $parameter, $serialized) { |
70
|
|
|
if (isset($serialized['encoded']) && $serialized['encoded']) { |
71
|
|
|
list($nameAndType, $data) = explode(';base64,', $serialized['encoded']); |
72
|
|
|
list($name, $type) = explode(';;data:', $nameAndType); |
73
|
|
|
|
74
|
|
|
return new Image(new MemoryFile($name, $type, base64_decode($data))); |
|
|
|
|
75
|
|
View Code Duplication |
} else if (isset($serialized['name']) && $serialized['name']) { |
|
|
|
|
76
|
|
|
return new Image($this->createPreservedFile($serialized)); |
|
|
|
|
77
|
|
|
} else { |
78
|
|
|
return null; |
79
|
|
|
} |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @param Parameter $parameter |
84
|
|
|
* @param null|Image $value |
85
|
|
|
* @return string |
86
|
|
|
*/ |
87
|
|
|
public function render(Parameter $parameter, $value) { |
88
|
|
|
return |
89
|
|
|
$this->renderImagePreservation($parameter, $value ? $value->getFile() : null) . |
90
|
|
|
new Element('div', ['class' => 'image-cropper'], [ |
91
|
|
|
new Element('label', [], [ |
92
|
|
|
new Element('span', ['class' => 'btn btn-success'], ['Choose Image']), |
93
|
|
|
new Element("input", array_merge([ |
94
|
|
|
'class' => 'sr-only image-input', |
95
|
|
|
"type" => "file", |
96
|
|
|
], $parameter->isRequired() && is_null($value) ? [ |
97
|
|
|
'required' => 'required' |
98
|
|
|
] : [])) |
99
|
|
|
]), |
100
|
|
|
new Element('span', ['class' => 'btn btn-success take-photo'], ['Take Photo']), |
101
|
|
|
new Element('div', ['class' => 'photo-preview-container', 'style' => 'display: none; text-align: center;'], [ |
102
|
|
|
new Element('p', [], ['Click on the preview to take a photo.']), |
103
|
|
|
new Element('div', ['class' => 'photo-preview', 'style' => 'margin: auto;']) |
104
|
|
|
]), |
105
|
|
|
|
106
|
|
|
new Element("input", [ |
107
|
|
|
'type' => 'hidden', |
108
|
|
|
'class' => "image-data", |
109
|
|
|
'name' => $parameter->getName() . '[encoded]' |
110
|
|
|
]), |
111
|
|
|
|
112
|
|
|
new Element('div', ['class' => 'image-container', 'style' => 'display: none;'], [ |
113
|
|
|
new Element('div', ['class' => 'form-group image-controls'], $this->renderControls()), |
114
|
|
|
new Element('img', ['class' => 'image-placeholder']), |
115
|
|
|
]) |
116
|
|
|
]); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* @return Element[] |
121
|
|
|
*/ |
122
|
|
|
protected function renderControls() { |
123
|
|
|
return [ |
124
|
|
|
new Element('div', ['class' => 'pull-right'], $this->renderSizeControls()), |
125
|
|
|
new Element('div', ['class' => 'btn-group'], $this->renderRotationButtons()), |
126
|
|
|
new Element('div', ['class' => 'btn-group'], array_merge( |
127
|
|
|
$this->renderAspectRatioButtons(), |
128
|
|
|
[$this->renderButton('free', 'No aspect ratio', "$(this).setOption('setAspectRatio', 0)")] |
129
|
|
|
)), |
130
|
|
|
]; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* @return Element[] |
135
|
|
|
*/ |
136
|
|
|
protected function renderSizeControls() { |
137
|
|
|
return [ |
138
|
|
|
new Element('div', ['class' => 'input-group pull-right', 'style' => 'width: 250px'], [ |
139
|
|
|
new Element('input', ['class' => 'form-control image-width', 'type' => 'text', 'title' => 'Image width']), |
140
|
|
|
new Element('span', ['class' => 'input-group-addon'], ['×']), |
141
|
|
|
new Element('input', ['class' => 'form-control image-height', 'type' => 'text', 'title' => 'Image height']), |
142
|
|
|
new Element('span', ['class' => 'input-group-addon'], ['px']) |
143
|
|
|
]), |
144
|
|
|
new Element('div', ['class' => 'btn-group'], [ |
145
|
|
|
$this->renderIconButton('resize-small', 'Shrink image', "$(this).changeFactor(1/1.25);"), |
146
|
|
|
$this->renderIconButton('resize-full', 'Enlarge image', "$(this).changeFactor(1.25);"), |
147
|
|
|
' ' |
148
|
|
|
]), |
149
|
|
|
]; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
private function renderAspectRatioButtons() { |
153
|
|
|
return array_map(function ($ratio) { |
154
|
|
|
list($nom, $den) = $ratio; |
155
|
|
|
return $this->renderButton("$nom:$den", "Fix aspect ratio to $nom:$den", "$(this).setOption('setAspectRatio', $nom/$den)"); |
156
|
|
|
}, $this->getAspectRatios()); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
private function renderRotationButtons() { |
160
|
|
|
return array_map(function ($angle) { |
161
|
|
|
return $this->renderButton("$angle°", "Rotate by $angle degree", "$(this).setOption('rotate', $angle)"); |
162
|
|
|
}, $this->getRotatingAngles()); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
protected function renderIconButton($glyphIcon, $title, $onClick) { |
166
|
|
|
return $this->renderButton(new Element('span', ['class' => 'glyphicon glyphicon-' . $glyphIcon]), $title, $onClick); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
protected function renderButton($caption, $title, $onClick) { |
170
|
|
|
return new Element('span', [ |
171
|
|
|
'class' => 'btn btn-default', |
172
|
|
|
'title' => $title, |
173
|
|
|
'onclick' => $onClick |
174
|
|
|
], [$caption]); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* @param Parameter $parameter |
179
|
|
|
* @return array|\rtens\domin\delivery\web\Element[] |
180
|
|
|
*/ |
181
|
|
|
public function headElements(Parameter $parameter) { |
182
|
|
|
$script = file_get_contents(__DIR__ . '/js/ImageField.js'); |
183
|
|
|
|
184
|
|
|
$script = str_replace( |
185
|
|
|
'$cropperOptions$', |
186
|
|
|
json_encode($this->getCropperOptions()), |
187
|
|
|
$script |
188
|
|
|
); |
189
|
|
|
$script = str_replace( |
190
|
|
|
'$webcamjsOptions$', |
191
|
|
|
json_encode($this->getWebcamOptions()), |
192
|
|
|
$script |
193
|
|
|
); |
194
|
|
|
|
195
|
|
|
return [ |
196
|
|
|
HeadElements::jquery(), |
197
|
|
|
HeadElements::bootstrapJs(), |
198
|
|
|
HeadElements::bootstrap(), |
199
|
|
|
HeadElements::script('//cdnjs.cloudflare.com/ajax/libs/cropper/0.9.3/cropper.min.js'), |
200
|
|
|
HeadElements::style('//cdnjs.cloudflare.com/ajax/libs/cropper/0.9.3/cropper.min.css'), |
201
|
|
|
HeadElements::script('http://pixlcore.com/demos/webcamjs/webcam.js'), |
202
|
|
|
new Element('script', [], [$script]), |
203
|
|
|
]; |
204
|
|
|
} |
205
|
|
|
} |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.