use zend router
[GitHub/Stricted/Domain-Control-Panel.git] / vendor / Zend / Mvc / Controller / Plugin / FilePostRedirectGet.php
1 <?php
2
3 /**
4 * Zend Framework (http://framework.zend.com/)
5 *
6 * @link http://github.com/zendframework/zf2 for the canonical source repository
7 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
8 * @license http://framework.zend.com/license/new-bsd New BSD License
9 */
10
11 namespace Zend\Mvc\Controller\Plugin;
12
13 use Zend\Filter\FilterChain;
14 use Zend\Form\FormInterface;
15 use Zend\Http\Response;
16 use Zend\InputFilter\FileInput;
17 use Zend\InputFilter\InputFilterInterface;
18 use Zend\Mvc\Exception\RuntimeException;
19 use Zend\Session\Container;
20 use Zend\Stdlib\ArrayUtils;
21 use Zend\Validator\ValidatorChain;
22
23 /**
24 * Plugin to help facilitate Post/Redirect/Get for file upload forms
25 * (http://en.wikipedia.org/wiki/Post/Redirect/Get)
26 *
27 * Requires that the Form's File inputs contain a 'fileRenameUpload' filter
28 * with the target option set: 'target' => /valid/target/path'.
29 * This is so the files are moved to a new location between requests.
30 * If this filter is not added, the temporary upload files will disappear
31 * between requests.
32 */
33 class FilePostRedirectGet extends AbstractPlugin
34 {
35 /**
36 * @var Container
37 */
38 protected $sessionContainer;
39
40 /**
41 * @param FormInterface $form
42 * @param string $redirect Route or URL string (default: current route)
43 * @param bool $redirectToUrl Use $redirect as a URL string (default: false)
44 * @return bool|array|Response
45 */
46 public function __invoke(FormInterface $form, $redirect = null, $redirectToUrl = false)
47 {
48 $request = $this->getController()->getRequest();
49 if ($request->isPost()) {
50 return $this->handlePostRequest($form, $redirect, $redirectToUrl);
51 } else {
52 return $this->handleGetRequest($form);
53 }
54 }
55
56 /**
57 * @param FormInterface $form
58 * @param string $redirect Route or URL string (default: current route)
59 * @param bool $redirectToUrl Use $redirect as a URL string (default: false)
60 * @return Response
61 */
62 protected function handlePostRequest(FormInterface $form, $redirect, $redirectToUrl)
63 {
64 $container = $this->getSessionContainer();
65 $request = $this->getController()->getRequest();
66 $postFiles = $request->getFiles()->toArray();
67 $postOther = $request->getPost()->toArray();
68 $post = ArrayUtils::merge($postOther, $postFiles, true);
69
70 // Fill form with the data first, collections may alter the form/filter structure
71 $form->setData($post);
72
73 // Change required flag to false for any previously uploaded files
74 $inputFilter = $form->getInputFilter();
75 $previousFiles = ($container->files) ?: [];
76 $this->traverseInputs(
77 $inputFilter,
78 $previousFiles,
79 function ($input, $value) {
80 if ($input instanceof FileInput) {
81 $input->setRequired(false);
82 }
83 return $value;
84 }
85 );
86
87 // Run the form validations/filters and retrieve any errors
88 $isValid = $form->isValid();
89 $data = $form->getData(FormInterface::VALUES_AS_ARRAY);
90 $errors = (!$isValid) ? $form->getMessages() : null;
91
92 // Merge and replace previous files with new valid files
93 $prevFileData = $this->getEmptyUploadData($inputFilter, $previousFiles);
94 $newFileData = $this->getNonEmptyUploadData($inputFilter, $data);
95 $postFiles = ArrayUtils::merge(
96 $prevFileData ?: [],
97 $newFileData ?: [],
98 true
99 );
100 $post = ArrayUtils::merge($postOther, $postFiles, true);
101
102 // Save form data in session
103 $container->setExpirationHops(1, ['post', 'errors', 'isValid']);
104 $container->post = $post;
105 $container->errors = $errors;
106 $container->isValid = $isValid;
107 $container->files = $postFiles;
108
109 return $this->redirect($redirect, $redirectToUrl);
110 }
111
112 /**
113 * @param FormInterface $form
114 * @return bool|array
115 */
116 protected function handleGetRequest(FormInterface $form)
117 {
118 $container = $this->getSessionContainer();
119 if (null === $container->post) {
120 // No previous post, bail early
121 unset($container->files);
122 return false;
123 }
124
125 // Collect data from session
126 $post = $container->post;
127 $errors = $container->errors;
128 $isValid = $container->isValid;
129 unset($container->post);
130 unset($container->errors);
131 unset($container->isValid);
132
133 // Fill form with the data first, collections may alter the form/filter structure
134 $form->setData($post);
135
136 // Remove File Input validators and filters on previously uploaded files
137 // in case $form->isValid() or $form->bindValues() is run
138 $inputFilter = $form->getInputFilter();
139 $this->traverseInputs(
140 $inputFilter,
141 $post,
142 function ($input, $value) {
143 if ($input instanceof FileInput) {
144 $input->setAutoPrependUploadValidator(false)
145 ->setValidatorChain(new ValidatorChain())
146 ->setFilterChain(new FilterChain);
147 }
148 return $value;
149 }
150 );
151
152 // set previous state
153 $form->isValid(); // re-validate to bind values
154 if (null !== $errors) {
155 $form->setMessages($errors); // overwrite messages
156 }
157 $this->setProtectedFormProperty($form, 'isValid', $isValid); // force previous state
158
159 // Clear previous files from session data if form was valid
160 if ($isValid) {
161 unset($container->files);
162 }
163
164 return $post;
165 }
166
167 /**
168 * @return Container
169 */
170 public function getSessionContainer()
171 {
172 if (!$this->sessionContainer) {
173 $this->sessionContainer = new Container('file_prg_post1');
174 }
175 return $this->sessionContainer;
176 }
177
178 /**
179 * @param Container $container
180 * @return FilePostRedirectGet
181 */
182 public function setSessionContainer(Container $container)
183 {
184 $this->sessionContainer = $container;
185 return $this;
186 }
187
188 /**
189 * @param FormInterface $form
190 * @param string $property
191 * @param mixed $value
192 * @return FilePostRedirectGet
193 */
194 protected function setProtectedFormProperty(FormInterface $form, $property, $value)
195 {
196 $formClass = new \ReflectionClass($form);
197 $property = $formClass->getProperty($property);
198 $property->setAccessible(true);
199 $property->setValue($form, $value);
200 return $this;
201 }
202
203 /**
204 * Traverse the InputFilter and run a callback against each Input and associated value
205 *
206 * @param InputFilterInterface $inputFilter
207 * @param array $values
208 * @param callable $callback
209 * @return array|null
210 */
211 protected function traverseInputs(InputFilterInterface $inputFilter, $values, $callback)
212 {
213 $returnValues = null;
214 foreach ($values as $name => $value) {
215 if (!$inputFilter->has($name)) {
216 continue;
217 }
218
219 $input = $inputFilter->get($name);
220 if ($input instanceof InputFilterInterface && is_array($value)) {
221 $retVal = $this->traverseInputs($input, $value, $callback);
222 if (null !== $retVal) {
223 $returnValues[$name] = $retVal;
224 }
225 continue;
226 }
227
228 $retVal = $callback($input, $value);
229 if (null !== $retVal) {
230 $returnValues[$name] = $retVal;
231 }
232 }
233 return $returnValues;
234 }
235
236 /**
237 * Traverse the InputFilter and only return the data of FileInputs that have an upload
238 *
239 * @param InputFilterInterface $inputFilter
240 * @param array $data
241 * @return array
242 */
243 protected function getNonEmptyUploadData(InputFilterInterface $inputFilter, $data)
244 {
245 return $this->traverseInputs(
246 $inputFilter,
247 $data,
248 function ($input, $value) {
249 $messages = $input->getMessages();
250 if (is_array($value) && $input instanceof FileInput && empty($messages)) {
251 $rawValue = $input->getRawValue();
252 if (
253 (isset($rawValue['error']) && $rawValue['error'] !== UPLOAD_ERR_NO_FILE)
254 || (isset($rawValue[0]['error']) && $rawValue[0]['error'] !== UPLOAD_ERR_NO_FILE)
255 ) {
256 return $value;
257 }
258 }
259 return;
260 }
261 );
262 }
263
264 /**
265 * Traverse the InputFilter and only return the data of FileInputs that are empty
266 *
267 * @param InputFilterInterface $inputFilter
268 * @param array $data
269 * @return array
270 */
271 protected function getEmptyUploadData(InputFilterInterface $inputFilter, $data)
272 {
273 return $this->traverseInputs(
274 $inputFilter,
275 $data,
276 function ($input, $value) {
277 $messages = $input->getMessages();
278 if (is_array($value) && $input instanceof FileInput && empty($messages)) {
279 $rawValue = $input->getRawValue();
280 if ((isset($rawValue['error']) && $rawValue['error'] === UPLOAD_ERR_NO_FILE)
281 || (isset($rawValue[0]['error']) && $rawValue[0]['error'] === UPLOAD_ERR_NO_FILE)
282 ) {
283 return $value;
284 }
285 }
286 return;
287 }
288 );
289 }
290
291 /**
292 * TODO: Good candidate for traits method in PHP 5.4 with PostRedirectGet plugin
293 *
294 * @param string $redirect
295 * @param bool $redirectToUrl
296 * @return Response
297 * @throws \Zend\Mvc\Exception\RuntimeException
298 */
299 protected function redirect($redirect, $redirectToUrl)
300 {
301 $controller = $this->getController();
302 $params = [];
303 $options = [];
304 $reuseMatchedParams = false;
305
306 if (null === $redirect) {
307 $routeMatch = $controller->getEvent()->getRouteMatch();
308
309 $redirect = $routeMatch->getMatchedRouteName();
310 //null indicates to redirect for self.
311 $reuseMatchedParams = true;
312 }
313
314 if (method_exists($controller, 'getPluginManager')) {
315 // get the redirect plugin from the plugin manager
316 $redirector = $controller->getPluginManager()->get('Redirect');
317 } else {
318 /*
319 * If the user wants to redirect to a route, the redirector has to come
320 * from the plugin manager -- otherwise no router will be injected
321 */
322 if ($redirectToUrl === false) {
323 throw new RuntimeException('Could not redirect to a route without a router');
324 }
325
326 $redirector = new Redirect();
327 }
328
329 if ($redirectToUrl === false) {
330 $response = $redirector->toRoute($redirect, $params, $options, $reuseMatchedParams);
331 $response->setStatusCode(303);
332 return $response;
333 }
334
335 $response = $redirector->toUrl($redirect);
336 $response->setStatusCode(303);
337
338 return $response;
339 }
340 }