/** * Test Views admin UI and field handlers. */ public function testViewsAdmin() { $admin_user = $this->drupalCreateUser(array('administer search_api', 'access administration pages', 'administer views')); $this->drupalLogin($admin_user); $this->drupalGet('admin/structure/views/view/search_api_test_view'); $this->assertResponse(200); // Switch to "Fields" row style. $this->clickLink($this->t('Rendered entity')); $this->assertResponse(200); $edit = array('row[type]' => 'fields'); $this->drupalPostForm(NULL, $edit, $this->t('Apply')); $this->assertResponse(200); $this->drupalPostForm(NULL, array(), $this->t('Apply')); $this->assertResponse(200); // Add the "User ID" relationship. $this->clickLink($this->t('Add relationships')); $edit = array('name[search_api_datasource_database_search_index_entity_entity_test.user_id]' => 'search_api_datasource_database_search_index_entity_entity_test.user_id'); $this->drupalPostForm(NULL, $edit, $this->t('Add and configure relationships')); $this->drupalPostForm(NULL, array(), $this->t('Apply')); // Add new fields. First check that the listing seems correct. $this->clickLink($this->t('Add fields')); $this->assertResponse(200); $this->assertText($this->t('Test entity datasource')); $this->assertText($this->t('Authored on')); $this->assertText($this->t('Body (indexed field)')); $this->assertText($this->t('Index Test index')); $this->assertText($this->t('Entity ID')); $this->assertText($this->t('Excerpt')); $this->assertText($this->t('The search result excerpted to show found search terms')); $this->assertText($this->t('Relevance')); $this->assertText($this->t('The relevance of this search result with respect to the query')); $this->assertText($this->t('Language code')); $this->assertText($this->t('The user language code.')); $this->assertText($this->t('(No description available)')); $this->assertNoText($this->t('Error: missing help')); // Then add some fields. $fields = array('views.counter', 'search_api_datasource_database_search_index_entity_entity_test.id', 'search_api_index_database_search_index.search_api_datasource', 'search_api_datasource_database_search_index_entity_entity_test.body', 'search_api_index_database_search_index.category', 'search_api_index_database_search_index.keywords', 'search_api_datasource_database_search_index_entity_entity_test.user_id'); $edit = array(); foreach ($fields as $field) { $edit["name[{$field}]"] = $field; } $this->drupalPostForm(NULL, $edit, $this->t('Add and configure fields')); $this->assertResponse(200); for ($i = 0; $i < count($fields); ++$i) { $this->submitFieldsForm(); } // Save the view. $this->drupalPostForm(NULL, array(), $this->t('Save')); $this->assertResponse(200); // Check the results. $this->drupalGet('search-api-test'); $this->assertResponse(200); foreach ($this->entities as $id => $entity) { $fields = array('search_api_datasource', 'id', 'body', 'category', 'keywords'); foreach ($fields as $field) { if ($field != 'search_api_datasource') { $data = Utility::extractFieldValues($entity->get($field)); if (!$data) { $data = array('[EMPTY]'); } } else { $data = array('entity:entity_test'); } $prefix = "#{$id} [{$field}] "; $text = $prefix . implode("|{$prefix}", $data); $this->assertText($text, "Correct value displayed for field {$field} on entity #{$id} (\"{$text}\")"); } } }
/** * Runs before any fields are rendered. * * This gives the handlers some time to set up before any handler has * been rendered. * * @param \Drupal\views\ResultRow[] $values * An array of all ResultRow objects returned from the query. * * @see \Drupal\views\Plugin\views\field\FieldHandlerInterface::preRender() */ public function preRender(&$values) { // We deal with the properties one by one, always loading the necessary // values for any nested properties coming afterwards. // @todo This works quite well, but will load each item/entity individually. // Instead, we should exploit the workflow of proceeding by each property // on its own to multi-load as much as possible (maybe even entities of // the same type from different properties). // @todo Also, this will unnecessarily load items/entities even if all // required fields are provided in the results. However, to solve this, // expandRequiredProperties() would have to provide more information, or // provide a separate properties list for each row. foreach ($this->expandRequiredProperties() as $datasource_id => $properties) { foreach ($properties as $property_path => $combined_property_path) { // Determine the path of the parent property, and the property key to // take from it for this property. If the name is "_object", we just // wanted the parent object to be loaded, so we might be done – except // when the parent is empty, in which case we wanted to load the // original search result, which we haven't done yet. list($parent_path, $name) = Utility::splitPropertyPath($property_path); if ($parent_path && $name == '_object') { continue; } // Now go through all rows and add the property to them, if necessary. foreach ($values as $i => $row) { // Bail for rows with the wrong datasource for this property, or for // which this field doesn't even apply (which will usually be the // same, though). if ($datasource_id != $row->search_api_datasource || !$this->isActiveForRow($row)) { continue; } // Check whether there are parent objects present. If no, either load // them (in case the parent is the result item itself) or bail. if (empty($row->_relationship_objects[$parent_path])) { if ($parent_path) { continue; } else { $row->_relationship_objects[$parent_path] = array($row->_item->getOriginalObject()); } } // If the property key is "_object", we just needed to load the search // result item, so we're now done. if ($name == '_object') { continue; } // Determine whether we want to set field values for this property on // this row. This is the case if the property is one of the explicitly // retrieved properties and not yet set on the result row object. $set_values = isset($this->retrievedProperties[$datasource_id][$property_path]) && !isset($row->{$combined_property_path}); if (empty($row->_relationship_objects[$property_path])) { // Iterate over all parent objects to get their typed data for this // property and to extract their values. $row->_relationship_objects[$property_path] = array(); foreach ($row->_relationship_objects[$parent_path] as $parent) { // Follow references. while ($parent instanceof DataReferenceInterface) { $parent = $parent->getTarget(); } // At this point we need the parent to be a complex item, // otherwise it can't have any children (and thus, our property // can't be present). if (!$parent instanceof ComplexDataInterface) { continue; } // Add the typed data for the property to our relationship objects // for this property path. To treat list properties correctly // regarding possible child properties, add all the list items // individually. try { $typed_data = $parent->get($name); // If the typed data is an entity, check whether the current // user can access it. $value = $typed_data->getValue(); if ($value instanceof EntityInterface) { if (!isset($account)) { $account = $this->getQuery()->getAccessAccount(); } if (!$value->access('view', $account)) { continue; } } if ($typed_data instanceof ListInterface) { foreach ($typed_data as $item) { $row->_relationship_objects[$property_path][] = $item; } } else { $row->_relationship_objects[$property_path][] = $typed_data; } } catch (\InvalidArgumentException $e) { // This can easily happen, e.g., when requesting a field that // only exists on a different bundle. Unfortunately, there is no // ComplexDataInterface::hasProperty() method, so we can only // catch and ignore the exception. } } } // Initially the array of values, if we want to set them. if ($set_values) { $row->{$combined_property_path} = array(); } // Iterate over the typed data objects, extract their values and set // the relationship objects for the next iteration of the outer loop // over properties. foreach ($row->_relationship_objects[$property_path] as $typed_data) { if ($set_values) { $row->{$combined_property_path}[] = Utility::extractFieldValues($typed_data); } } // If we just set any field values on the result row, clean them up // by merging them together (currently it's an array of arrays, but it // should be just a flat array). if ($set_values && $row->{$combined_property_path}) { $row->{$combined_property_path} = call_user_func_array('array_merge', $row->{$combined_property_path}); } } } } }