<?php namespace Drupal\uw_cfg_common\Service; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Field\EntityReferenceFieldItemListInterface; use Drupal\Core\Url; use Drupal\node\Entity\Node; use Drupal\Core\Entity\EntityTypeManagerInterface; use Symfony\Component\HttpFoundation\RequestStack; /** * Class UwFieldValue. * * Gets the data out from individual fields of a node. * * @package Drupal\uw_cfg_common\Service */ class UwNodeFieldValue { // Date format: "Thursday, December 2, 2021". const DATE_FORMAT_DATE_ONLY = 'l, F j, Y'; // Time format: "10:00 AM". const DATE_FORMAT_TIME_ONLY = 'g:i A'; // Date and time format: "Thursday, December 2, 2021 10:00 AM". const DATE_FORMAT_DATE_TIME = 'l, F j, Y g:i A'; /** * Entity type manager from core. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; /** * The current request stack. * * @var \Symfony\Component\HttpFoundation\RequestStack */ protected $requestStack; /** * The UW Service. * * @var UWServiceInterface */ protected $uwService; /** * Default constructor. * * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack * The current request stack. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager * The entity type manager. * @param \Drupal\uw_cfg_common\Service\UWServiceInterface $uwService * The UW Service. */ public function __construct(RequestStack $requestStack, EntityTypeManagerInterface $entityTypeManager, UWServiceInterface $uwService) { $this->requestStack = $requestStack; $this->entityTypeManager = $entityTypeManager; $this->uwService = $uwService; } /** * Function to get the value of a node field. * * @param \Drupal\node\Entity\Node $node * The node. * @param string $view_mode * The view mode of the bnode. * @param string $type * The type of field. * @param mixed $field_name * The name of the field. * @param array|null $extra_options * Some extra options if requried. * * @return array|\Drupal\Core\GeneratedUrl|mixed|string|void|null * Array of the value of the field. * * @throws \Drupal\Core\Entity\EntityMalformedException */ public function getFieldValue(Node $node, string $view_mode, string $type, $field_name = NULL, array $extra_options = NULL) { // Address field type. if ($type == 'address') { return $this->getAddressField($node, $field_name); } // Author field type. if ($type == 'author') { return $this->getAuthor($node); } // Long text field type with a text format. if ($type == 'formatted_text') { return $this->getFormattedText($node, $field_name); } // Only content, either layout builder or summary. if ($type == 'content') { return $this->getContentField($node, $view_mode, $field_name); } // Office hours field type. if ($type == 'hours') { $hours = $node->$field_name->view('teaser'); if (isset($hours['#title'])) { return $hours; } else { return []; } } // The media for the top of the page. if ($type == 'media') { if ($node->hasField('field_uw_type_of_media')) { return [ 'type' => $node->field_uw_type_of_media->value, 'media' => $this->getMedia($node), ]; } } // Date field type (smart dates). if ($type == 'date') { return $this->getDates($node, $field_name, $view_mode); } // Select list field. if ($type == 'select') { return $node->$field_name->value; } // Taxonomy terms field type. if ($type == 'terms') { return $this->getTermsField($node, $field_name); } // Image field type. if ($type == 'image') { return $this->getImage($node, $field_name, $extra_options); } // Link field type. if ($type == 'link') { return $this->getLinkInfo($node, $field_name); } // Map (leaflet) field type. if ($type == 'map') { return $this->getMapField($node, $field_name); } // Plain text field type (textbox). if ($type == 'plain_text' || $type == 'multiple_plain_text') { if ($type == 'multiple_plain_text') { return $this->getPlainText($node, $field_name, TRUE); } else { return $this->getPlainText($node, $field_name); } } // Project members. if ($type == 'project_members') { return $this->getProjectMembers($node, $field_name); } // Source or hero image field type. if ($type == 'sources' || $type == 'hero') { return $this->getSources($node, $field_name); } // Catalog tags, this is a special type of terms, since we // need to worry about the tabs that go along with them. if ($type == 'catalog_terms') { return $this->getCatalogTags($node, $field_name); } // Timeline from smart date range. if ($type == 'timeline') { return $this->getTimeline($node, $field_name); } // Title of the node. if ($type == 'title') { return $node->getTitle(); } // URL of the node. if ($type == 'url') { return $node->toUrl()->toString(); } } /** * Function to get the media for the top of the page. * * @param \Drupal\node\Entity\Node $node * The node. * * @return array * Render array for formatted text. */ public function getMedia(Node $node): array { // Set to empty array in case we do not return anything. $media = []; // Get the specific type of media. switch ($node->field_uw_type_of_media->value) { // Get the banner. case 'banner': $media = $this->getBanner($node, 'field_uw_banner'); break; // Get the image. case 'image': $media = $this->getSources($node, 'field_uw_hero_image'); break; // Get the remote video media. case 'remote_video': $media = $this->getRemoteVideo($node, 'field_uw_remote_video'); break; } // Return the render things for the media. return $media; } /** * Function to get the banner. * * @param \Drupal\node\Entity\Node $node * The node. * @param string|null $field_name * The name of the field to get. * * @return array * Render array for formatted text. */ public function getBanner(Node $node, string $field_name = NULL): array { // Set the banners as empty in case we do not have any. $banners = []; // Ensure that there is a media with and if so get // the classes for the width. if ($node->hasField('field_uw_media_width')) { // Get the layout builder style based from the node value // of media width. $layout_builder_style = $layout_style = \Drupal::entityTypeManager()->getStorage('layout_builder_style')->load($node->field_uw_media_width->value); // Get the classes associated with the layout builder style. $media_width_classes = $layout_builder_style->getClasses(); } // Flag to use page title as big text. $use_page_title_big_text_flag = FALSE; if ($node->hasField('field_uw_page_title_big_text')) { if ($node->field_uw_page_title_big_text->value) { $use_page_title_big_text_flag = TRUE; } } // Process banner images. foreach ($node->$field_name as $banner_item) { $banner_para = $banner_item->entity; $type = $banner_para->getType(); $banner = [ 'type' => $type, 'big_text' => $use_page_title_big_text_flag ? $node->label() : $banner_para->field_uw_ban_big_text->value, 'small_text' => $banner_para->field_uw_ban_small_text->value, ]; // Link details needs to be created using for loop. foreach ($banner_para->field_uw_ban_link as $link_item) { $banner['link'] = $link_item->getUrl()->toString(); } if ($type === 'uw_para_image_banner') { $banner['image'] = $this->uwService->prepareResponsiveImage($banner_para->field_uw_ban_image->entity, 'uw_ris_media'); } elseif ($type === 'uw_para_local_video_banner') { $banner['image'] = $this->uwService->prepareResponsiveImage($banner_para->field_uw_ban_fallback_image->entity, 'uw_ris_media'); $banner['video'] = $banner_para->field_uw_ban_video->entity->field_media_file->entity->getFileUri(); } elseif ($type === 'uw_para_vimeo_video_banner') { $banner['image'] = $this->uwService->prepareResponsiveImage($banner_para->field_uw_ban_fallback_image->entity, 'uw_ris_media'); // Vimeo embed needs to be handled same way as links. if (isset($banner_para->field_uw_ban_vimeo_video->entity->field_media_oembed_video)) { foreach ($banner_para->field_uw_ban_vimeo_video->entity->field_media_oembed_video as $embed_video) { $banner['video'] = $embed_video->value; } } } // If we are using an image, set the sources and the // img_element and remove the image array element. // Also add on the alt for the image. if (isset($banner['image']['sources'])) { $banner['sources'] = $banner['image']['responsive_sources']; $banner['img_element'] = $banner['image']['img_element']['#uri']; $banner['alt'] = $banner['image']['alt']; unset($banner['image']); } $images[] = $banner; } $banners = [ 'images' => $images, 'autoplay' => $node->field_uw_autoplay->value, 'slide_speed' => $node->field_uw_slide_speed->value, 'style' => $node->field_uw_text_overlay_style->value, 'transition_speed' => $node->field_uw_transition_speed->value, 'media_width' => $media_width_classes ?? NULL, 'text_overlay' => $node->field_uw_text_overlay_style->value, 'use_page_title_big_text' => $use_page_title_big_text_flag, 'bundle' => $node->getType(), 'uuid' => uniqid(), ]; return $banners; } /** * Function to get the remote video. * * @param \Drupal\node\Entity\Node $node * The node. * @param string|null $field_name * The name of the field to get. * * @return array * Render array for formatted text. */ public function getRemoteVideo(Node $node, string $field_name = NULL): array { // Get the media object. $media = $node->$field_name->entity; // Set the remote video URL. $remote_video['view']['url'] = $media->field_media_oembed_video->value; // Check for YouTube. if (preg_match('/^https?:\/\/(?:www\.youtube(?:-nocookie)?\.com\/(watch\?(?:\S+&)?v=|embed\/|.+#.+\/)|youtu\.be\/)([\w\-]{11})(?:[#&?]\S*)?$/', $remote_video['view']['url'])) { // Set the type of video to YouTube. $remote_video['view']['type'] = 'YouTube'; } // Check for Vimeo. elseif (strrpos($remote_video['view']['url'], 'vimeo')) { // Set the type of video to Vimeo. $remote_video['view']['type'] = 'Vimeo'; } // Generic video. else { // Set the type of video to Generic. $remote_video['view']['type'] = 'Generic'; } // Set the video content to be the rendering of the media object. $remote_video['video'] = $this->entityTypeManager->getViewBuilder('media')->view($media, 'default'); return $remote_video; } /** * Function to get the formatted text. * * @param \Drupal\node\Entity\Node $node * The node. * @param string|null $field_name * The name of the field to get. * * @return array * Render array for formatted text. */ public function getFormattedText(Node $node, string $field_name = NULL): array { // Ensure that we have a field to check. if ($field_name !== NULL) { // Get the value of the field. $value = $node->$field_name->value; // If the value is not null, then return the // formatted text render array. if ($value !== NULL) { return [ '#type' => 'processed_text', '#text' => $value, '#format' => $node->$field_name->format, ]; } } // If we get here there is no value, so // return an empty array and our clean // node data function will handle it. return []; } /** * Function to get catalog tags. * * @param \Drupal\node\Entity\Node $node * The node. * @param array $field_name * The field name. * * @return array * Array of values for the field. * * @throws \Drupal\Core\Entity\EntityMalformedException */ public function getCatalogTags(Node $node, array $field_name): array { // Empty arrays so that we don't get undefined // index errors. $tabs = []; $tags = []; // Get the entity and values for the catalog, // which is the taxonomy term catalog. $catalog_entity = $node->field_uw_catalog_catalog->entity; $tabs_values = $catalog_entity->field_uw_catalog_tabs_display->getValue(); // Setup the array that we can use for in_array, // which is the tabs to be displayed. foreach ($tabs_values as $tab_value) { $tabs[] = $tab_value['value']; } // If there are tabs, then get them. if (!empty($tabs)) { foreach ($field_name as $key => $field) { if (in_array($key, $tabs)) { // Set the type of tags, we need special one // for catalog category tags. switch ($key) { case 'Category': $type = 'category_tags'; break; case 'Faculty': $type = 'faculty_tags'; break; case 'Audience': $type = 'audience_tags'; break; default: $type = 'tags'; break; } // Get the tags to add. $tags_to_add = $this->getTermsFromEntityField($node->$field, $type); // If tags to add is not empty, then add to the // tags array. if (!empty($tags_to_add)) { $tags[$key] = $this->getTermsFromEntityField($node->$field, $type); } // If this is a catalog category tag, we need to // add in the correct UW url to the term. if ($type !== 'tags') { // Get the URL to the catalog taxonomy term. $url = Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $catalog_entity->id()])->toString(); // Ensure we add the correct tag type to the url. switch ($type) { case 'category_tags': $url .= '/category'; break; case 'faculty_tags': $url .= '/faculty'; break; case 'audience_tags': $url .= '/audience'; break; } // Enusre that there is a tag. if (isset($tags[$key])) { // Step through all the catalog category tags and // fix to the correct UW URL. foreach ($tags[$key] as $index => $value) { // Since the drupal views uses the full title in the // url (including all little keywords (the, is, all, of) // we need to ensure that we include those in the url // for the node. $value['url'] = strtolower($value['title']); $value['url'] = str_replace(' ', '-', $value['url']); $tags[$key][$index]['url'] = $url . '/' . $value['url']; } } } } } } return $tags; } /** * Gets a plain text field or fields. * * @param \Drupal\node\Entity\Node $node * The node. * @param string $field_name * The field name. * @param bool $multiple * Flag if multiple plain text. * * @return mixed * Plain text values. */ public function getPlainText(Node $node, string $field_name, bool $multiple = FALSE) { $plain_text = []; // If there are no multiple plain text, just return value. // If there are multiple plain text, step through and // get the values. if (!$multiple) { return $node->$field_name->value; } else { // Get the values of the plain text field. $values = $node->$field_name->getValue(); // Step through each and get the actual value. foreach ($values as $value) { $plain_text[] = $value['value']; } // Return array of plain text. return $plain_text; } } /** * Prepares image for theme. * * @param \Drupal\node\Entity\Node $node * Node entity. * @param string $field_name * Field name. * @param string|null $extra_options * An array of extra options for the image. * * @return array * Array of data such as url, alt. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ public function getImage(Node $node, string $field_name, array $extra_options = NULL): array { // Empty image array, empty if no image. $image = []; // If this is responsive, get the sources. if (isset($extra_options['is_responsive'])) { // Get the sources for the image. $image = $this->getSources($node, $field_name, $extra_options); } else { // Get the media id. $mid = $node->$field_name->getValue(); // If there is an image, process it. if ($mid) { // Load in the media item. /** @var \Drupal\media\Entity\Media $media */ $media = $this->entityTypeManager->getStorage('media')->load($mid[0]['target_id']); // Get the file id from the media object. $fid = $media->getSource()->getSourceFieldValue($media); // If there is a file id, then get the uri, // using the thumbnail image style. if (isset($extra_options['image_style'])) { $file = $this->entityTypeManager->getStorage('file')->load($fid); $image['uri'] = $this->entityTypeManager->getStorage('image_style')->load($extra_options['image_style'])->buildUrl($file->getFileUri()); $image['alt'] = $media->field_media_image->alt; } else { $file = $this->entityTypeManager->getStorage('file')->load($fid); $image['uri'] = $this->entityTypeManager->getStorage('image_style')->load('thumbnail')->buildUrl($file->getFileUri()); $image['alt'] = $media->field_media_image->alt; } } } // If there is a type for the image, add it to the array. if (!empty($image) && isset($extra_options['type'])) { $image['type'] = $extra_options['type']; } return $image; } /** * Get the value of a map field. * * @param \Drupal\node\Entity\Node $node * The node. * @param string $field_name * The field name. * * @return array|null * Array of field value or NULL. */ public function getMapField(Node $node, string $field_name) { // Set the map initially to null, if there are // coordinates, then will be replaced. $map = NULL; // If there are coordinates, set the map. if ($node->$field_name->getValue()) { $display = [ 'type' => 'leaflet_formatter_default', 'label' => 'visually_hidden', ]; $map = $node->$field_name->view($display); } return $map; } /** * Get the value of an content field. * * @param \Drupal\node\Entity\Node $node * The node. * @param string $view_mode * The view mode. * @param string|null $field_name * The field name. * * @return array|null * Array of field value or NULL. */ public function getContentField(Node $node, string $view_mode, string $field_name = NULL): ?array { // If on the teaser, return the summary field values. if ($view_mode == 'teaser') { return $this->getFormattedText($node, $field_name); } return NULL; } /** * Get the taxonomy terms field(s) value. * * @param \Drupal\node\Entity\Node $node * The node. * @param array $field_name * Array of field names. * * @return array * Array of taxonomy term values. * * @throws \Drupal\Core\Entity\EntityMalformedException */ public function getTermsField(Node $node, array $field_name): array { // Need empty array in case there are no terms. $tags = []; // Step through each of the terms and add to array. foreach ($field_name as $field) { $tags = array_merge($tags, $this->getTermsFromEntityField($node->$field, 'tags')); } // Return array of terms. return $tags; } /** * Get the value of an address field. * * @param \Drupal\node\Entity\Node $node * The node. * @param string $field_name * The field name. * * @return mixed * Array of field value or NULL. */ public function getAddressField(Node $node, string $field_name) { $address = $node->$field_name->getValue(); if (isset($address[0])) { return $address[0]; } return NULL; } /** * Get the author of the node. * * @param \Drupal\node\Entity\Node $node * The node. * * @return array * Array of the author info. */ public function getAuthor(Node $node): array { // If there is no author in the field, get the owner // of the blog post. if (!$node->field_author->value) { // Set the author to the person who made blog. $author = [ 'name' => $node->getOwner()->getDisplayName(), ]; } // If there is an author, get the author name and link. else { // Get the link field from the node. $link = $node->field_uw_author_link->getValue(); // Get the auther name from the node. $author['name'] = $node->field_author->value; if (!empty($link)) { $author['link'] = $link[0]['uri']; } } return $author; } /** * Gets dates from node. * * @param \Drupal\node\Entity\Node $node * Node entity. * @param string $field_name * The field name that has the date(s). * @param string $view_mode * The view mode of the node. */ public function getDates(Node $node, string $field_name, string $view_mode) { $return_dates = []; // If there is no dates to process, return empty array. if (!$node->$field_name->value) { return $return_dates; } // If this is not and event, just get the date. if ($node->getType() !== 'uw_ct_event') { $return_dates[] = $this->getDate([$node->$field_name->value], 'blog'); } else { // Get all the dates. $dates = $node->$field_name->getValue(); // Get the date query parameter. $date_parameter = $this->requestStack->getCurrentRequest()->query->get('date'); // If there is a date query parameter, convert // to timestamp so we can compare against dates // in the event. If there is no parameter, set // the timestamp todays date. if ($date_parameter) { $check_date = strtotime($date_parameter['value']); } else { $check_date = strtotime("now"); } // Step through each of the dates and get // out correct values. foreach ($dates as $date) { // ISTWCMS-5088: we need to ensure that at least // some dates show on the node page, so let's just // display them all. // If not node page, only get dates in the future. if ($view_mode == 'full') { $return_dates[] = $this->getDate($date, 'event'); } else { // Ensure that the dates are greater than timestamp // that we generated above. if ($date['end_value'] > $check_date) { $return_dates[] = $this->getDate($date, 'event'); } } } } // If the dates are an array and there is more than one, // sort the array so that the dates appear in order. if ( is_array($return_dates) && count($return_dates) > 1 ) { // Sorting the array by the start date, so that // the dates actually appear in the correct order. $keys = array_column($return_dates, 'start_date'); array_multisort($keys, SORT_ASC, $return_dates); } return $return_dates; } /** * Function that parses terms and returns a list. * * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $values * List of values for the provided field. * @param string|null $type * The type of terms to get, if none provided just term name returned. * * @return array * List of terms with name and link. * * @throws \Drupal\Core\Entity\EntityMalformedException */ public function getTermsFromEntityField(EntityReferenceFieldItemListInterface $values, string $type = NULL): array { // Array to hold the terms, need to set to // null in case there are no terms to be // returned. $terms = []; // Step through each of the values which is a // list of referenced taxonomy terms. foreach ($values as $term_ref) { // Get the term entity, which is the same as // loading the term. $term_entity = $term_ref->entity; // If there is an entity, set the variable. if ($term_entity) { // If this is a tags term type, we have to include // url and title. If not just the tag name. if ( $type === 'tags' || $type == 'category_tags' || $type == 'faculty_tags' || $type == 'audience_tags' ) { // Get the url to the term. $url = $term_entity->toUrl()->toString(); // If catalog term we only want the end of the url // as we need to redirect to the UW category url. if ( $type == 'category_tags' || $type == 'faculty_tags' || $type == 'audience_tags' ) { $parts = explode('/', $url); $url = end($parts); } // Set the variables. By default function // toUrl on entity uses canonical url. $terms[] = [ 'title' => $term_entity->getName(), 'url' => $url, ]; } else { $terms[] = $term_entity->getName(); } } } // Return an array of term names. return $terms; } /** * Gets sources from node. * * @param \Drupal\node\Entity\Node $node * Node entity. * @param string $field_name * The field name that has the date(s). * @param string|null $extra_options * An array of extra options. * * @return array * Either array with responsive image. */ public function getSources(Node $node, string $field_name, array $extra_options = NULL): array { $return_sources = []; if ($node->$field_name) { // Get the image entity. $image = $node->$field_name->entity; // If there is an image, get the responsive image sources. if ($image) { $sources = $this->uwService->prepareResponsiveImage($image, 'uw_ris_media'); } else { return []; } if (isset($sources['responsive_sources'])) { $return_sources['sources'] = $sources['responsive_sources']; $return_sources['img_element'] = $sources['img_element']['#uri']; $return_sources['alt'] = $sources['alt']; } // If there is a crop on image, pull out only those sources. // If no crop is specified use the default which is responsive. if (isset($extra_options['crop'])) { $image_styles = $this->uwService->getCropImageStyles($extra_options['crop']); } else { $image_styles = $this->uwService->getCropImageStyles('responsive'); } // Step through and remove any sources that are // not going to be in the image. foreach ($return_sources['sources'] as $index => $source) { if (!in_array($source['style'], $image_styles)) { unset($return_sources['sources'][$index]); } } } return $return_sources; } /** * Get a date in the proper format. * * @param array $date * An array of date info. * @param string $type * The type of date. * * @return array * An array about a date. */ public function getDate(array $date, string $type): array { $return_date = []; if ($type == 'event') { // Check for same day. if (date('Ymd', $date['value']) == date('Ymd', $date['end_value'])) { $return_date['same_day'] = TRUE; } else { $return_date['same_day'] = FALSE; } // If this is the same day, get the date and the start and end times. if ($date['duration'] < '1439') { $return_date['all_day'] = FALSE; $return_date['date_range'] = TRUE; } // This is not the day, get the start and end date with time. elseif ($date['duration'] > '1439') { $return_date['all_day'] = FALSE; $return_date['date_range'] = TRUE; } // The all date case, duration is always 1439. else { $return_date['all_day'] = TRUE; $return_date['date_range'] = TRUE; } $return_date['start_date'] = $date['value']; $return_date['end_date'] = $date['end_value']; } else { // If the date contains a time code, we need to convert // it, because it is stored in UTC. if (strpos($date[0], 'T')) { $return_date['date'] = date('c', strtotime($date[0] . ' UTC')); } else { $return_date['date'] = $date[0]; } } return $return_date; } /** * Gets link info from node. * * @param \Drupal\node\Entity\Node $node * Node entity. * @param string $field_name * The field name that has the date(s). * * @return array * Array with link info. */ public function getLinkInfo(Node $node, string $field_name): array { $return_link_data = []; // Get the link from the node. $link_data = $node->$field_name->getValue(); // If there is data in the link, get the variables. if ($link_data) { // Step through each link and get the info. foreach ($link_data as $ld) { // Get correct uri, if external just use uri from node // value, if not, then generate url from uri. if (UrlHelper::isExternal($ld['uri'])) { $link_info['uri'] = $ld['uri']; } else { $link_info['uri'] = URL::fromUri($ld['uri'])->toString(); } // For 'Link to contact' field, // if the uri is like 'internal:/blog', the link title is empty. // if the uri is like 'entity:node/6', set the node title as link title. if ($field_name == 'field_uw_ct_profile_link_contact') { // Get uri from the given link data. $uri = $ld['uri']; // Split uri into an array. $nid = explode('/', $uri); // Get the node object. $node = Node::load($nid[1]); // If it is node object, set the link node title as link title. if ($node) { $link_info['title'] = $node->get('title')->value; } // If it is not node object, still use the existing title value. else { $link_info['title'] = $ld['title']; } } // For other link fields except 'Link to contact' field. else { $link_info['title'] = $ld['title']; } $return_link_data[] = $link_info; } } return $return_link_data; } /** * Gets timeline from smart date range. * * @param \Drupal\node\Entity\Node $node * Node entity. * @param string $field_name * The field name that has the timeline. * * @return array * Array with link info. */ public function getTimeline(Node $node, string $field_name): array { $timeline = $node->$field_name->getValue(); if ($timeline) { $timeline = [ 'start_date' => $timeline[0]['value'], 'end_date' => $timeline[0]['end_value'], ]; } return $timeline; } /** * Gets project members. * * @param \Drupal\node\Entity\Node $node * Node entity. * @param string $field_name * The field name that has the timeline. * * @return array|null * Array with link info. */ public function getProjectMembers(Node $node, string $field_name): ?array { // Ensuring that we return something. $project_members = NULL; // Get the members from the node, which is a paragraph. $members = $node->$field_name->referencedEntities(); // Step through each of the members and get the info. foreach ($members as $member) { // Default empty variables for roles and links. $roles = NULL; $link = NULL; $member_name = NULL; // Get the tids for the role from the paragraph. $role_tids = $member->field_uw_project_role->getValue(); // If there are role tids, process them. if ($role_tids) { // Step through each of the tids and get the name of the role term. foreach ($role_tids as $role_tid) { // Load the term. $role = $this->entityTypeManager->getStorage('taxonomy_term')->load($role_tid['target_id']); // Set the role name in the array. $roles[] = [ 'title' => $role->getName(), 'url' => $role->toUrl()->toString(), ]; } } // Get the link from the paragraph. $link_data = $member->field_uw_project_members_link->getValue(); // If there is data in the link, get the variables. if ($link_data) { // Step through each link and get the info. foreach ($link_data as $ld) { // Get correct uri, if external just use uri from node // value, if not, then generate url from uri. if (UrlHelper::isExternal($ld['uri'])) { $link = $ld['uri']; } else { $link = URL::fromUri($ld['uri'])->toString(); } } } // Setup member name, if there is no member use the link. if ( $member->field_uw_project_name->value == NULL && $link !== NULL ) { $member_name = $link; } else { $member_name = $member->field_uw_project_name->value; } // If there is any values in the project members, // add it to the variables. if ($member_name || $link || $roles) { // Setup the project members array. $project_members[] = [ 'name' => $member_name, 'roles' => $roles, 'link' => $link, ]; } } return $project_members; } }