| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- <?php
- /**
- * @file
- * Announcements feed helper functions.
- */
- /**
- * Returns the list of announcements.
- *
- * @return array
- * A build array with announcements.
- */
- function announcements_feed_get_announcements() {
- drupal_set_title(t('Community announcements'));
- drupal_add_css(drupal_get_path('module', 'announcements_feed')
- . '/announcements_feed.css', array(
- 'group' => CSS_DEFAULT,
- 'every_page' => TRUE,
- ));
- try {
- $announcements = announcements_feed_get_all_announcements();
- }
- catch (Exception $e) {
- drupal_set_message(t('An error occurred while parsing the announcements feed, check the logs for more information.'), 'error');
- return array();
- }
- $build = array();
- foreach ($announcements as $announcement) {
- $key = $announcement['featured'] ? '#featured' : '#standard';
- $build[$key][] = $announcement;
- }
- $build = array_merge($build, array(
- '#theme' => 'announcements_feed',
- '#count' => count($announcements),
- '#feed_link' => variable_get('announcements_feed_link', ANNOUNCEMENTS_FEED_DEFAULT_LINK),
- ));
- return $build;
- }
- /**
- * Generate an array of announcements with keys.
- *
- * @return array
- * An array of announcements.
- */
- function announcements_feed_get_all_announcements() {
- $announcements = announcements_feed_fetch();
- $announcements_feed = array();
- foreach ($announcements as $announcement) {
- $announcements_feed[] = array(
- 'id' => $announcement['id'],
- 'title' => $announcement['title'],
- 'link' => $announcement['url'],
- 'date_modified' => $announcement['date_modified'],
- 'date_published' => $announcement['date_published'],
- 'teaser' => $announcement['content_html'],
- 'version' => $announcement['_drupalorg']['version'],
- 'featured' => (bool) $announcement['_drupalorg']['featured'],
- );
- }
- return $announcements_feed;
- }
- /**
- * Fetches the feed either from a local cache or fresh remotely.
- *
- * The feed follows the "JSON Feed" format:
- * - https://www.jsonfeed.org/version/1.1/
- *
- * The structure of an announcement item in the feed is:
- * - id: Id.
- * - title: Title of the announcement.
- * - content_html: Announcement teaser.
- * - url: URL
- * - date_modified: Last updated timestamp.
- * - date_published: Created timestamp.
- * - _drupalorg.featured: 1 if featured, 0 if not featured.
- * - _drupalorg.version: Target version of Drupal, as a Composer version.
- *
- * @param bool $force
- * (optional) Whether to always fetch new items or not. Defaults to FALSE.
- *
- * @return array
- * An array of announcements from the feed relevant to the Drupal version.
- * The array is empty if there were no matching announcements. If an error
- * occurred while fetching/decoding the feed, it is thrown as an exception.
- *
- * @throws Exception
- */
- function announcements_feed_fetch($force = FALSE) {
- $announcements = cache_get('announcements_feed');
- if ($force || empty($announcements)) {
- $announcements_feed_json_url = variable_get('announcements_feed_json_url', ANNOUNCEMENTS_FEED_DEFAULT_JSON_URL);
- $response = drupal_http_request($announcements_feed_json_url);
- if ($response->code == 200) {
- $feeds = json_decode($response->data, TRUE);
- if (!isset($feeds['items'])) {
- watchdog('announcements_feed', 'The feed format is not valid.', NULL, WATCHDOG_ERROR);
- throw new Exception('Announcements feed JSON format is invalid');
- }
- $announcements = array();
- if ($feeds['items']) {
- $announcements = $feeds['items'];
- }
- $announcements = array_filter($announcements, 'announcements_feed_filter_announcements');
- cache_set('announcements_feed', $announcements, 'cache', REQUEST_TIME + variable_get('announcements_feed_max_age', ANNOUNCEMENTS_FEED_DEFAULT_MAX_AGE));
- }
- else {
- watchdog(
- 'announcements_feed',
- 'The feed failed to fetch with an error code: @code, error message: @message.',
- array('@code' => $response->code, '@message' => $response->error),
- WATCHDOG_ERROR
- );
- throw new Exception($response->error, $response->code);
- }
- }
- else {
- $announcements = $announcements->data;
- }
- // The drupal.org endpoint is sorted by created date in descending order.
- // We will limit the announcements based on the configuration limit.
- $announcements_feed_limit = variable_get('announcements_feed_limit', ANNOUNCEMENTS_FEED_DEFAULT_LIMIT);
- $announcements = array_slice($announcements, 0, $announcements_feed_limit);
- // For the remaining announcements, put all the featured announcements
- // before the rest.
- uasort($announcements, 'announcements_feed_sort_featured');
- return $announcements;
- }
- /**
- * Sort the elements of announcements_feed by values in comparison function.
- */
- function announcements_feed_sort_featured($a, $b) {
- $a_value = (int) $a['_drupalorg']['featured'];
- $b_value = (int) $b['_drupalorg']['featured'];
- if ($a_value == $b_value) {
- return 0;
- }
- return ($a_value < $b_value) ? -1 : 1;
- }
- /**
- * Filter the announcements relevant to the Drupal version used with valid URL controlled by drupal.org.
- *
- * @param array $announcement
- * Announcement feed array item to check.
- *
- * @return bool
- * Return TRUE if $announcement is relevant and the URL is valid.
- */
- function announcements_feed_filter_announcements($announcement) {
- $announcement_url = '';
- $announcement_version = '';
- if (!empty($announcement['url'])) {
- $announcement_url = $announcement['url'];
- }
- if (!empty($announcement['_drupalorg']['version'])) {
- $announcement_version = $announcement['_drupalorg']['version'];
- }
- return announcements_feed_validate_url($announcement_url) && announcements_feed_is_relevant_item($announcement_version);
- }
- /**
- * Check whether the version given is relevant to the Drupal version used.
- *
- * @param string $version
- * Version to check.
- *
- * @return bool
- * Return TRUE if the version matches Drupal version.
- */
- function announcements_feed_is_relevant_item($version) {
- if ($version == '*') {
- return TRUE;
- }
- // Split the version if received in || formats.
- $version_patterns = '/\|\|/';
- $all_versions = preg_split($version_patterns, $version);
- // The operation is optional and defaults to equals.
- $p_op = '(?P<operation>!=|\^|==|=|<|<=|>|>=|<>)?';
- $operations = '=';
- // Extracts major version from version string like 7, 8, 9.
- $p_major = '(?P<major>\d+)';
- // Extracts minor version from version string.
- $p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
- foreach ($all_versions as $version) {
- if (preg_match("/^\s*$p_op\s*$p_major(\.$p_minor)?/", $version, $matches)) {
- $feed_version = $matches['major'];
- if (!empty($matches['minor'])) {
- $feed_version = $matches['major'] . '.' . $matches['minor'];
- }
- if (!empty($matches['operation'])) {
- $operations = $matches['operation'];
- if ($operations == '^') {
- $operations = '>=';
- }
- }
- if (isset($operations) && version_compare(VERSION, $feed_version, $operations)) {
- return TRUE;
- }
- }
- }
- return FALSE;
- }
- /**
- * Check whether a link is controlled by drupal.org.
- *
- * @param string $url
- * URL to check.
- *
- * @return bool
- * Return TRUE if the URL is controlled by drupal.org.
- */
- function announcements_feed_validate_url($url) {
- if (empty($url)) {
- return FALSE;
- }
- $host = parse_url($url, PHP_URL_HOST);
- // First character can only be a letter or a digit.
- // @see https://www.rfc-editor.org/rfc/rfc1123#page-13
- return $host && preg_match('/^([a-zA-Z0-9][a-zA-Z0-9\-_]*\.)?drupal\.org$/', $host);
- }
|