diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/Data.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/Data.php new file mode 100644 index 0000000..c038810 --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/Data.php @@ -0,0 +1,169 @@ +contentType = $content_type; + } + + /** + * Gets the request content type. + * + * This will return the overriden content type if set, otherwise returns the + * content type from the request. + */ + public function getContentType() { + if (isset($this->contentType)) { + return $this->contentType; + } + + // Return the content type based on the request object. + $negotiation = drupal_container()->get('content_negotiation'); + $request = drupal_container()->get('request'); + $content_type = $negotiation->getContentType($request); + + return $request->getMimeType($content_type); + } + + /** + * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::defineOptions(). + */ + protected function defineOptions() { + $options = parent::defineOptions(); + + // Set the default style plugin to 'json'. + $options['style']['contains']['type']['default'] = 'serialize'; + $options['row']['contains']['type']['default'] = 'data_entity'; + $options['defaults']['default']['style'] = FALSE; + $options['defaults']['default']['row'] = FALSE; + + return $options; + } + + /** + * Overrides Drupal\views\Plugin\views\display\PathPluginBase::optionsSummary(). + */ + public function optionsSummary(&$categories, &$options) { + parent::optionsSummary($categories, $options); + + unset($categories['page']); + // Hide some settings, as they aren't useful for pure data output. + unset($options['hide_admin_links'], $options['analyze-theme']); + + $categories['path'] = array( + 'title' => t('Path settings'), + 'column' => 'second', + 'build' => array( + '#weight' => -10, + ), + ); + + $options['path']['category'] = 'path'; + $options['path']['title'] = t('Path'); + } + + /** + * Overrides Drupal\views\Plugin\views\display\PathPluginBase::execute(). + */ + public function execute() { + parent::execute(); + + $output = $this->view->render(); + + $response = new Response($output, 200, array('Content-type' => $this->getContentType())); + + return $response; + } + + /** + * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::render(). + */ + public function render() { + $output = $this->view->style_plugin->render(); + + if (!empty($this->view->live_preview)) { + return '
' . $output . '
'; + } + + return $output; + } + +} diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/row/DataEntityRow.php b/core/modules/views/lib/Drupal/views/Plugin/views/row/DataEntityRow.php new file mode 100644 index 0000000..c635e5e --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Plugin/views/row/DataEntityRow.php @@ -0,0 +1,41 @@ +_entity; + } + +} diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/row/DataFieldRow.php b/core/modules/views/lib/Drupal/views/Plugin/views/row/DataFieldRow.php new file mode 100644 index 0000000..84031d2 --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Plugin/views/row/DataFieldRow.php @@ -0,0 +1,127 @@ +options['aliases'])) { + // Prepare a trimmed version of replacement aliases. + $this->replacementAliases = array_map('trim', (array) $this->options['aliases']); + } + } + + /** + * Overrides \Drupal\views\Plugin\views\row\RowPluginBase::buildOptionsForm(). + */ + protected function defineOptions() { + $options = parent::defineOptions(); + $options['aliases'] = array('default' => array()); + + return $options; + } + + + /** + * Overrides \Drupal\views\Plugin\views\row\RowPluginBase::buildOptionsForm(). + */ + public function buildOptionsForm(&$form, &$form_state) { + parent::buildOptionsForm($form, $form_state); + + $form['aliases'] = array( + '#type' => 'fieldset', + '#title' => t('Field ID aliases'), + '#description' => t('Rename views default field ID\'s in the output data.'), + '#tree' => TRUE, + ); + + if ($fields = $this->view->display_handler->getOption('fields')) { + $options = $this->options; + + foreach ($fields as $id => $field) { + $form['aliases'][$id] = array( + '#type' => 'textfield', + '#title' => $id, + '#default_value' => isset($options['aliases'][$id]) ? $options['aliases'][$id] : '', + ); + } + } + } + + /** + * Overrides \Drupal\views\Plugin\views\row\RowPluginBase::validateOptionsForm(). + */ + public function validateOptionsForm(&$form, &$form_state) { + $aliases = $form_state['values']['row_options']['aliases']; + if (array_unique($aliases) !== $aliases) { + form_set_error('aliases', t('All field aliases must be unique')); + } + + } + + /** + * Overrides \Drupal\views\Plugin\views\row\RowPluginBase::render(). + */ + public function render($row) { + $output = array(); + + foreach ($this->view->field as $id => $field) { + $output[$this->getFieldKeyAlias($id)] = $row->{$field->field_alias}; + } + + return $output; + } + + /** + * Return an alias for a field ID, as set in the options form. + * + * @param string $id + * The field id to lookup an alias for. + * + * @return string + * The matches user entered alias, or the original ID if nothing is found. + */ + public function getFieldKeyAlias($id) { + if (isset($this->replacementAliases[$id])) { + return $this->replacementAliases[$id]; + } + return $id; + } + +} diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/style/Serialize.php b/core/modules/views/lib/Drupal/views/Plugin/views/style/Serialize.php new file mode 100644 index 0000000..6f18d0f --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Plugin/views/style/Serialize.php @@ -0,0 +1,62 @@ +get('serializer'); + $request = drupal_container()->get('request'); + $negotiation = drupal_container()->get('content_negotiation'); + + $rows = array(); + // If the Data Entity row plugin is used, this will be an array of entities + // which will pass through Serializer to one of the registered Normalizers, + // which will transform it to arrays/scalars. If the Data field row plugin + // is used, $rows will not contain objects and will pass directly to the + // Encoder. + foreach ($this->view->result as $row) { + $rows[] = $this->row_plugin->render($row); + } + + $content_type = $negotiation->getContentType($request); + $this->displayHandler->setContentType($request->getMimeType($content_type)); + + return $serializer->serialize($rows, $content_type); + } + +} diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/StyleSerializeTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/StyleSerializeTest.php new file mode 100644 index 0000000..813510b --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/StyleSerializeTest.php @@ -0,0 +1,127 @@ + 'Style: Serialize plugin', + 'description' => 'Tests the serialize style plugin.', + 'group' => 'Views Plugins', + ); + } + + protected function setUp() { + parent::setUp(); + + for ($i = 1; $i <= 10; $i++) { + $this->drupalCreateNode(); + } + + $this->enableViewsTestModule(); + + $this->adminUser = $this->drupalCreateUser(array('administer views', 'bypass node access', 'access user profiles', 'view revisions')); + } + + /** + * Checks the behavior of serialize callback paths and row plugins. + */ + public function testSerializeResponses() { + // Test the serialize callback. + $view = views_get_view('test_serialize_display_field'); + $view->initDisplay(); + $this->executeView($view); + + $actual_json = $this->drupalGet('test/serialize/field', array(), array('Content-type: application/json')); + $this->assertResponse(200); + + // Test the http Content-type. + $headers = $this->drupalGetHeaders(); + $this->assertEqual($headers['content-type'], 'application/json', 'The header Content-type is correct.'); + + $expected = array(); + foreach ($view->result as $row) { + $expected_row = array(); + foreach ($view->field as $id => $field) { + $expected_row[$id] = $row->{$field->field_alias}; + } + $expected[] = $expected_row; + } + + $this->assertIdentical($actual_json, json_encode($expected), 'The expected JSON output was found.'); + + // Test a 403 callback. + $this->drupalGet('test/serialize/denied'); + $this->assertResponse(403); + + // Test the entity rows. + + $view = views_get_view('test_serialize_display_entity'); + $view->initDisplay(); + $this->executeView($view); + + $expected = array(); + foreach ($view->result as $row) { + $expected[] = $row->_entity; + } + + $actual_json = $this->drupalGet('test/serialize/entity', array(), array('Content-type: application/json')); + $this->assertResponse(200); + + $this->assertIdentical($actual_json, json_encode($expected), 'The expected JSON output was found.'); + } + + /** + * Test the field ID alias functionality of the DataFieldRow plugin. + */ + public function testUIFieldAlias() { + $this->drupalLogin($this->adminUser); + + // Test the UI settings for adding field ID aliases. + $this->drupalGet('admin/structure/views/view/test_serialize_display_field/edit/data_1'); + $row_options = 'admin/structure/views/nojs/display/test_serialize_display_field/data_1/row_options'; + $this->assertLinkByHref($row_options); + + // Add a random alias for the name field and save the view. + $random_name = $this->randomName(); + $this->drupalPost($row_options, array('row_options[aliases][name]' => $random_name), t('Apply')); + $this->drupalPost(NULL, array(), t('Save')); + + $view = views_get_view('test_serialize_display_field'); + $view->setDisplay('data_1'); + $this->executeView($view); + + $expected = array(); + foreach ($view->result as $row) { + $expected_row = array(); + foreach ($view->field as $id => $field) { + $expected_row[$random_name] = $row->{$field->field_alias}; + } + $expected[] = $expected_row; + } + + $this->assertIdentical($this->drupalGet('test/serialize/field', array(), array('Content-type: application/json')), json_encode($expected)); + } + +} diff --git a/core/modules/views/lib/Drupal/views/ViewExecutable.php b/core/modules/views/lib/Drupal/views/ViewExecutable.php index 639cd92..e3f2d3d 100644 --- a/core/modules/views/lib/Drupal/views/ViewExecutable.php +++ b/core/modules/views/lib/Drupal/views/ViewExecutable.php @@ -1228,9 +1228,6 @@ public function render($display_id = NULL) { drupal_theme_initialize(); $config = config('views.settings'); - // Set the response so other parts can alter it. - $this->response = new Response('', 200); - $start = microtime(TRUE); if (!empty($this->live_preview) && $config->get('ui.show.additional_queries')) { $this->startQueryCapture(); @@ -1261,6 +1258,11 @@ public function render($display_id = NULL) { // Initialize the style plugin. $this->initStyle(); + if (!isset($this->response)) { + // Set the response so other parts can alter it. + $this->response = new Response('', 200); + } + // Give field handlers the opportunity to perform additional queries // using the entire resultset prior to rendering. if ($this->style_plugin->usesFields()) { diff --git a/core/modules/views/tests/views_test_config/config/views.view.test_serialize_display_entity.yml b/core/modules/views/tests/views_test_config/config/views.view.test_serialize_display_entity.yml new file mode 100644 index 0000000..f9a3836 --- /dev/null +++ b/core/modules/views/tests/views_test_config/config/views.view.test_serialize_display_entity.yml @@ -0,0 +1,49 @@ +base_table: node +name: test_serialize_display_entity +description: '' +tag: '' +human_name: 'Test serialize display entity rows' +core: 8.x +api_version: '3.0' +display: + default: + display_plugin: default + id: default + display_title: Master + position: '' + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: none + query: + type: views_query + exposed_form: + type: basic + style: + type: serialize + row: + type: data_entity + sorts: + created: + id: created + table: node + field: created + order: DESC + title: 'Test serialize' + arguments: { } + data_1: + display_plugin: data + id: data_1 + display_title: serialize + position: '' + display_options: + defaults: + access: false + path: test/serialize/entity +base_field: nid +disabled: '0' +module: views +langcode: und diff --git a/core/modules/views/tests/views_test_config/config/views.view.test_serialize_display_field.yml b/core/modules/views/tests/views_test_config/config/views.view.test_serialize_display_field.yml new file mode 100644 index 0000000..a3214e5 --- /dev/null +++ b/core/modules/views/tests/views_test_config/config/views.view.test_serialize_display_field.yml @@ -0,0 +1,82 @@ +base_table: views_test_data +name: test_serialize_display_field +description: '' +tag: '' +human_name: 'Test serialize display field rows' +core: 8.x +api_version: '3.0' +display: + default: + display_plugin: default + id: default + display_title: Master + position: '' + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: none + query: + type: views_query + exposed_form: + type: basic + style: + type: serialize + row: + type: data_field + fields: + name: + id: name + table: views_test_data + field: name + label: '' + sorts: + created: + id: created + table: views_test_data + field: created + order: DESC + title: 'Test serialize' + arguments: { } + data_1: + display_plugin: data + id: data_1 + display_title: serialize + position: '' + display_options: + defaults: + access: false + style: false + row: false + path: test/serialize/field + access: + type: none + style: + type: serialize + row: + type: data_field + data_2: + display_plugin: data + id: data_2 + display_title: 'serialize - access denied' + position: '' + display_options: + defaults: + access: false + style: false + row: false + path: test/serialize/denied + access: + type: perm + options: + perm: 'administer views' + style: + type: serialize + row: + type: data_field +base_field: id +disabled: '0' +module: views +langcode: und diff --git a/core/modules/views/views_ui/lib/Drupal/views_ui/ViewEditFormController.php b/core/modules/views/views_ui/lib/Drupal/views_ui/ViewEditFormController.php index 853d5c9..ae362ff 100644 --- a/core/modules/views/views_ui/lib/Drupal/views_ui/ViewEditFormController.php +++ b/core/modules/views/views_ui/lib/Drupal/views_ui/ViewEditFormController.php @@ -817,8 +817,14 @@ public function getFormBucket(ViewUI $view, $type, $display) { // Different types now have different rearrange forms, so we use this switch // to get the right one. + $rearrange_url = "admin/structure/views/nojs/rearrange/{$view->get('name')}/{$display['id']}/$type"; + $rearrange_text = t('Rearrange'); + $class = 'icon compact rearrange'; + switch ($type) { case 'filter': + // The rearrange form for filters contains the and/or UI, so override + // the used path. $rearrange_url = "admin/structure/views/nojs/rearrange-$type/{$view->get('name')}/{$display['id']}/$type"; $rearrange_text = t('And/Or, Rearrange'); // TODO: Add another class to have another symbol for filter rearrange. @@ -836,6 +842,7 @@ public function getFormBucket(ViewUI $view, $type, $display) { ); return $build; } + break; case 'header': case 'footer': case 'empty': @@ -847,10 +854,7 @@ public function getFormBucket(ViewUI $view, $type, $display) { ); return $build; } - default: - $rearrange_url = "admin/structure/views/nojs/rearrange/{$view->get('name')}/{$display['id']}/$type"; - $rearrange_text = t('Rearrange'); - $class = 'icon compact rearrange'; + break; } // Create an array of actions to pass to theme_links