Commit 667b44d5 authored by Fran Garcia-Linares's avatar Fran Garcia-Linares Committed by Neil Drumm
Browse files

Issue #3265112 by fjgarlin, drumm: Decouple change records from www.drupal.org issues

parent 8de74885
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -25,3 +25,4 @@ files[] = views/drupalorg_handler_project_subtitle.inc
files[] = views/drupalorg_handler_security_coverage.inc
files[] = views/drupalorg_handler_release_branch_info.inc
files[] = views/drupalorg_handler_release_core_version_requirement.inc
files[] = views/drupalorg_plugin_argument_default_current_issue_url.inc
+34 −0
Original line number Diff line number Diff line
@@ -6864,3 +6864,37 @@ function drupalorg_update_7156() {
function drupalorg_update_7157() {
  db_drop_field('drupalorg_packaging_job', 'versioncontrol_label_id');
}

/**
 * Convert change notice issue entity references to links, so they can
 * reference off-site, to git.drupalcode.org.
 */
function drupalorg_update_7158() {
  $results = db_query("SELECT DISTINCT i.entity_id FROM {field_data_field_issues} i WHERE i.bundle = 'changenotice' AND i.entity_type = 'node'")->fetchCol();
  $max = count($results);
  $progress = 0;

  drush_print($max . " nodes will be updated.");
  foreach (array_chunk($results, 50) as $nids) {
    foreach (node_load_multiple($nids) as $node) {
      $progress += 1;
      $field_issues_ids = [];
      if (!empty($node->field_issues)) {
        foreach ($node->field_issues[LANGUAGE_NONE] as $issue) {
          $field_issues_ids[] = $issue['target_id'];
        }
      }
      if (!empty($field_issues_ids)) {
        $links = [];
        foreach ($field_issues_ids as $nid) {
          $links[] = [
            'url' => url('node/' . $nid, ['absolute' => TRUE, 'alias' => TRUE]),
          ];
        }
        $node->field_issue_links[LANGUAGE_NONE] = $links;
        node_save($node);
      }
    }
    drush_print("* " . $progress . "/" . $max . " processed...");
  }
}
+231 −1
Original line number Diff line number Diff line
@@ -123,6 +123,13 @@ function drupalorg_init() {
    drupal_add_http_header('Access-Control-Allow-Headers', 'Content-Type');
  }

  // GitLab integration, including rendering links to GitLab issues.
  drupal_add_js(drupal_get_path('module', 'drupalorg') . '/js/gitlab.js', ['scope' => 'footer', 'requires_jquery' => FALSE]);
  drupal_add_js(['drupalorgGitlab' => [
    'gitlab_base_url' => variable_get('versioncontrol_gitlab_url'),
    'base_url' => $GLOBALS['base_url'],
  ]], 'setting');

  // Audience extension, see
  // https://www.drupal.org/news/drupalorg-2015-advertising-initiatives
  // Include for anonymous users, except on excluded pages.
@@ -557,6 +564,13 @@ function drupalorg_menu() {
    'file' => 'drupalorg.pages.inc',
  ];

  $items['drupalorg_issues_metadata'] = [
    'page callback' => '_drupalorg_gitlab_issues_metadata',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
    'file' => 'drupalorg.pages.inc',
  ];

  return $items;
}

@@ -5044,6 +5058,167 @@ function drupalorg_node_presave(stdClass $node) {
    // projects.
    $wrapper->field_project_has_releases = !project_promote_project_is_sandbox($node);
  }
  elseif ($node->type == 'changenotice' && !empty($node->field_issue_links)) {
    $field_issue_links_ids = [];
    $field_issues_ids = [];

    // Transform any internal alias into the system path so we can match by it on views.
    foreach ($node->field_issue_links[LANGUAGE_NONE] as $index => $issue_link) {
      $id = _drupalorg_extract_nid_from_url($issue_link['url']);
      if ($id) {
        $field_issue_links_ids[] = $id;
        $node->field_issue_links[LANGUAGE_NONE][$index]['url'] = url('node/' . $id, ['absolute' => TRUE, 'alias' => TRUE]);
      }
    }

    // Sync the old field_issues field with the data from the newly created
    // field_issue_links field to keep backwards compatibility with other
    // parts of the site for places where only internal issues are still used.
    if (!empty($node->field_issues)) {
      foreach ($node->field_issues[LANGUAGE_NONE] as $issue) {
        $field_issues_ids[] = $issue['target_id'];
      }
    }

    if (!empty($field_issue_links_ids)) {
      foreach ($field_issue_links_ids as $id) {
        if (!in_array($id, $field_issues_ids)) {
          $node->field_issues[LANGUAGE_NONE][] = array(
            'target_id' => $id
          );
        }
      }
    }
  }
}

/**
 * Tries to obtain the nid from a URL within the site.
 */
