diff --git a/config/default/dashboard.dashboard.dashboard.yml b/config/default/dashboard.dashboard.dashboard.yml index 4876a3419..8894f9e3e 100644 --- a/config/default/dashboard.dashboard.dashboard.yml +++ b/config/default/dashboard.dashboard.dashboard.yml @@ -59,11 +59,11 @@ layout: label: 'Section one col' context_mapping: { } components: - b393947f-61c3-4752-939e-6c084e51b70f: - uuid: b393947f-61c3-4752-939e-6c084e51b70f + fdff49bf-c002-4f14-a705-911da9e979a8: + uuid: fdff49bf-c002-4f14-a705-911da9e979a8 region: content configuration: - id: 'views_block:site_dashboard_active_site_editors-block_1' + id: 'views_block:editoria11y_results-block_top_results' label: '' label_display: visible provider: views @@ -71,13 +71,24 @@ layout: views_label: '' items_per_page: none exposed: { } - weight: 0 + weight: 7 additional: { } - 50ecf2ae-f119-4527-89bc-78bdb5a63e7b: - uuid: 50ecf2ae-f119-4527-89bc-78bdb5a63e7b + c2e96c4c-3687-40e8-baba-030bd9a7ea35: + uuid: c2e96c4c-3687-40e8-baba-030bd9a7ea35 region: content configuration: - id: 'views_block:new_default_image_alt_text-block_1' + id: hs_dashboard_hsdp_announcements + label: 'HSDP Announcements' + label_display: visible + provider: hs_dashboard + context_mapping: { } + weight: 8 + additional: { } + b393947f-61c3-4752-939e-6c084e51b70f: + uuid: b393947f-61c3-4752-939e-6c084e51b70f + region: content + configuration: + id: 'views_block:site_dashboard_active_site_editors-block_1' label: '' label_display: visible provider: views @@ -85,13 +96,13 @@ layout: views_label: '' items_per_page: none exposed: { } - weight: 1 + weight: 2 additional: { } - fdff49bf-c002-4f14-a705-911da9e979a8: - uuid: fdff49bf-c002-4f14-a705-911da9e979a8 + 50ecf2ae-f119-4527-89bc-78bdb5a63e7b: + uuid: 50ecf2ae-f119-4527-89bc-78bdb5a63e7b region: content configuration: - id: 'views_block:editoria11y_results-block_top_results' + id: 'views_block:new_default_image_alt_text-block_1' label: '' label_display: visible provider: views @@ -99,7 +110,7 @@ layout: views_label: '' items_per_page: none exposed: { } - weight: 2 + weight: 6 additional: { } 639187d0-99c0-40de-a51c-1adfcfc5a258: uuid: 639187d0-99c0-40de-a51c-1adfcfc5a258 diff --git a/docroot/modules/humsci/hs_dashboard/hs_dashboard.services.yml b/docroot/modules/humsci/hs_dashboard/hs_dashboard.services.yml index ba369d59a..ccf15141b 100644 --- a/docroot/modules/humsci/hs_dashboard/hs_dashboard.services.yml +++ b/docroot/modules/humsci/hs_dashboard/hs_dashboard.services.yml @@ -2,3 +2,6 @@ services: hs_dashboard.imports_info_manager: class: Drupal\hs_dashboard\ImportsInfoManager arguments: ['@entity_type.manager', '@config.factory'] + hs_dashboard.announcements_manager: + class: Drupal\hs_dashboard\AnnouncementsManager + arguments: ['@http_client', '@logger.factory', '@file_system', '@date.formatter'] diff --git a/docroot/modules/humsci/hs_dashboard/src/AnnouncementsManager.php b/docroot/modules/humsci/hs_dashboard/src/AnnouncementsManager.php new file mode 100644 index 000000000..b52d10ab4 --- /dev/null +++ b/docroot/modules/humsci/hs_dashboard/src/AnnouncementsManager.php @@ -0,0 +1,291 @@ +httpClient = $http_client; + $this->logger = $logger_factory->get('hs_dashboard'); + $this->fileSystem = $file_system; + $this->dateFormatter = $date_formatter; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('http_client'), + $container->get('logger.factory'), + $container->get('file_system'), + $container->get('date.formatter'), + ); + } + + /** + * Retrieves the CSV from Google Sheets. + * + * @param string $url + * The URL of the CSV file to retrieve. + * + * @return array + * Returns a csv array - if one is not found, an empty array is returned. + */ + private function getCsvAnnouncements($url): array { + try { + $response = $this->httpClient->request('GET', $url, [ + 'headers' => [ + 'Accept' => 'text/csv', + ], + 'timeout' => 10, + ]); + + if ($response->getStatusCode() !== 200) { + $this->logger->error('Invalid response status from {url} { message }: ', [ + 'url' => $url, + 'message' => $response->getStatusCode(), + ]); + throw new \Exception('Invalid response status: ' . $response->getStatusCode()); + } + + $csv_content = $response->getBody()->getContents(); + return $this->parseCsv($csv_content); + + } + catch (RequestException $e) { + $this->logger->error('Error retrieving CSV from {url}: {message}', [ + 'url' => $url, + 'message' => $e->getMessage(), + ]); + return []; + } + } + + /** + * Parses a CSV file. + * + * @param string $csv_content + * The CSV content. + * + * @return array + * An array of CSV data. + */ + private function parseCsv(string $csv_content): array { + $rows = []; + + $temp_file_path = 'temporary://csv_import.csv'; + + $file_uri = $this->fileSystem->saveData($csv_content, $temp_file_path, FileExists::Replace); + + if ($file_uri) { + $handle = fopen($this->fileSystem->realpath($file_uri), 'r'); + + if ($handle !== FALSE) { + // Skip first header row. + $first_row = TRUE; + while (($data = fgetcsv($handle)) !== FALSE) { + if ($first_row) { + $first_row = FALSE; + continue; + } + + // Removes empty rows. + if (empty($data[1]) || empty($data[3])) { + continue; + } + + if (isset($data[1])) { + $data[1] = $this->convertDateToTimestamp(trim($data[1])); + } + + if (isset($data[3])) { + $data[3] = $this->convertMarkdownLinks(trim($data[3])); + } + + $rows[] = $data; + } + fclose($handle); + } + } + + // Sort by date descending. + usort($rows, function ($a, $b) { + return $b[1] <=> $a[1]; + }); + + // Convert dates. + foreach ($rows as &$row) { + $row[1] = $this->formatDate($row[1]); + } + + return $rows; + } + + /** + * Converts dates from Google Sheets into formatted dates. + * + * @param string $value + * A string of text to convert into a formatted date. + * + * @return int + * A Unix timestamp. + */ + private function convertDateToTimestamp(string $value): int { + $value = str_replace("\u{A0}", ' ', $value); + $date = \DateTime::createFromFormat('M d, Y', $value); + + return $date ? $date->getTimestamp() : 0; + + } + + /** + * Converts a Unix timestamp into a Drupal formatted date. + * + * @param int $timestamp + * The Unix timestamp. + * + * @return string + * A formatted Drupal date. + */ + private function formatDate(int $timestamp): string { + return $this->dateFormatter->format($timestamp, 'medium'); + } + + /** + * Convert markdown links into HTML links. + * + * @param string $text + * Text to covert from markdown into HTML. + */ + private function convertMarkdownLinks(string $text): string { + $markdown_link_regex = "/\[(.*?)\]\((https?:\/\/.*?)\)/"; + + return preg_replace_callback($markdown_link_regex, function ($matches) { + $converted_link = Link::fromTextAndUrl( + $this->t('@link_text', ['@link_text' => $matches[1]]), + Url::fromUri($matches[2]) + ); + + return $converted_link->toString()->__toString(); + }, $text); + + } + + /** + * Returns table headers. These are statically set. + * + * @return array + * An array of table headers. + */ + public function getTableHeader(): array { + + $tableHeader = [ + [ + 'data' => $this->t('Date'), + ], + [ + 'data' => $this->t('Update'), + ], + ]; + + return $tableHeader; + } + + /** + * Returns table rows based on the announcements found in csv. + * + * @return array + * An array of table rows with announcement data. + */ + public function getTableRows(): array { + $table_rows = []; + + if ($cache = \Drupal::cache()->get('hs_dashboard_csv_announcements')) { + return $cache->data; + } + $csv_data = $this->getCsvAnnouncements(static::ANNOUNCEMENTS_CSV); + + foreach ($csv_data as $row) { + $table_rows[] = [ + 'data' => [ + ['data' => $row[1]], + ['data' => ['#markup' => $row[3]]], + ], + ]; + } + + \Drupal::cache()->set('hs_dashboard_csv_announcements', $table_rows, time() + 120); + return $table_rows; + } + +} diff --git a/docroot/modules/humsci/hs_dashboard/src/Plugin/Block/HsdpAnnouncementsBlock.php b/docroot/modules/humsci/hs_dashboard/src/Plugin/Block/HsdpAnnouncementsBlock.php new file mode 100644 index 000000000..a9d7e9cb2 --- /dev/null +++ b/docroot/modules/humsci/hs_dashboard/src/Plugin/Block/HsdpAnnouncementsBlock.php @@ -0,0 +1,78 @@ +announcementsManager = $announcements_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('hs_dashboard.announcements_manager'), + ); + } + + /** + * {@inheritdoc} + */ + public function build(): array { + $rows = $this->announcementsManager->getTableRows(); + + if (!$rows) { + $build['content']['#theme'] = 'markup'; + $build['content']['#markup'] = $this->t('There were no announcements found.'); + } + else { + $build['content']['#theme'] = 'table'; + $build['content']['#header'] = $this->announcementsManager->getTableHeader(); + $build['content']['#rows'] = $rows; + } + + return $build; + } + +}