src/Plugin/Field/FieldWidget/LocalistUrlWidget.php
File `LocalistUrlWidget.php` has 295 lines of code (exceeds 250 allowed). Consider refactoring.<?php namespace Drupal\stanford_fields\Plugin\Field\FieldWidget; use Drupal\Component\Utility\NestedArray;use Drupal\Core\Cache\Cache;use Drupal\Core\Cache\CacheBackendInterface;Unused use statementuse Drupal\Core\Field\Attribute\FieldWidget;use Drupal\Core\Field\FieldDefinitionInterface;use Drupal\Core\Field\FieldItemListInterface;use Drupal\Core\Form\FormStateInterface;use Drupal\Core\Render\Element\Url as UrlElement;use Drupal\Core\StringTranslation\TranslatableMarkup;use Drupal\Core\Url;use Drupal\link\Plugin\Field\FieldWidget\LinkWidget;use GuzzleHttp\ClientInterface;use Symfony\Component\DependencyInjection\ContainerInterface;use GuzzleHttp\Promise\Utils; /** * Plugin implementation of the 'localist_url' widget. */Perl-style comments are not allowed; use "// Comment" instead#[FieldWidget(`syntax error, unexpected ','`
Line indented incorrectly; expected 0 spaces, found 2 id: 'localist_url', label: new TranslatableMarkup('Localist URL'), field_types: ['link'],)]Missing class doc commentclass LocalistUrlWidget extends LinkWidget { /** * Http Client Service. * * @var \GuzzleHttp\ClientInterface */ protected $client; /** * Caching service. * * @var \Drupal\Core\Cache\CacheBackendInterface */ protected $cache; /** * API data from Localist. * * @var array */ protected $apiData = []; /**Doc comment short description must end with a full stop
Doc comment short description must start with a capital letter * {@inheritDoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings'], $container->get('http_client'), $container->get('cache.default') ); } /**Doc comment short description must start with a capital letter
Doc comment short description must end with a full stop * {@inheritDoc} */ public static function defaultSettings() { $settings = [ 'base_url' => '', 'select_distinct' => FALSE, ]; return $settings + parent::defaultSettings(); } /**Doc comment short description must start with a capital letter
Doc comment short description must end with a full stop * {@inheritDoc} */ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ClientInterface $client, CacheBackendInterface $cache) { parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); $this->client = $client; $this->cache = $cache; } /**Doc comment short description must end with a full stop
Doc comment short description must start with a capital letter * {@inheritDoc} */ public function settingsForm(array $form, FormStateInterface $form_state) { $elements = parent::settingsForm($form, $form_state); $elements['placeholder_url']['#access'] = FALSE; $elements['placeholder_title']['#access'] = FALSE; $elements['select_distinct'] = [ '#type' => 'checkbox', '#title' => $this->t('Select Distinct'), '#default_value' => $this->getSetting('select_distinct'), ]; $elements['base_url'] = [ '#type' => 'url', '#title' => $this->t('Base localist domain'), '#required' => TRUE, '#default_value' => $this->getSetting('base_url'), '#element_validate' => [ [UrlElement::class, 'validateUrl'], [$this, 'validateUrl'], ], ]; return $elements; } /** * Validate the given domain has a localist API response. * * @param array $element * Url form element. * @param \Drupal\Core\Form\FormStateInterface $form_state * Current form state object. * @param array $complete_form * Complete form. */ public function validateUrl(array &$element, FormStateInterface $form_state, array &$complete_form) { $input = NestedArray::getValue($form_state->getValues(), $element['#parents']); if ($form_state::hasAnyErrors()) { return; } try { $response = $this->client->request('GET', '/api/2/events', ['base_uri' => $input]); json_decode((string) $response->getBody(), TRUE, 512, JSON_THROW_ON_ERROR); } catch (\Throwable $e) { $form_state->setError($element, $this->t('URL is not a Localist domain.')); } } /**Doc comment short description must start with a capital letter
Doc comment short description must end with a full stop * {@inheritDoc} */ public function settingsSummary() { $summary = []; if (empty($this->getSetting('base_url'))) { $summary[] = $this->t('No Base URL Provided'); } else { $summary[] = $this->t('Base URL: @url', ['@url' => $this->getSetting('base_url')]); } return $summary; } /**Doc comment short description must end with a full stop
Doc comment short description must start with a capital letter * {@inheritDoc} */Method `formElement` has 41 lines of code (exceeds 40 allowed). Consider refactoring. public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { $element = parent::formElement($items, $delta, $element, $form, $form_state); // Fallback to inherited link widget if the base_url is not set. if (!$this->getSetting('base_url')) { return $element; } try { $this->getApiData(); } catch (\Throwable $e) { $this->messenger() ->addError('Unable to fetch data from the system to provide easy to use field options. Please try again later.'); return $element; } $element['uri']['#access'] = FALSE; $element['title']['#access'] = FALSE; $element['attributes']['#access'] = FALSE; $item = $items[$delta]; $element['filters'] = [ '#type' => 'details', '#title' => $this->t('Filters'), '#open' => TRUE, '#collapsible' => FALSE, ]; $query_parameters = []; if ($item->uri) { parse_str(parse_url(urldecode($item->uri), PHP_URL_QUERY), $query_parameters); } $element['filters']['group_id'] = $this->getGroups($query_parameters['group_id'] ?? NULL); $element['filters']['venue_id'] = $this->getPlaces($query_parameters['venue_id'] ?? NULL); $element['filters']['type'] = $this->getFilters($query_parameters['type'] ?? []); $element['filters']['match'] = [ '#type' => 'select', '#title' => $this->t('Content Must Match'), '#default_value' => $query_parameters['match'] ?? NULL, '#empty_option' => $this->t('At least one selected group or venue, and one selected filter item'), '#options' => [ 'any' => $this->t('Any selected group, venue, or filter item'), 'all' => $this->t('At least one selected group or venue, and all selected filter items'), 'or' => $this->t('Any selected group or venue, and one selected filter item'), ], ]; return $element; } /**Doc comment short description must start with a capital letter
Doc comment short description must end with a full stop * {@inheritDoc} */Function `massageFormValues` has a Cognitive Complexity of 12 (exceeds 10 allowed). Consider refactoring. public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { if (!$this->getSetting('base_url')) { return parent::massageFormValues($values, $form, $form_state); } foreach ($values as $delta => &$value) { foreach ($value['filters'] as &$filter_values) { if (is_array($filter_values)) { $filter_values = self::flattenValues($filter_values); } } $value['filters'] = array_filter($value['filters']); if (empty($value['filters'])) { unset($values[$delta]); continue; } $value['filters']['days'] = '365'; $value['filters']['pp'] = '100'; // We may in the future have a configuration value // to include the "distinct"key to our API call. // This tries to find such a value, // and applies the key if it finds it. if ($this->getSetting('select_distinct')) { $value['filters']['distinct'] = TRUE; } $value['uri'] = Url::fromUri(rtrim($this->getSetting('base_url'), '/') . '/api/2/events', ['query' => $value['filters']]) ->toString(); } return parent::massageFormValues($values, $form, $form_state); } /** * Flatten a multidimensional array. * * @param array $array * The array to flatten. * * @return array * Flattened array. */ protected static function flattenValues(array $array): array { $return = []; array_walk_recursive($array, function ($a) use (&$return) { $return[] = $a; }); return $return; } /** * Get the form element with the filters from localist. * * @param array $default_value * Default value for the form elements. * * @return array * Form element render array. */ protected function getFilters(array $default_value = []): array { $element = []; foreach ($this->apiData['events/filters'] ?? [] as $filter_key => $options) { $filter_options = []; foreach ($options as $option) { $filter_options[$option['id']] = $option['name']; } asort($filter_options); $element[$filter_key] = [ '#type' => 'select', '#title' => $this->apiData['events/labels']['filters'][$filter_key], '#multiple' => TRUE, '#options' => $filter_options, '#default_value' => array_intersect($default_value, array_keys($filter_options)), '#chosen' => TRUE, ]; } return $element; } /** * Gets groups and departments. * * @param string|null $default_value * Default value for the form elements. * * @return array * Form element render array. */ protected function getGroups(?string $default_value = NULL): array { $element = [ '#type' => 'select', '#title' => $this->t('Departments/Groups'), '#multiple' => FALSE, '#options' => [], '#empty_option' => 'Select one:', '#default_value' => $default_value, ]; foreach ($this->apiData['groups'] ?? [] as $group) { $element['#options'][$group['group']['id']] = $group['group']['name']; } foreach ($this->apiData['departments'] ?? [] as $department) { $element['#options'][$department['department']['id']] = $department['department']['name']; } asort($element['#options']); return $element; } /** * Get the form element for the venues selection. * * @param string|null $default_value * Default value for the form element. * * @return array * Form element render array. */ protected function getPlaces(?string $default_value = NULL): array { $element = [ '#type' => 'select', '#title' => $this->t('Venues'), '#multiple' => FALSE, '#options' => [], '#empty_option' => 'Select one:', '#default_value' => $default_value, ]; foreach ($this->apiData['places'] ?? [] as $place) { $element['#options'][$place['place']['id']] = $place['place']['name']; } return $element; } /** * Get the data from the localist API. */ protected function getApiData() { // Data was already fetched. if ($this->apiData) { return $this->apiData; } $base_url = $this->getSetting('base_url'); // Check for some cached data before we fetch it all again. if ($cache = $this->cache->get("localist_api:$base_url")) { $this->apiData = $cache->data['data']; // If the cache is not expired, return it. Otherwise, we'll attempt to // fetch from the API. Fallback is the old cached data. if ($cache->data['expires'] > time()) { return $this->apiData; } } try { $this->fetchApiData(); } catch (\Throwable $e) { if (!$this->apiData) { throw $e; } } } /** * Call the Localist API with various endpoints to gather all the data needed. * * @return array * Keyed array of api data. */ protected function fetchApiData(): array { $base_url = $this->getSetting('base_url'); $options = [ 'timeout' => 5, 'base_uri' => $base_url, 'query' => ['pp' => 1], ]; $promises = [ 'groups' => $this->client->requestAsync('GET', '/api/2/groups', $options), 'departments' => $this->client->requestAsync('GET', '/api/2/departments', $options), 'places' => $this->client->requestAsync('GET', '/api/2/places', $options), 'events/filters' => $this->client->requestAsync('GET', '/api/2/events/filters', $options), 'events/labels' => $this->client->requestAsync('GET', '/api/2/events/labels', $options), ]; $results = self::unwrapAsyncRequests($promises); foreach ($results as $key => $response) { if (empty($response['page']['total'])) { $this->apiData[$key] = $response; continue; } $this->apiData[$key] = $this->fetchPagedApiData($key, $response['page']['total']); } $this->cache->set("localist_api:$base_url", [ 'data' => $this->apiData, 'expires' => time() + 60 * 60, ], Cache::PERMANENT, ['localist_api']); return $this->apiData; } /** * Given the endpoint and count, async fetch from the API all pages. * * @param string $endpoint * Localist API Endpoint. * @param int $total_count * Total number of items to chunk up. * * @return array * Indexed array of api data. */ protected function fetchPagedApiData($endpoint, $total_count): array { $base_url = $this->getSetting('base_url'); $options = [ 'timeout' => 5, 'base_uri' => $base_url, 'query' => ['pp' => 100], ]; $number_of_pages = ceil($total_count / 100); for ($i = 1; $i <= $number_of_pages; $i++) { $options['query']['page'] = $i; $paged_data[$i] = $this->client->requestAsync('GET', '/api/2/' . $endpoint, $options); } $paged_data = self::unwrapAsyncRequests($paged_data); $data = []; foreach ($paged_data as $page) { unset($page['page']); $key = key($page); $data = array_merge($data, $page[$key]); } return $data; } /** * Unwrap async promises and decode their body data. * * @param \GuzzleHttp\Promise\PromiseInterface[] $promises * Associative array of Guzzle promises. * * @return array * Associative array of json decoded data. */ protected static function unwrapAsyncRequests(array $promises): array { $promises = Utils::unwrap($promises); /** @var \GuzzleHttp\Psr7\Response $response */ foreach ($promises as &$response) { $response = json_decode((string) $response->getBody(), TRUE, 512, JSON_THROW_ON_ERROR); } return $promises; } }