function _drupalorg_extract_nid_from_url($url) {
  global $base_url;
  $nid = FALSE;
  $internal_link = str_replace($base_url . '/', '', $url);
  $system_path = drupal_get_normal_path($internal_link);
  if (strstr($system_path, 'node/') !== FALSE) {
    $path = explode('/', $system_path);
    $nid = $path[1];
  }
  elseif (strstr($system_path, 'i/') !== FALSE) {
    $path = explode('/', $system_path);
    $nid = $path[1];
  }

  return is_numeric($nid) ? $nid : FALSE;
}

/**
 * Extract drupal.org issue IDs or links from the parameters.
 */
function _drupalorg_get_ids_and_urls_from_params() {
  global $base_url;
  $query = drupal_get_query_parameters();
  $issue_ids = array();
  $urls = array();

  // Accept the new field pre-population too.
  // Normal links will be populated as they are, but we will also accept
  // drupal.org issues URLs (absolute or relative).
  if (!empty($query['field_issue_links'])) {
    $issue_links = explode(',', $query['field_issue_links']);
    foreach ($issue_links as $link) {
      $internal = FALSE;

      if (is_numeric($link)) {
        $link = 'node/' . $link;
        $internal = TRUE;
      }
      // Partial URL or alias. Get the ID of the node.
      elseif ($link[0] == '/') {
        $link = ltrim($link, '/');
        $internal = TRUE;
      }
      // Absolute URL within the site. Get the ID of the node.
      elseif (strstr($link, $base_url) !== FALSE) {
        $link = str_replace($base_url . '/', '', $link);
        $internal = TRUE;
      }

      if ($internal) {
        $nid = _drupalorg_extract_nid_from_url($link);
        if ($nid && is_numeric($nid)) {
          $issue_ids[] = $nid;
        }
        else {
          $urls[] = $base_url . '/' . $link;
        }
      }
      else {
        $urls[] = $link;
      }
    }
  }

  return array(
    'issue_ids' => array_unique($issue_ids),
    'urls' => array_unique($urls),
  );
}

/**
 * Produce an array of links from the URL parameters.
 */
function _drupalorg_get_links_from_params() {
  $links = array();
  $params = _drupalorg_get_ids_and_urls_from_params();

  $issue_ids = $params['issue_ids'];
  if (!empty($issue_ids)) {
    $issues = node_load_multiple($issue_ids);
    foreach ($issues as $issue) {
      $links[] = array(
        'url' => url('node/' . $issue->nid, ['absolute' => TRUE, 'alias' => TRUE])
      );
    }
  }

  $urls = $params['urls'];
  if (!empty($urls)) {
    foreach ($urls as $url) {
      $links[] = array(
        'url' => url($url, array('absolute' => TRUE))
      );
    }
  }

  return $links;
}

/**
 * Implements hook_form_alter().
 */
function drupalorg_form_changenotice_node_form_alter(&$form, &$form_state, $form_id) {
  // Only do this for new records and if the new field is deployed.
  if (!isset($form['nid']['#value']) && !empty($form['field_issue_links'])) {
    $links = _drupalorg_get_links_from_params();
    if (!empty($links)) {
      $delta = 0;
      foreach ($links as $link) {
        $form['field_issue_links'][LANGUAGE_NONE][$delta] = array(
          '#type' => 'link_field',
          '#delta' => $delta,
          '#weight' => $delta,
          '#default_value' => $link,
          '#field_parents' => array(),
          '#field_name' => 'field_issue_links',
          '#language' => LANGUAGE_NONE,
        );
        $delta++;
      }

      $form_state['field']['field_issue_links'][LANGUAGE_NONE]['items_count'] = $delta - 1;
    }
  }
}

/**
@@ -5489,7 +5664,7 @@ function drupalorg_preprocess_views_view(&$v) {
      if (user_access('create changenotice content')) {
        $v['footer'] .= ', ' . l(t('add change notice'), 'node/add/changenotice', ['query' => [
          'field_project' => $issue_wrapper->field_project->getIdentifier(),
          'field_issues' => $issue_wrapper->getIdentifier(),
          'field_issue_links' => url('node/' . $issue_wrapper->getIdentifier(), ['absolute' => TRUE, 'alias' => TRUE]),
        ]]);
      }
    }
@@ -10550,3 +10725,58 @@ function _drupalorg_git_auth_access($permissions) {

  return VersioncontrolAuthHandlerMappedAccounts::DENY;
}

/**
 * Checks if the issue is already migrated or not.
 *
 * @param object $record
 *   Issue object.
 * @param string|null $project_web_url
 *   Url of the project in gitlab.
 *
 * @return bool|string
 *   Redirect URL or false.
 */
function _drupalorg_gitlab_is_issue_migrated($record, $project_web_url = NULL) {
  if (!empty($record) && !empty($record->nid) && !empty($record->type) && $record->type == 'project_issue') {
    if (is_null($project_web_url)) {
      $project_web_url = variable_get('versioncontrol_gitlab_url');
    }

    $redirects = redirect_load_multiple(FALSE, ['source' => 'node/' . $record->nid]);
    if (!empty($redirects)) {
      foreach ($redirects as $redirect) {
        if (strpos($redirect->redirect, $project_web_url) !== FALSE) {
          // The redirection is only made when everything is migrated.
          return $redirect->redirect;
        }
      }
    }
  }

  return FALSE;
}

