diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 43d15a6..00e942b 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -651,9 +651,14 @@ public function processForm($form_id, &$form, &$form_state) { $form_state['executed'] = TRUE; // Redirect the form based on values in $form_state. - $redirect = $this->redirectForm($form_state); - if (is_object($redirect)) { - return $redirect; + if (!isset($form_state['response']) && $redirect = $this->redirectForm($form_state)) { + $form_state['response'] = $redirect; + } + + // If there is a response in form_state, respect that instead of doing + // a redirect. + if (isset($form_state['response']) && $form_state['response'] instanceof Response) { + return $form_state['response']; } } diff --git a/core/lib/Drupal/Core/Form/FormBuilderInterface.php b/core/lib/Drupal/Core/Form/FormBuilderInterface.php index ad6febf..8a102f9 100644 --- a/core/lib/Drupal/Core/Form/FormBuilderInterface.php +++ b/core/lib/Drupal/Core/Form/FormBuilderInterface.php @@ -114,6 +114,12 @@ public function getForm($form_arg); * already set $form_state['rebuild'] to cause the form processing to * bypass submit handlers and rebuild the form instead, even if there are * no validation errors. + * - response: Used when a form needs to return some kind of a + * \Symfony\Component\HttpFoundation\Response object, e.g., a + * \Symfony\Component\HttpFoundation\BinaryFileResponse when triggering a + * file download. If you use the $form_state['redirect'] key, it will be + * used to build a \Symfony\Component\HttpFoundation\RedirectResponse and + * will populate this key. * - redirect: Used to redirect the form on submission. It may either be a * string containing the destination URL, or an array of arguments * compatible with url(). See url() for complete information. diff --git a/core/modules/locale/lib/Drupal/locale/Form/ExportForm.php b/core/modules/locale/lib/Drupal/locale/Form/ExportForm.php new file mode 100644 index 0000000..433c8c9 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Form/ExportForm.php @@ -0,0 +1,178 @@ +languageManager = $language_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('language_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormID() { + return 'locale_translate_export_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, array &$form_state) { + $languages = $this->languageManager->getLanguages(); + $language_options = array(); + foreach ($languages as $langcode => $language) { + if ($langcode != 'en' || locale_translate_english()) { + $language_options[$langcode] = $language->name; + } + } + $language_default = $this->languageManager->getDefaultLanguage(); + + if (empty($language_options)) { + $form['langcode'] = array( + '#type' => 'value', + '#value' => Language::LANGCODE_SYSTEM, + ); + $form['langcode_text'] = array( + '#type' => 'item', + '#title' => $this->t('Language'), + '#markup' => $this->t('No language available. The export will only contain source strings.'), + ); + } + else { + $form['langcode'] = array( + '#type' => 'select', + '#title' => $this->t('Language'), + '#options' => $language_options, + '#default_value' => $language_default->id, + '#empty_option' => $this->t('Source text only, no translations'), + '#empty_value' => Language::LANGCODE_SYSTEM, + ); + $form['content_options'] = array( + '#type' => 'details', + '#title' => $this->t('Export options'), + '#collapsed' => TRUE, + '#tree' => TRUE, + '#states' => array( + 'invisible' => array( + ':input[name="langcode"]' => array('value' => Language::LANGCODE_SYSTEM), + ), + ), + ); + $form['content_options']['not_customized'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Include non-customized translations'), + '#default_value' => TRUE, + ); + $form['content_options']['customized'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Include customized translations'), + '#default_value' => TRUE, + ); + $form['content_options']['not_translated'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Include untranslated text'), + '#default_value' => TRUE, + ); + } + + $form['actions'] = array( + '#type' => 'actions' + ); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => $this->t('Export') + ); + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state) { + // If template is required, language code is not given. + if ($form_state['values']['langcode'] != Language::LANGCODE_SYSTEM) { + $language = $this->languageManager->getLanguage($form_state['values']['langcode']); + } + else { + $language = NULL; + } + $content_options = isset($form_state['values']['content_options']) ? $form_state['values']['content_options'] : array(); + $reader = new PoDatabaseReader(); + $languageName = ''; + if ($language != NULL) { + $reader->setLangcode($language->id); + $reader->setOptions($content_options); + $languages = $this->languageManager->getLanguages(); + $languageName = isset($languages[$language->id]) ? $languages[$language->id]->name : ''; + $filename = $language->id .'.po'; + } + else { + // Template required. + $filename = 'drupal.pot'; + } + + $item = $reader->readItem(); + if (!empty($item)) { + $uri = tempnam('temporary://', 'po_'); + $header = $reader->getHeader(); + $header->setProjectName($this->config('system.site')->get('name')); + $header->setLanguageName($languageName); + + $writer = new PoStreamWriter; + $writer->setUri($uri); + $writer->setHeader($header); + + $writer->open(); + $writer->writeItem($item); + $writer->writeItems($reader); + $writer->close(); + + $response = new BinaryFileResponse($uri); + $response->setContentDisposition('attachment', $filename); + $form_state['response'] = $response; + } + else { + drupal_set_message($this->t('Nothing to export.')); + } + } + +} diff --git a/core/modules/locale/lib/Drupal/locale/Form/LocaleForm.php b/core/modules/locale/lib/Drupal/locale/Form/LocaleForm.php index c06a4aa..aaa0bfa 100644 --- a/core/modules/locale/lib/Drupal/locale/Form/LocaleForm.php +++ b/core/modules/locale/lib/Drupal/locale/Form/LocaleForm.php @@ -22,16 +22,6 @@ public function import() { } /** - * Wraps locale_translate_export_form(). - * - * @todo Remove locale_translate_export_form(). - */ - public function export() { - module_load_include('bulk.inc', 'locale'); - return drupal_get_form('locale_translate_export_form'); - } - - /** * Wraps locale_translation_status_form(). * * @todo Remove locale_translation_status_form(). diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc index c0e7bbe..01d4960 100644 --- a/core/modules/locale/locale.bulk.inc +++ b/core/modules/locale/locale.bulk.inc @@ -5,9 +5,7 @@ * Mass import-export and batch import functionality for Gettext .po files. */ -use Drupal\Component\Gettext\PoStreamWriter; use Drupal\locale\Gettext; -use Drupal\locale\PoDatabaseReader; use Drupal\Core\Language\Language; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Drupal\file\FileInterface; @@ -148,135 +146,6 @@ function locale_translate_import_form_submit($form, &$form_state) { } /** - * Form constructor for the Gettext translation files export form. - * - * @see locale_translate_export_form_submit() - * @ingroup forms - * - * @deprecated Use \Drupal\locale\Form\LocaleForm::export() - */ -function locale_translate_export_form($form, &$form_state) { - $languages = language_list(); - $language_options = array(); - foreach ($languages as $langcode => $language) { - if ($langcode != 'en' || locale_translate_english()) { - $language_options[$langcode] = $language->name; - } - } - $language_default = language_default(); - - if (empty($language_options)) { - $form['langcode'] = array( - '#type' => 'value', - '#value' => Language::LANGCODE_SYSTEM, - ); - $form['langcode_text'] = array( - '#type' => 'item', - '#title' => t('Language'), - '#markup' => t('No language available. The export will only contain source strings.'), - ); - } - else { - $form['langcode'] = array( - '#type' => 'select', - '#title' => t('Language'), - '#options' => $language_options, - '#default_value' => $language_default->id, - '#empty_option' => t('Source text only, no translations'), - '#empty_value' => Language::LANGCODE_SYSTEM, - ); - $form['content_options'] = array( - '#type' => 'details', - '#title' => t('Export options'), - '#collapsed' => TRUE, - '#tree' => TRUE, - '#states' => array( - 'invisible' => array( - ':input[name="langcode"]' => array('value' => Language::LANGCODE_SYSTEM), - ), - ), - ); - $form['content_options']['not_customized'] = array( - '#type' => 'checkbox', - '#title' => t('Include non-customized translations'), - '#default_value' => TRUE, - ); - $form['content_options']['customized'] = array( - '#type' => 'checkbox', - '#title' => t('Include customized translations'), - '#default_value' => TRUE, - ); - $form['content_options']['not_translated'] = array( - '#type' => 'checkbox', - '#title' => t('Include untranslated text'), - '#default_value' => TRUE, - ); - } - - $form['actions'] = array( - '#type' => 'actions' - ); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Export') - ); - return $form; -} - -/** - * Form submission handler for locale_translate_export_form(). - */ -function locale_translate_export_form_submit($form, &$form_state) { - // If template is required, language code is not given. - if ($form_state['values']['langcode'] != Language::LANGCODE_SYSTEM) { - $language = language_load($form_state['values']['langcode']); - } - else { - $language = NULL; - } - $content_options = isset($form_state['values']['content_options']) ? $form_state['values']['content_options'] : array(); - $reader = new PoDatabaseReader(); - $languageName = ''; - if ($language != NULL) { - $reader->setLangcode($language->id); - $reader->setOptions($content_options); - $languages = language_list(); - $languageName = isset($languages[$language->id]) ? $languages[$language->id]->name : ''; - $filename = $language->id .'.po'; - } - else { - // Template required. - $filename = 'drupal.pot'; - } - - $item = $reader->readItem(); - if (!empty($item)) { - $uri = tempnam('temporary://', 'po_'); - $header = $reader->getHeader(); - $header->setProjectName(\Drupal::config('system.site')->get('name')); - $header->setLanguageName($languageName); - - $writer = new PoStreamWriter; - $writer->setUri($uri); - $writer->setHeader($header); - - $writer->open(); - $writer->writeItem($item); - $writer->writeItems($reader); - $writer->close(); - - $response = new BinaryFileResponse($uri); - $response->setContentDisposition('attachment', $filename); - // @todo remove lines below once converted to new routing system. - $response->prepare(\Drupal::request()) - ->send(); - } - else { - drupal_set_message('Nothing to export.'); - } -} - -/** * Prepare a batch to import all translations. * * @param array $options diff --git a/core/modules/locale/locale.routing.yml b/core/modules/locale/locale.routing.yml index 190ddf7..6658dad 100644 --- a/core/modules/locale/locale.routing.yml +++ b/core/modules/locale/locale.routing.yml @@ -32,7 +32,7 @@ locale.translate_import: locale.translate_export: path: '/admin/config/regional/translate/export' defaults: - _content: '\Drupal\locale\Form\LocaleForm::export' + _form: '\Drupal\locale\Form\ExportForm' _title: 'Export' requirements: _permission: 'translate interface' diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php index 0b52a75..278ade3 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php @@ -112,6 +112,101 @@ public function testGetFormIdWithBaseForm() { } /** + * Tests the handling of $form_state['response']. + * + * @dataProvider formStateResponseProvider + */ + public function testHandleFormStateResponse($class, $form_state_key) { + $form_id = 'test_form_id'; + $expected_form = $form_id(); + + $response = $this->getMockBuilder($class) + ->disableOriginalConstructor() + ->getMock(); + $response->expects($this->any()) + ->method('prepare') + ->will($this->returnValue($response)); + + $form_arg = $this->getMockForm($form_id, $expected_form); + $form_arg->expects($this->any()) + ->method('submitForm') + ->will($this->returnCallback(function ($form, &$form_state) use ($response, $form_state_key) { + $form_state[$form_state_key] = $response; + })); + + $form_state = array(); + $this->formBuilder->getFormId($form_arg, $form_state); + + try { + $form_state['values'] = array(); + $form_state['redirect'] = FALSE; + $form_state['input']['form_id'] = $form_id; + $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE); + $this->fail('TestFormBuilder::sendResponse() was not triggered.'); + } + catch (\Exception $e) { + $this->assertSame('exit', $e->getMessage()); + } + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $form_state['response']); + } + + /** + * Provides test data for testHandleFormStateResponse(). + */ + public function formStateResponseProvider() { + return array( + array('Symfony\Component\HttpFoundation\Response', 'response'), + array('Symfony\Component\HttpFoundation\RedirectResponse', 'redirect'), + ); + } + + /** + * Tests the handling of a redirect when $form_state['response'] exists. + */ + public function testHandleRedirectWithResponse() { + $form_id = 'test_form_id'; + $expected_form = $form_id(); + + // Set up a response that will be used. + $response = $this->getMockBuilder('Symfony\Component\HttpFoundation\Response') + ->disableOriginalConstructor() + ->getMock(); + $response->expects($this->once()) + ->method('prepare') + ->will($this->returnValue($response)); + + // Set up a redirect that will not be called. + $redirect = $this->getMockBuilder('Symfony\Component\HttpFoundation\RedirectResponse') + ->disableOriginalConstructor() + ->getMock(); + $redirect->expects($this->never()) + ->method('prepare'); + + $form_arg = $this->getMockForm($form_id, $expected_form); + $form_arg->expects($this->any()) + ->method('submitForm') + ->will($this->returnCallback(function ($form, &$form_state) use ($response, $redirect) { + // Set both the response and the redirect. + $form_state['response'] = $response; + $form_state['redirect'] = $redirect; + })); + + $form_state = array(); + $this->formBuilder->getFormId($form_arg, $form_state); + + try { + $form_state['values'] = array(); + $form_state['input']['form_id'] = $form_id; + $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE); + $this->fail('TestFormBuilder::sendResponse() was not triggered.'); + } + catch (\Exception $e) { + $this->assertSame('exit', $e->getMessage()); + } + $this->assertSame($response, $form_state['response']); + } + + /** * Tests the redirectForm() method when a redirect is expected. * * @param array $form_state diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php index 3b7a97f..44059c4 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php +++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php @@ -189,15 +189,19 @@ protected function getMockForm($form_id, $expected_form = NULL, $count = 1) { * The form object. * @param array $form_state * An associative array containing the current state of the form. + * @param bool $programmed + * Whether $form_state['programmed'] should be set to TRUE or not. If it is + * not set to TRUE, you must provide additional data in $form_state for the + * submission to take place. * * @return array * The built form. */ - protected function simulateFormSubmission($form_id, FormInterface $form_arg, array &$form_state) { + protected function simulateFormSubmission($form_id, FormInterface $form_arg, array &$form_state, $programmed = TRUE) { $form_state['build_info']['callback_object'] = $form_arg; $form_state['build_info']['args'] = array(); $form_state['input']['op'] = 'Submit'; - $form_state['programmed'] = TRUE; + $form_state['programmed'] = $programmed; $form_state['submitted'] = TRUE; return $this->formBuilder->buildForm($form_id, $form_state); }