| <?php | |
| /******************************************************************************* | |
| * Copyright (c) 2011 Eclipse Foundation and others. | |
| * All rights reserved. This program and the accompanying materials | |
| * are made available under the terms of the Eclipse Public License v1.0 | |
| * which accompanies this distribution, and is available at | |
| * http://www.eclipse.org/legal/epl-v10.html | |
| * | |
| * Contributors: | |
| * Wayne Beaton - initial API and implementation | |
| * Wayne Beaton - Modified query to skip 'committers_only' bugs. | |
| *******************************************************************************/ | |
| /* | |
| * The Bug class is used by the contribution review tool to review | |
| * bugs that either have been marked iplog+ or perhaps should be. | |
| * | |
| * TODO This class should be generalized. | |
| * | |
| * This file assumes that the $App variable has been defined. | |
| */ | |
| // TODO Generalize this | |
| require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/Project.class.php"); | |
| require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/common.php"); | |
| require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/debug.php"); | |
| trace_file_info(__FILE__); | |
| class Bug { | |
| var $bug_id; | |
| var $title; | |
| var $product; | |
| var $component; | |
| var $target_milestone; | |
| var $patches = array(); | |
| var $flags = array(); | |
| /** | |
| * Cache the committers in case we need them. | |
| * @see Bug::getComments() | |
| * | |
| * @var BugCommitter[] | |
| */ | |
| var $committers; | |
| function getId() { | |
| return $this->bug_id; | |
| } | |
| function getTitle() { | |
| return $this->title; | |
| } | |
| function getProduct() { | |
| return $this->product; | |
| } | |
| function getComponent() { | |
| return $this->component; | |
| } | |
| function getTargetMilestone() { | |
| return $this->target_milestone; | |
| } | |
| function getLink() { | |
| return "https://bugs.eclipse.org/$this->bug_id"; | |
| } | |
| function asHtml() { | |
| $link = $this->getLink(); | |
| $text = "<a href=\"$link\">$this->bug_id</a> $this->product: $this->title"; | |
| if ($this->target_milestone) { | |
| $text = "$text ($this->target_milestone)"; | |
| } | |
| return $text; | |
| } | |
| function has_no_patch() { | |
| return $this->count_patches() == 0; | |
| } | |
| function has_single_patch() { | |
| return $this->count_patches() == 1; | |
| } | |
| function count_patches() { | |
| return count($this->get_patches()); | |
| } | |
| function get_patches() { | |
| $patches = array(); | |
| foreach($this->patches as $patch) { | |
| if ($patch->is_patch()) $patches[] = $patch; | |
| } | |
| return $patches; | |
| } | |
| function has_iplog_flag() { | |
| foreach ($this->flags as $flag) { | |
| if ($flag->is_iplog_flag()) return true; | |
| } | |
| return false; | |
| } | |
| function has_iplog_patch() { | |
| foreach ($this->get_patches() as $patch) { | |
| if ($patch->has_iplog_flag()) return true; | |
| } | |
| return false; | |
| } | |
| /* | |
| * Answers true if all the patches come from a single contributor. | |
| * Assumes that the receiver has at least one patch. | |
| */ | |
| function all_patches_from_same_contributor() { | |
| $contributor = $this->patches[0]->contributor; | |
| foreach($this->get_patches() as $patch) { | |
| if (strcmp($patch->contributor, $contributor) != 0) return false; | |
| } | |
| return true; | |
| } | |
| function has_good_candidate_patch() { | |
| if ($this->has_single_patch()) return true; | |
| if ($this->all_patches_from_same_contributor()) return true; | |
| return false; | |
| } | |
| /** | |
| * Add recommendations to the given array. What do we recommend be | |
| * done for this bug? Should the bug itself be marked iplog+, or | |
| * one of the attachments? Perhaps the iplog flag should be | |
| * cleared... | |
| */ | |
| function add_recommendations(&$recommendations) { | |
| if ($this->has_no_patch()) return; | |
| if ($this->has_iplog_flag()) $recommendations[] = new ClearBugFlagRecommendation($this); | |
| foreach($this->get_patches() as $patch) { | |
| $patch->addRecommendations($recommendations); | |
| } | |
| } | |
| function get_link_string() { | |
| return "<a target=\"_blank\" href=\"https://bugs.eclipse.org/$this->bug_id\">$this->bug_id</a>"; | |
| } | |
| /** | |
| * Get the comments for the bug. Note that this will issue an database request | |
| * for the data. Note also that the results of this query are not cached. | |
| */ | |
| function getComments() { | |
| global $App; | |
| $sql = "select | |
| longdescs.comment_id as id, | |
| UNIX_TIMESTAMP(longdescs.bug_when) AS created, | |
| LENGTH(longdescs.thetext) AS size, | |
| profiles.login_name as login, | |
| profiles.realname as realname | |
| from longdescs | |
| join profiles on (profiles.userid = longdescs.who) | |
| where longdescs.bug_id = $this->bug_id"; | |
| debugMessage($sql); | |
| $result = $App->bugzilla_sql( $sql ); | |
| $comments = array(); | |
| while( $row = mysql_fetch_assoc($result) ) { | |
| $comment = new BugComment(); | |
| $comment->id = $row['id']; | |
| $comment->creation = $row['created']; | |
| $comment->size = $row['size']; | |
| $comment->contributor = $row['login']; | |
| $comment->realname = $row['realname']; | |
| $comment->by_committer = is_project_committer($comment->contributor, $comment->creation, $this->committers); | |
| $comments[] = $comment; | |
| } | |
| mysql_free_result($result); | |
| return $comments; | |
| } | |
| } | |
| class Patch { | |
| var $attach_id; | |
| var $description; | |
| var $contributor; | |
| var $realname; | |
| var $size; | |
| var $by_committer; | |
| var $is_patch; | |
| var $isObsolete; | |
| var $creation; | |
| var $flags = array(); | |
| /** | |
| * We only consider an attachment a patch if it is marked as a patch and | |
| * was contributed by a non-committer. | |
| */ | |
| function is_patch() { | |
| if (!$this->is_patch) return false; | |
| if ($this->by_committer) return false; | |
| return true; | |
| } | |
| function isObsolete() { | |
| return $this->isObsolete; | |
| } | |
| function has_iplog_flag($sign = '+') { | |
| foreach ($this->flags as $flag) { | |
| if ($flag->is_iplog_flag($sign)) return true; | |
| } | |
| return false; | |
| } | |
| function get_link_string() { | |
| return "<a target=\"_blank\" href=\"https://bugs.eclipse.org/bugs/attachment.cgi?id=$this->attach_id&action=edit\">$this->attach_id</a>"; | |
| } | |
| function addRecommendations(&$recommendations) { | |
| if ($this->has_iplog_flag()) return; | |
| if ($this->has_iplog_flag('-')) return; | |
| if ($this->isObsolete()) return; | |
| $recommendations[] = new MarkPatchFlagRecommendation($this); | |
| } | |
| } | |
| class BugComment { | |
| var $id; | |
| var $creation; | |
| var $size; | |
| var $login; | |
| var $realname; | |
| var $by_committer; | |
| } | |
| class BugCommitter { | |
| // TODO Consolidate this with the Committer class. | |
| var $id; | |
| var $email; | |
| var $start; | |
| var $end; | |
| } | |
| class Flag { | |
| var $name; | |
| var $value; | |
| function is_iplog_flag($sign = '+') { | |
| if ($this->name != 'iplog') return false; | |
| if ($this->value != $sign) return false; | |
| return true; | |
| } | |
| } | |
| class Recommendation { | |
| } | |
| class ClearBugFlagRecommendation extends Recommendation { | |
| var $bug; | |
| function __construct($bug) { | |
| $this->bug = $bug; | |
| } | |
| function get_html_string() { | |
| return "Clear the iplog flag on bug " . $this->bug->get_link_string(); | |
| } | |
| } | |
| class MarkPatchFlagRecommendation extends Recommendation { | |
| var $patch; | |
| function __construct($patch) { | |
| $this->patch = $patch; | |
| } | |
| function get_html_string() { | |
| return "Set the iplog flag on attachment " . $this->patch->get_link_string() . " (or mark this attachment as obsolete)."; | |
| } | |
| } | |
| function find_bugs($projectid, &$committers = null){ | |
| global $App; | |
| global $_GET; | |
| if (!$committers) $committers = find_committers($projectid); | |
| $project = get_project($projectid); | |
| if (!$project) { | |
| debugMessage("No project with id $projectid found!"); | |
| return array(); | |
| } | |
| /* | |
| * Obtain the product and components for the project. Note that | |
| * the components are returned as an array of component names. | |
| * We roll them into a single string with each component | |
| * surrounded in single quites and separated from the previous | |
| * component by a comma. | |
| */ | |
| $product = $project->getBugzillaProduct(); | |
| $components = $project->getBugzillaComponents(); | |
| if ($components) { | |
| $components = "'" . implode("','", $components) . "'"; | |
| } | |
| debugMessage('Bugzilla product is ' | |
| . ($product ? $product : 'not specified') | |
| . ', components are ' | |
| . ($components ? $components : 'not specified') | |
| . '.'); | |
| clear_project_cache(); | |
| // $keys = find_bugzilla_keys($projectid); | |
| // $keys_list = '"' . implode('","', $keys) . '"'; | |
| $sql = " | |
| SELECT | |
| bugs.bug_id as bug_id, | |
| bugs.short_desc as title, | |
| products.name as product, | |
| components.name as component, | |
| bugflagtypes.name as bugflag_name, | |
| bugflags.status as bugflag_value, | |
| attachments.attach_id as attach_id, | |
| attachments.description as description, | |
| attachments.isobsolete as isobsolete, | |
| attachments.ispatch as ispatch, | |
| attachments.creation_ts as creation, | |
| LENGTH(attach_data.thedata) AS size, | |
| profiles.login_name as attach_contributor, | |
| profiles.realname as attach_realname, | |
| flagtypes.name as flag_name, | |
| flags.status as flag_value | |
| FROM | |
| bugs | |
| join products on (bugs.product_id = products.id) | |
| join components on (bugs.component_id = components.id) | |
| left join attachments on (bugs.bug_id = attachments.bug_id and attachments.isobsolete = 0) | |
| left join attach_data on (attachments.attach_id = attach_data.id) | |
| left join profiles on (attachments.submitter_id = profiles.userid) | |
| left join flags on (attachments.attach_id = flags.attach_id) | |
| left join flagtypes on (flags.type_id = flagtypes.id) | |
| left join flags as bugflags on (bugs.bug_id = bugflags.bug_id and bugflags.attach_id is null) | |
| left join flagtypes as bugflagtypes on (bugflags.type_id = bugflagtypes.id) | |
| WHERE | |
| products.name = '$product' | |
| and bugs.bug_id not in (select bg.bug_id from bug_group_map as bg join groups as g on (bg.group_id=g.id and g.name = 'Security_Advisories')) | |
| and (flagtypes.name is null or flagtypes.name = 'iplog') | |
| and bugs.bug_status IN ( 'RESOLVED', 'VERIFIED', 'CLOSED' ) | |
| and bugs.resolution IN ( 'FIXED' )"; | |
| if ($components) | |
| $sql .= " and bugs.component_id in (SELECT id FROM components WHERE product_id=products.id and name in ($components))"; | |
| debugMessage($sql); | |
| $result = $App->bugzilla_sql( $sql ); | |
| $bugs = array(); | |
| while( $row = mysql_fetch_assoc($result) ) { | |
| $bug_id = $row['bug_id']; | |
| $description = $row['description']; | |
| if (strcmp($description, "mylyn/context/zip") == 0) continue; | |
| if (strcmp($description, "mylar/context/zip") == 0) continue; | |
| if (isset($bugs[$bug_id])) { | |
| $bug = $bugs[$bug_id]; | |
| } else { | |
| $bug = new Bug(); | |
| $bug->committers = $committers; // Cache the committers in case we need them. | |
| $bug->bug_id = $bug_id; | |
| $bug->title = $row['title']; | |
| $bug->product = $row['product']; | |
| $bug->component = $row['component']; | |
| $bugs[$bug_id] = $bug; | |
| } | |
| $flag_name = $row['bugflag_name']; | |
| if ($flag_name) { | |
| $flag = new Flag(); | |
| $flag->name = $flag_name; | |
| $flag->value = $row['bugflag_value']; | |
| $bug->flags[$flag_name] = $flag; | |
| //echo "Found bug flag $flag_name ($flag->value) on $bug_id<br/>"; | |
| } | |
| $attach_id = $row['attach_id']; | |
| if (!$attach_id) continue; | |
| $patch = new Patch(); | |
| $patch->attach_id = $attach_id; | |
| $patch->description = $row['description']; | |
| $patch->contributor = $row['attach_contributor']; | |
| $patch->realname = $row['attach_realname']; | |
| $patch->size = $row['size']; | |
| $patch->is_patch = $row['ispatch']; | |
| $patch->isObsolete = $row['isobsolete']; | |
| $creation = $row['creation']; | |
| $patch->creation = strtotime($creation); | |
| $flag_name = $row['flag_name']; | |
| if ($flag_name) { | |
| $flag = new Flag(); | |
| $flag->name = $flag_name; | |
| $flag->value = $row['flag_value']; | |
| $patch->flags[$flag_name] = $flag; | |
| } | |
| $patch->by_committer = (is_contributor_committer($patch, $committers)); | |
| $bug->patches[] = $patch; | |
| } | |
| mysql_free_result($result); | |
| return $bugs; | |
| } | |
| function find_committers($projectid) { | |
| global $App; | |
| mustBeValidProjectId($projectid); | |
| $sql = "SELECT | |
| People.PersonId as id, | |
| People.Email as email, | |
| PeopleProjects.ActiveDate AS start, | |
| PeopleProjects.InactiveDate AS end | |
| FROM | |
| People | |
| join PeopleProjects on (People.PersonId = PeopleProjects.PersonId) | |
| WHERE | |
| PeopleProjects.Relation = 'CM' | |
| AND PeopleProjects.ProjectId = '$projectid'"; | |
| //echo $sql; | |
| $result = $App->foundation_sql( $sql ); | |
| $committers = array(); | |
| while( $row = mysql_fetch_assoc($result) ) { | |
| $committer = new BugCommitter(); | |
| $committer->id = $row['id']; | |
| $committer->email = $row['email']; | |
| $committer->start = strtotime($row['start']); | |
| $committer->end = strtotime($row['end']); | |
| $committers[$committer->email] = $committer; | |
| } | |
| mysql_free_result($result); | |
| return $committers; | |
| } | |
| function dump_committers() { | |
| global $committers; | |
| foreach ($committers as $committer) { | |
| $start = date("Y.m.d", $committer->start); | |
| $end = "present"; | |
| if ($committer->end) $end = date("Y.m.d", $committer->end); | |
| echo "$committer->email ($start to $end)<br/>"; | |
| } | |
| } | |
| function dump_bugs(&$bugs) { | |
| $count = count($bugs); | |
| echo "<p>$count bugs</p>"; | |
| if ($count == 0) return; | |
| $ids = array(); | |
| foreach ($bugs as $bug) $ids[] = $bug->bug_id; | |
| $all = implode(',', $ids); | |
| echo "<p>Review all in <a target=\"_blank\" href=\"https://bugs.eclipse.org/bugs/buglist.cgi?bug_id=$all\">Eclipse Bugzilla</a>.</p>"; | |
| echo "<ul>"; | |
| foreach ($bugs as $bug) { | |
| echo "<li>"; | |
| dump_bug($bug); | |
| echo "</li>"; | |
| $ids[] = $bug->bug_id; | |
| } | |
| echo "</ul>"; | |
| } | |
| function dump_bug(&$bug) { | |
| $product = htmlentities($bug->product); | |
| $title = htmlentities($bug->title); | |
| echo "$product <a target=\"_blank\" href=\"https://bugs.eclipse.org/$bug->bug_id\">$bug->bug_id</a> $title"; | |
| dump_flags($bug->flags); | |
| if (count($bug->patches) == 0) { | |
| echo " (no attachments)"; | |
| } else { | |
| echo "<ul>"; | |
| foreach ($bug->patches as $patch) { | |
| echo "<li>"; | |
| dump_patch($patch); | |
| echo "</li>"; | |
| } | |
| echo "</ul>"; | |
| } | |
| } | |
| function dump_patch(&$patch) { | |
| $description = htmlentities($patch->description); | |
| if ($patch->by_committer) echo "<strike>"; | |
| echo "<a target=\"_blank\" href=\"https://bugs.eclipse.org/bugs/attachment.cgi?id=$patch->attach_id&action=edit\">$patch->attach_id</a>"; | |
| //echo " ($patch->contributor)"; | |
| echo ": $description"; | |
| dump_flags($patch->flags); | |
| if ($patch->is_patch) echo " (PATCH)"; | |
| echo " "; | |
| echo date("Y.m.d", $patch->creation); | |
| if ($patch->by_committer) echo "</strike>"; | |
| } | |
| function dump_flags(&$flags) { | |
| if (!$flags) return; | |
| $collect = array(); | |
| foreach ($flags as $flag) { | |
| $collect[] = $flag->name . $flag->value; | |
| } | |
| echo ' [' . implode(', ', $collect) . ']'; | |
| } | |
| /* | |
| * This function returns true if the contributor was a committer on the project | |
| * at the time the patch was created. | |
| */ | |
| function is_contributor_committer(&$patch, &$committers) { | |
| $contributor = $patch->contributor; | |
| $creation = $patch->creation; | |
| return is_project_committer($contributor, $creation, $committers); | |
| } | |
| /** | |
| * @internal | |
| * @param string $contributor email address | |
| * @param unknown $creation | |
| * @param unknown $committers | |
| * @return boolean | |
| */ | |
| function is_project_committer($contributor, $creation, $committers) { | |
| $trace = trace("Checking whether or not $contributor is a committer."); | |
| if (!isset($committers[$contributor])) { | |
| $trace->trace('No committer record found'); | |
| return false; | |
| } | |
| $trace->trace('Patch was created on ' . date('Y/m/d', $creation)); | |
| $committer = $committers[$contributor]; | |
| $trace->trace('Committer start date ' . date('Y/m/d', $committer->start)); | |
| if ($committer->start > $creation) { | |
| $trace->trace('Committer status started after patch.'); | |
| return false; | |
| } | |
| if ($committer->end) { | |
| $trace->trace('Committer end date ' . date('Y/m/d', $committer->end)); | |
| if ($committer->end < $creation) { | |
| $trace->trace('Committer status ended before patch.'); | |
| return false; | |
| } | |
| } | |
| $trace->trace ("$contributor was a committer at the time of contribution!"); | |
| return true; | |
| } | |
| /** | |
| * This function returns an array containing Bug instances representing | |
| * bugs that have been flagged as resolutions to 'security' issues. | |
| * | |
| * @return Bug[] | |
| */ | |
| function findResolvedSecurityBugs() { | |
| // TODO Generalize this with #find_bugs | |
| global $App; | |
| $sql = "select | |
| b.bug_id as bug_id, | |
| p.name as product, | |
| c.name as component, | |
| b.short_desc as title, | |
| b.target_milestone as target_milestone | |
| from bugs as b | |
| join products as p on (b.product_id=p.id) | |
| join components as c on (b.component_id = c.id) | |
| join keywords as k on (b.bug_id=k.bug_id) | |
| join keyworddefs as kd on (k.keywordid=kd.id) | |
| where | |
| b.bug_id not in (select bg.bug_id from bug_group_map as bg join groups as g on (bg.group_id=g.id and g.name = 'Security_Advisories')) | |
| and kd.name='security' | |
| and b.bug_status IN ( 'RESOLVED', 'VERIFIED', 'CLOSED' ) | |
| and b.resolution IN ( 'FIXED' )"; | |
| $result = $App->bugzilla_sql($sql); | |
| $bugs = array(); | |
| while( $row = mysql_fetch_assoc($result) ) { | |
| $bug = new Bug(); | |
| $bug->bug_id = $row['bug_id']; | |
| $bug->title = $row['title']; | |
| $bug->product = $row['product']; | |
| $bug->component = $row['component']; | |
| $bug->target_milestone = $row['target_milestone']; | |
| $bugs[] = $bug; | |
| } | |
| return $bugs; | |
| } | |
| ?> |