2adbd1f0f0b78ed1f62b34cb3a9117518afe3044
[yaffs-website] / web / core / lib / Drupal / Core / FileTransfer / Form / FileTransferAuthorizeForm.php
1 <?php
2
3 namespace Drupal\Core\FileTransfer\Form;
4
5 use Drupal\Core\Form\FormBase;
6 use Drupal\Core\Form\FormStateInterface;
7 use Drupal\Core\Render\Element;
8 use Symfony\Component\DependencyInjection\ContainerInterface;
9 use Symfony\Component\HttpFoundation\Response;
10
11 /**
12  * Provides the file transfer authorization form.
13  */
14 class FileTransferAuthorizeForm extends FormBase {
15
16   /**
17    * The app root.
18    *
19    * @var string
20    */
21   protected  $root;
22
23   /**
24    * Constructs a new FileTransferAuthorizeForm object.
25    *
26    * @param string $root
27    *   The app root.
28    */
29   public function __construct($root) {
30     $this->root = $root;
31   }
32
33   /**
34    * {@inheritdoc}
35    */
36   public static function create(ContainerInterface $container) {
37     return new static ($container->get('app.root'));
38   }
39
40   /**
41    * {@inheritdoc}
42    */
43   public function getFormId() {
44     return 'authorize_filetransfer_form';
45   }
46
47   /**
48    * {@inheritdoc}
49    */
50   public function buildForm(array $form, FormStateInterface $form_state) {
51     // Get all the available ways to transfer files.
52     if (empty($_SESSION['authorize_filetransfer_info'])) {
53       drupal_set_message($this->t('Unable to continue, no available methods of file transfer'), 'error');
54       return [];
55     }
56     $available_backends = $_SESSION['authorize_filetransfer_info'];
57
58     if (!$this->getRequest()->isSecure()) {
59       $form['information']['https_warning'] = [
60         '#prefix' => '<div class="messages messages--error">',
61         '#markup' => $this->t('WARNING: You are not using an encrypted connection, so your password will be sent in plain text. <a href=":https-link">Learn more</a>.', [':https-link' => 'https://www.drupal.org/https-information']),
62         '#suffix' => '</div>',
63       ];
64     }
65
66     // Decide on a default backend.
67     $authorize_filetransfer_default = $form_state->getValue(['connection_settings', 'authorize_filetransfer_default']);
68     if (!$authorize_filetransfer_default) {
69       $authorize_filetransfer_default = key($available_backends);
70     }
71
72     $form['information']['main_header'] = [
73       '#prefix' => '<h3>',
74       '#markup' => $this->t('To continue, provide your server connection details'),
75       '#suffix' => '</h3>',
76     ];
77
78     $form['connection_settings']['#tree'] = TRUE;
79     $form['connection_settings']['authorize_filetransfer_default'] = [
80       '#type' => 'select',
81       '#title' => $this->t('Connection method'),
82       '#default_value' => $authorize_filetransfer_default,
83       '#weight' => -10,
84     ];
85
86     /*
87      * Here we create two submit buttons. For a JS enabled client, they will
88      * only ever see submit_process. However, if a client doesn't have JS
89      * enabled, they will see submit_connection on the first form (when picking
90      * what filetransfer type to use, and submit_process on the second one (which
91      * leads to the actual operation).
92      */
93     $form['submit_connection'] = [
94       '#prefix' => "<br style='clear:both'/>",
95       '#name' => 'enter_connection_settings',
96       '#type' => 'submit',
97       '#value' => $this->t('Enter connection settings'),
98       '#weight' => 100,
99     ];
100
101     $form['submit_process'] = [
102       '#name' => 'process_updates',
103       '#type' => 'submit',
104       '#value' => $this->t('Continue'),
105       '#weight' => 100,
106     ];
107
108     // Build a container for each connection type.
109     foreach ($available_backends as $name => $backend) {
110       $form['connection_settings']['authorize_filetransfer_default']['#options'][$name] = $backend['title'];
111       $form['connection_settings'][$name] = [
112         '#type' => 'container',
113         '#attributes' => ['class' => ["filetransfer-$name", 'filetransfer']],
114         '#states' => [
115           'visible' => [
116             'select[name="connection_settings[authorize_filetransfer_default]"]' => ['value' => $name],
117           ],
118         ],
119       ];
120       // We can't use #prefix on the container itself since then the header won't
121       // be hidden and shown when the containers are being manipulated via JS.
122       $form['connection_settings'][$name]['header'] = [
123         '#markup' => '<h4>' . $this->t('@backend connection settings', ['@backend' => $backend['title']]) . '</h4>',
124       ];
125
126       $form['connection_settings'][$name] += $this->addConnectionSettings($name);
127
128       // Start non-JS code.
129       if ($form_state->getValue(['connection_settings', 'authorize_filetransfer_default']) == $name) {
130
131         // Change the submit button to the submit_process one.
132         $form['submit_process']['#attributes'] = [];
133         unset($form['submit_connection']);
134
135         // Activate the proper filetransfer settings form.
136         $form['connection_settings'][$name]['#attributes']['style'] = 'display:block';
137         // Disable the select box.
138         $form['connection_settings']['authorize_filetransfer_default']['#disabled'] = TRUE;
139
140         // Create a button for changing the type of connection.
141         $form['connection_settings']['change_connection_type'] = [
142           '#name' => 'change_connection_type',
143           '#type' => 'submit',
144           '#value' => $this->t('Change connection type'),
145           '#weight' => -5,
146           '#attributes' => ['class' => ['filetransfer-change-connection-type']],
147         ];
148       }
149       // End non-JS code.
150     }
151     return $form;
152   }
153
154   /**
155    * {@inheritdoc}
156    */
157   public function validateForm(array &$form, FormStateInterface $form_state) {
158     // Only validate the form if we have collected all of the user input and are
159     // ready to proceed with updating or installing.
160     if ($form_state->getTriggeringElement()['#name'] != 'process_updates') {
161       return;
162     }
163
164     if ($form_connection_settings = $form_state->getValue('connection_settings')) {
165       $backend = $form_connection_settings['authorize_filetransfer_default'];
166       $filetransfer = $this->getFiletransfer($backend, $form_connection_settings[$backend]);
167       try {
168         if (!$filetransfer) {
169           throw new \Exception($this->t('The connection protocol %backend does not exist.', ['%backend' => $backend]));
170         }
171         $filetransfer->connect();
172       }
173       catch (\Exception $e) {
174         // The format of this error message is similar to that used on the
175         // database connection form in the installer.
176         $form_state->setErrorByName('connection_settings', $this->t('Failed to connect to the server. The server reports the following message: <p class="error">@message</p> For more help installing or updating code on your server, see the <a href=":handbook_url">handbook</a>.', [
177           '@message' => $e->getMessage(),
178           ':handbook_url' => 'https://www.drupal.org/documentation/install/modules-themes',
179         ]));
180       }
181     }
182   }
183
184   /**
185    * {@inheritdoc}
186    */
187   public function submitForm(array &$form, FormStateInterface $form_state) {
188     $form_connection_settings = $form_state->getValue('connection_settings');
189     switch ($form_state->getTriggeringElement()['#name']) {
190       case 'process_updates':
191
192         // Save the connection settings to the DB.
193         $filetransfer_backend = $form_connection_settings['authorize_filetransfer_default'];
194
195         // If the database is available then try to save our settings. We have
196         // to make sure it is available since this code could potentially (will
197         // likely) be called during the installation process, before the
198         // database is set up.
199         try {
200           $filetransfer = $this->getFiletransfer($filetransfer_backend, $form_connection_settings[$filetransfer_backend]);
201
202           // Now run the operation.
203           $response = $this->runOperation($filetransfer);
204           if ($response instanceof Response) {
205             $form_state->setResponse($response);
206           }
207         }
208         catch (\Exception $e) {
209           // If there is no database available, we don't care and just skip
210           // this part entirely.
211         }
212
213         break;
214
215       case 'enter_connection_settings':
216         $form_state->setRebuild();
217         break;
218
219       case 'change_connection_type':
220         $form_state->setRebuild();
221         $form_state->unsetValue(['connection_settings', 'authorize_filetransfer_default']);
222         break;
223     }
224   }
225
226   /**
227    * Gets a FileTransfer class for a specific transfer method and settings.
228    *
229    * @param $backend
230    *   The FileTransfer backend to get the class for.
231    * @param $settings
232    *   Array of settings for the FileTransfer.
233    *
234    * @return \Drupal\Core\FileTransfer\FileTransfer|bool
235    *   An instantiated FileTransfer object for the requested method and settings,
236    *   or FALSE if there was an error finding or instantiating it.
237    */
238   protected function getFiletransfer($backend, $settings = []) {
239     $filetransfer = FALSE;
240     if (!empty($_SESSION['authorize_filetransfer_info'][$backend])) {
241       $backend_info = $_SESSION['authorize_filetransfer_info'][$backend];
242       if (class_exists($backend_info['class'])) {
243         $filetransfer = $backend_info['class']::factory($this->root, $settings);
244       }
245     }
246     return $filetransfer;
247   }
248
249   /**
250    * Generates the Form API array for a given connection backend's settings.
251    *
252    * @param string $backend
253    *   The name of the backend (e.g. 'ftp', 'ssh', etc).
254    *
255    * @return array
256    *   Form API array of connection settings for the given backend.
257    *
258    * @see hook_filetransfer_backends()
259    */
260   protected function addConnectionSettings($backend) {
261     $defaults = [];
262     $form = [];
263
264     // Create an instance of the file transfer class to get its settings form.
265     $filetransfer = $this->getFiletransfer($backend);
266     if ($filetransfer) {
267       $form = $filetransfer->getSettingsForm();
268     }
269     // Fill in the defaults based on the saved settings, if any.
270     $this->setConnectionSettingsDefaults($form, NULL, $defaults);
271     return $form;
272   }
273
274   /**
275    * Sets the default settings on a file transfer connection form recursively.
276    *
277    * The default settings for the file transfer connection forms are saved in
278    * the database. The settings are stored as a nested array in the case of a
279    * settings form that has details or otherwise uses a nested structure.
280    * Therefore, to properly add defaults, we need to walk through all the
281    * children form elements and process those defaults recursively.
282    *
283    * @param $element
284    *   Reference to the Form API form element we're operating on.
285    * @param $key
286    *   The key for our current form element, if any.
287    * @param array $defaults
288    *   The default settings for the file transfer backend we're operating on.
289    */
290   protected function setConnectionSettingsDefaults(&$element, $key, array $defaults) {
291     // If we're operating on a form element which isn't a details, and we have
292     // a default setting saved, stash it in #default_value.
293     if (!empty($key) && isset($defaults[$key]) && isset($element['#type']) && $element['#type'] != 'details') {
294       $element['#default_value'] = $defaults[$key];
295     }
296     // Now, we walk through all the child elements, and recursively invoke
297     // ourselves on each one. Since the $defaults settings array can be nested
298     // (because of #tree, any values inside details will be nested), if
299     // there's a subarray of settings for the form key we're currently
300     // processing, pass in that subarray to the recursive call. Otherwise, just
301     // pass on the whole $defaults array.
302     foreach (Element::children($element) as $child_key) {
303       $this->setConnectionSettingsDefaults($element[$child_key], $child_key, ((isset($defaults[$key]) && is_array($defaults[$key])) ? $defaults[$key] : $defaults));
304     }
305   }
306
307   /**
308    * Runs the operation specified in $_SESSION['authorize_operation'].
309    *
310    * @param $filetransfer
311    *   The FileTransfer object to use for running the operation.
312    *
313    * @return \Symfony\Component\HttpFoundation\Response|null
314    *   The result of running the operation. If this is an instance of
315    *   \Symfony\Component\HttpFoundation\Response the calling code should use
316    *   that response for the current page request.
317    */
318   protected function runOperation($filetransfer) {
319     $operation = $_SESSION['authorize_operation'];
320     unset($_SESSION['authorize_operation']);
321
322     require_once $operation['file'];
323     return call_user_func_array($operation['callback'], array_merge([$filetransfer], $operation['arguments']));
324   }
325
326 }