/**
 * Returns the GitLab project id from a given namespace and project name.
 *
 * @param string $namespace
 *   Namespace where the project is.
 * @param string $name
 *   Name of the project.
 *
 * @return string
 *   Id of the project in gitlab.
 */
function drupalorg_get_gitlab_project_id($namespace, $name) {
  static $ids = [];

  $key = $namespace . '/' . $name;
  if (!isset($ids[$key])) {
    // Not using EntityFieldQuery since we are getting only the GitLab project
    // ID, not the versioncontrol repository ID.
    $ids[$key] = db_query_range('SELECT vgr.gitlab_project_id FROM {versioncontrol_repositories} vr INNER JOIN {versioncontrol_gitlab_repositories} vgr ON vgr.repo_id = vr.repo_id AND vgr.namespace = :namespace WHERE vr.name = :name', 0, 1, [':namespace' => $namespace, ':name' => $name])->fetchField();
  }

  return $ids[$key];
}
+72 −0
Original line number Diff line number Diff line
@@ -2358,3 +2358,75 @@ function drupalorg_issue_fork_tugboat_preview_log($preview_id) {
    return t('Error fetching Tugboat preview log.');
  }
}

/**
 * Menu callback, returns additional GitLab metadata only for the URLs that are
 * migrated.
 */
function _drupalorg_gitlab_issues_metadata() {
  $client = versioncontrol_gitlab_get_client()->api('issues');
  $gitlab_base_url = variable_get('versioncontrol_gitlab_url');
  $params = drupal_get_query_parameters();
  $links = !empty($params['links']) ? $params['links'] : FALSE;
  $data = [];
  if (!empty($links) && is_array($links)) {
    $links = array_unique($links);
    foreach ($links as $link) {
      if (filter_var($link, FILTER_VALIDATE_URL)) {
        $gitlab_link = FALSE;
        $node = FALSE;
        if (strpos($link, $GLOBALS['base_url']) === 0) {
          // Internal link. Let's check if it's migrated and get the gitlab link.
          $relative_path = str_replace($GLOBALS['base_url'] . '/', '', $link);
          $system_path = drupal_lookup_path('source', $relative_path);
          if ($system_path === FALSE) {
            // Is it already a system path?
            $alias = drupal_lookup_path('alias', $relative_path);
            if ($alias) {
              $system_path = $relative_path;
            }
          }

          // Get the node and try to see if the issue is migrated.
          if ($system_path) {
            $nid = (int) str_replace('node/', '', $system_path);
            if ($nid && ($node = node_load($nid))) {
              $gitlab_link = _drupalorg_gitlab_is_issue_migrated($node, $gitlab_base_url);
            }
          }
        }
        elseif (strpos($link, $gitlab_base_url) === 0) {
          $gitlab_link = $link;
        }

        if ($gitlab_link) {
          if (preg_match('#(?<namespace>.*)/(?<project>.*)/-/issues/(?<issue>\d+)#', substr($gitlab_link, strlen($gitlab_base_url) + 1), $match)) {
            if ($gitlab_project_id = drupalorg_get_gitlab_project_id($match['namespace'], $match['project'])) {
              try {
                $gitlab_issue = $client->show($gitlab_project_id, $match['issue']);
                $data[$link] = [
                  'link' => $gitlab_link,
                  'title' => '#' . $gitlab_issue['iid'] . ' (' . $match['project'] . '): ' . $gitlab_issue['title'],
                  'status' => $gitlab_issue['state'],
                ];
              }
              catch (Exception $e) {
                $gitlab_issue = FALSE;
              }
            }
          }
        }
        elseif ($node) {
          // Issue not migrated but link might need the right text anyway.
          $data[$link] = [
            'link' => $link,
            'title' => "#$node->nid: $node->title",
            'status' => $node->field_issue_status[LANGUAGE_NONE][0]['value'],
          ];
        }
      }
    }
  }

  return drupal_json_output($data);
}
+19 −0
Original line number Diff line number Diff line
@@ -160,3 +160,22 @@ function drupalorg_views_data_alter(&$data) {
  );
  $data['field_data_field_issue_last_status_change']['field_issue_last_status_change_value']['filter']['handler'] = 'views_handler_filter_date';
}

/**
 * Implements hook_views_plugins().
 */
function drupalorg_views_plugins() {
  $path = drupal_get_path('module', 'drupalorg') . '/views/plugins';
  return [
    'module' => 'views',
    'argument default' => [
      'drupalorg_plugin_argument_default_current_issue_url' => [
        'title' => t('URL of the issue being viewed'),
        'handler' => 'drupalorg_plugin_argument_default_current_issue_url',
        // 'path' => $path,
        // So that the parent class is included.
        'parent' => 'fixed',
      ],
    ],
  ];
}
Loading