Rev 1293 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
################################################################################ Codestriker: Copyright (c) 2001, 2002 David Sitsky. All rights reserved.# sits@users.sourceforge.net## This program is free software; you can redistribute it and modify it under# the terms of the GPL.# Model object for handling topic data.package Codestriker::Model::Topic;use strict;use Encode qw(decode_utf8);use Codestriker::DB::DBI;use Codestriker::Model::File;use Codestriker::Model::Metrics;sub new {my ($class, $topicid) = @_;my $self = {};$self->{topicid} = 0;$self->{author} = "";$self->{title} = "";$self->{bug_ids} = "";$self->{reviewers} = "";$self->{cc} = "";$self->{description} = "";$self->{document} = "";$self->{creation_ts} = "";$self->{modified_ts} = "";$self->{topic_state} = "";$self->{topic_state_id} = 0;$self->{version} = 0;$self->{start_tag} = "";$self->{end_tag} = "";$self->{module} = "";$self->{repository} = "";$self->{project_id} = "";$self->{project_name} = "";$self->{obsoleted_topics} = [];$self->{obsoleted_by} = [];$self->{comments} = [];$self->{metrics} = Codestriker::Model::Metrics->new($topicid);## 29-Aug-07 SJK: Create a class variable to hold an optional URL that is# often included in the description, for linking to Release Manager etc.#$self->{desc_url} = "";bless $self, $class;if (defined($topicid)) {$self->read($topicid);}return $self;}# Delete the specified participant type from the topic.sub _delete_participants($$$) {my ($self, $dbh, $type) = @_;my $delete_participants =$dbh->prepare_cached('DELETE FROM participant ' .'WHERE topicid = ? AND type = ?');my $success = defined $delete_participants;$success &&= $delete_participants->execute($self->{topicid}, $type);return $success;}# Insert the specified participants into the topic.sub _insert_participants($$$$$) {my ($self, $dbh, $type, $participants, $timestamp) = @_;my $insert_participant =$dbh->prepare_cached('INSERT INTO participant (email, topicid, type,' .'state, modified_ts, version) ' .'VALUES (?, ?, ?, ?, ?, ?)');my $success = defined $insert_participant;my @participants = split /, /, $participants;for (my $i = 0; $i <= $#participants; $i++) {$success &&= $insert_participant->execute($participants[$i],$self->{topicid}, $type, 0,$timestamp, 0);}return $success;}# Delete the bugids associated with a particular topic.sub _delete_bug_ids($$) {my ($self, $dbh) = @_;my $delete_topicbug =$dbh->prepare_cached('DELETE FROM topicbug WHERE topicid = ?');my $success = defined $delete_topicbug;$success &&= $delete_topicbug->execute($self->{topicid});return $success;}# Insert the comma-separated list of bug_ids into the topic.sub _insert_bug_ids($$$) {my ($self, $dbh, $bug_ids) = @_;my $insert_bugs =$dbh->prepare_cached('INSERT INTO topicbug (topicid, bugid) ' .'VALUES (?, ?)');my $success = defined $insert_bugs;my @bug_ids = split /, /, $bug_ids;for (my $i = 0; $i <= $#bug_ids; $i++) {$success &&= $insert_bugs->execute($self->{topicid}, $bug_ids[$i]);}return $success;}# Create a new topic with all of the specified properties.sub create($$$$$$$$$$$$) {my ($self, $topicid, $author, $title, $bug_ids, $reviewers, $cc,$description, $document, $start_tag, $end_tag, $module,$repository, $projectid, $deltas_ref, $obsoleted_topics) = @_;my $timestamp = Codestriker->get_timestamp(time);$self->{topicid} = $topicid;$self->{author} = $author;$self->{title} = $title;$self->{bug_ids} = $bug_ids;$self->{reviewers} = $reviewers;$self->{cc} = $cc;$self->{description} = $description;$self->{document} = $document;$self->{creation_ts} = $timestamp;$self->{modified_ts} = $timestamp;$self->{topic_state} = 0;$self->{topic_state_id} = 0;$self->{project_id} = $projectid;$self->{version} = 0;$self->{start_tag} = $start_tag;$self->{end_tag} = $end_tag;$self->{module} = $module;$self->{repository} = $repository;$self->{metrics} = Codestriker::Model::Metrics->new($topicid);$self->{obsoleted_topics} = [];$self->{obsoleted_by} = [];# Obtain a database connection.my $dbh = Codestriker::DB::DBI->get_connection();# Create the prepared statements.my $insert_topic =$dbh->prepare_cached('INSERT INTO topic (id, author, title, ' .'description, document, state, creation_ts, ' .'modified_ts, version, start_tag, end_tag, ' .'module, repository, projectid) ' .'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');my $success = defined $insert_topic;# Create all of the necessary rows. It is assumed state 0 is the initial# state.$success &&= $insert_topic->execute($topicid, $author, $title,$description, $document, 0,$timestamp, $timestamp, 0,$start_tag, $end_tag, $module,$repository, $projectid);# Insert the associated bug records.$success &&= $self->_insert_bug_ids($dbh, $bug_ids);# Insert the reviewers and cc participants.$success &&=$self->_insert_participants($dbh,$Codestriker::PARTICIPANT_REVIEWER,$reviewers, $timestamp);$success &&=$self->_insert_participants($dbh,$Codestriker::PARTICIPANT_CC,$cc, $timestamp);# Create the appropriate delta rows.$success &&= Codestriker::Model::File->create($dbh, $topicid, $deltas_ref);# Create any obsolete records, if any.if (defined $obsoleted_topics && $obsoleted_topics ne '') {my $insert_obsolete_topic =$dbh->prepare_cached('INSERT INTO topicobsolete ' .'(topicid, obsoleted_by) ' .'VALUES (?, ?)');my $success = defined $insert_obsolete_topic;my @data = split ',', $obsoleted_topics;my @obsoleted = ();for (my $i = 0; $success && $i <= $#data; $i+=2) {my $obsolete_topic_id = $data[$i];my $obsolete_topic_version = $data[$i+1];$success &&=$insert_obsolete_topic->execute($obsolete_topic_id,$topicid);push @obsoleted, $obsolete_topic_id if $success;}$self->{obsoleted_topics} = \@obsoleted;}Codestriker::DB::DBI->release_connection($dbh, $success);die $dbh->errstr unless $success;}# Read the contents of a specific topic, and return the results in the# provided reference variables.sub read($$) {my ($self, $topicid) = @_;$self->{topicid} = $topicid;# Obtain a database connection.my $dbh = Codestriker::DB::DBI->get_connection();# Setup the prepared statements.my $select_topic = $dbh->prepare_cached('SELECT topic.id, topic.author, ' .'topic.title, ' .'topic.description, ' .'topic.document, topic.state, ' .'topic.creation_ts, ' .'topic.modified_ts, ' .'topic.version, ' .'topic.start_tag, ' .'topic.end_tag, ' .'topic.module, ' .'topic.repository, ' .'project.id, project.name ' .'FROM topic, project ' .'WHERE topic.id = ? AND ' .'topic.projectid = project.id');my $select_bugs =$dbh->prepare_cached('SELECT bugid FROM topicbug WHERE topicid = ?');my $select_participants =$dbh->prepare_cached('SELECT type, email FROM participant ' .'WHERE topicid = ?');my $select_obsoleted_by =$dbh->prepare_cached('SELECT obsoleted_by FROM topicobsolete ' .'WHERE topicid = ?');my $select_topics_obsoleted =$dbh->prepare_cached('SELECT topicid FROM topicobsolete ' .'WHERE obsoleted_by = ?');my $success = defined $select_topic && defined $select_bugs &&defined $select_participants && defined $select_obsoleted_by &&defined $select_topics_obsoleted;my $rc = $Codestriker::OK;# Retrieve the topic information.$success &&= $select_topic->execute($topicid);my ($id, $author, $title, $description, $document, $state,$creationtime, $modifiedtime, $version, $start_tag, $end_tag,$module, $repository, $projectid, $projectname);if ($success) {($id, $author, $title, $description, $document, $state,$creationtime, $modifiedtime, $version, $start_tag, $end_tag,$module, $repository, $projectid, $projectname)= $select_topic->fetchrow_array();$select_topic->finish();if (!defined $id) {$success = 0;$rc = $Codestriker::INVALID_TOPIC;}}# Retrieve the bug ids relating to this topic.my @bugs = ();$success &&= $select_bugs->execute($topicid);if ($success) {my @data;while (@data = $select_bugs->fetchrow_array()) {push @bugs, $data[0];}$select_bugs->finish();}# Retrieve the participants in this review.my @reviewers = ();my @cc = ();$success &&= $select_participants->execute($topicid);if ($success) {while (my @data = $select_participants->fetchrow_array()) {if ($data[0] == 0) {push @reviewers, $data[1];} else {push @cc, $data[1];}}$select_participants->finish();}# Retrieve the topics obsoleted by this topic.$success &&= $select_topics_obsoleted->execute($topicid);my @obsoleted_topics = ();if ($success) {while (my ($id) = $select_topics_obsoleted->fetchrow_array()) {push @obsoleted_topics, $id;}$select_topics_obsoleted->finish();}# Retrieve the topics that have obsoleted this topic.$success &&= $select_obsoleted_by->execute($topicid);my @obsoleted_by = ();if ($success) {while (my ($id) = $select_obsoleted_by->fetchrow_array()) {push @obsoleted_by, $id;}$select_obsoleted_by->finish();}# Close the connection, and check for any database errors.Codestriker::DB::DBI->release_connection($dbh, $success);# Store the data into the referenced variables if the operation was# successful.if ($success) {$self->{author} = $author;$self->{title} = decode_utf8($title);$self->{bug_ids} = join ', ', @bugs;$self->{reviewers} = join ', ', @reviewers;$self->{cc} = join ', ', @cc;$self->{description} = decode_utf8($description);$self->{document} = decode_utf8($document);$self->{creation_ts} = $creationtime;$self->{modified_ts} = $modifiedtime;$self->{topic_state} = $Codestriker::topic_states[$state];$self->{topic_state_id} = $state;$self->{project_id} = $projectid;$self->{project_name} = decode_utf8($projectname);$self->{start_tag} = $start_tag;$self->{end_tag} = $end_tag;$self->{module} = $module;$self->{version} = $version;$self->{metrics} = Codestriker::Model::Metrics->new($topicid);$self->{obsoleted_topics} = \@obsoleted_topics;$self->{obsoleted_by} = \@obsoleted_by;# Set the repository to the default system value if it is not defined.if (!defined $repository || $repository eq "") {$self->{repository} = $Codestriker::default_repository;} else {$self->{repository} = $repository;}## 29-Aug-07 SJK: Detect & store optional URL that is# often included in the description, for linking to Release Manager etc.#if ( $self->{description} =~ m/(http:\/\/[^ \t\n]*)/i ) {$self->{desc_url} = $1;}}return $success ? $Codestriker::OK : $rc;}# Reads from the db if needed, and returns the list of comments for# this topic. If the list of comments have already been returned, the# function will skip the db call, and just return the list from# memory.sub read_comments {my ($self) = shift;if (scalar(@{$self->{comments}}) == 0) {my @comments = Codestriker::Model::Comment->read_all_comments_for_topic($self->{topicid});$self->{comments} = \@comments;}return @{$self->{comments}};}# returns a count of the comments that have the given comment# metric (commentmetrictype) set to value. Used# on the search page.sub get_comment_metric_count {my ($self,$commentmetrictype,$value) = @_;my $count = 0;if (scalar(@{$self->{comments}}) == 0) {# Obtain a database connection.my $dbh = Codestriker::DB::DBI->get_connection();# the comments have not been read into memory yet.$count = $dbh->selectrow_array('SELECT COUNT(commentstatemetric.value)FROM commentstatemetric, commentstateWHERE commentstate.topicid = ? ANDcommentstate.id = commentstatemetric.id ANDcommentstatemetric.name = ? ANDcommentstatemetric.value = ?',{}, $self->{topicid},$commentmetrictype, $value);Codestriker::DB::DBI->release_connection($dbh, 1);} else {# already read into memory, don't hit the database.foreach my $comment (@{$self->{comments}}) {if ( exists( $comment->{$commentmetrictype} ) &&$comment->{$commentmetrictype} eq $value ) {++$count;}}}return $count;}# Retrieve the changed files which are a part of this review. It will only pull them# from the database once.sub get_filestable{my ($self,$filenames, $revisions, $offsets, $binary, $numchanges) = @_;if (exists ($self->{filetable})) {( $filenames, $revisions, $offsets,$binary, $numchanges ) = @{$self->{filetable}};}else {Codestriker::Model::File->get_filetable($self->{topicid},$filenames,$revisions,$offsets,$binary,$numchanges);$self->{filetable} = [$filenames,$revisions,$offsets,$binary,$numchanges ];}}# Determine if the specified topic id exists in the table or not.sub exists($) {my ($topicid) = @_;# Obtain a database connection.my $dbh = Codestriker::DB::DBI->get_connection();# Prepare the statement and execute it.my $select_topic = $dbh->prepare_cached('SELECT COUNT(*) FROM topic ' .'WHERE id = ?');my $success = defined $select_topic;$success &&= $select_topic->execute($topicid);my $count;if ($success) {($count) = $select_topic->fetchrow_array();$select_topic->finish();}Codestriker::DB::DBI->release_connection($dbh, $success);die $dbh->errstr unless $success;return $count;}# This function returns the metrics objects that are part of the topic.sub get_metrics {my ($self) = @_;return $self->{metrics};}# Returns the size of the topic text in lines. If the topic is a diff topic# it attempts to only count the lines that have changed, and not count the# context around the lines.sub get_topic_size_in_lines {my ($self) = @_;my @deltas = Codestriker::Model::Delta->get_delta_set($self->{topicid}, -1);my $line_count = 0;foreach my $delta (@deltas){my @document = split /\n/, $delta->{text};$line_count += scalar( grep /^[+-][^+-][^+-]/, @document );}return $line_count;}# This function is used to create a new topic id. The function insures# that the new topic id is difficult to guess, and is not taken in the# database already.sub create_new_topicid {# For "hysterical" reasons, the topic id is randomly generated. Seed the# generator based on the time and the pid. Keep searching until we find# a free topicid. In 99% of the time, we will get a new one first time.srand(time() ^ ($$ + ($$ << 15)));my $topicid;do {$topicid = int rand(10000000);} while (Codestriker::Model::Topic::exists($topicid));return $topicid;}# Everytime a topic is stored the version number is incremented. When# a page is created it includes the version number of the topic used# to create the page. The user posts information back to server to# change, the version information needs to be checked to make sure# somebody else has not modified the server.sub check_for_stale($$) {my ($self, $version) = @_;return $self->{version} ne $version;}# Update the state of the specified topic.sub change_state($$) {my ($self, $new_state) = @_;my $modified_ts = Codestriker->get_timestamp(time);# Map the new state to its number.my $new_stateid;for ($new_stateid = 0; $new_stateid <= $#Codestriker::topic_states;$new_stateid++) {last if ($Codestriker::topic_states[$new_stateid] eq $new_state);}if ($new_stateid > $#Codestriker::topic_states) {die "Unable to change topic to invalid state: \"$new_state\"";}# Obtain a database connection.my $dbh = Codestriker::DB::DBI->get_connection();# Check that the version reflects the current version in the DB.my $select_topic =$dbh->prepare_cached('SELECT version ' .'FROM topic WHERE id = ?');my $update_topic =$dbh->prepare_cached('UPDATE topic SET version = ?, state = ?, ' .'modified_ts = ? WHERE id = ?');my $success = defined $select_topic && defined $update_topic;my $rc = $Codestriker::OK;# Retrieve the current topic data.$success &&= $select_topic->execute($self->{topicid});# Make sure that the topic still exists, and is therefore valid.my ($current_version);if ($success && ! (($current_version) =$select_topic->fetchrow_array())) {# Invalid topic id.$success = 0;$rc = $Codestriker::INVALID_TOPIC;}$success &&= $select_topic->finish();# Check the version number.if ($self->{version} != $current_version) {$success = 0;$rc = $Codestriker::STALE_VERSION;}# If the state hasn't changed, don't do anything, otherwise update the# topic.if ($new_state ne $self->{topic_state}) {$self->{version} = $self->{version} + 1;$success &&= $update_topic->execute($self->{version}, $new_stateid,$modified_ts,$self->{topicid});}$self->{modified_ts} = $modified_ts;$self->{topic_state} = $new_state;Codestriker::DB::DBI->release_connection($dbh, $success);return $rc;}# Update the properties of the specified topic. This is not implemented# very efficiently, however it is not expected to be called very often.sub update($$$$$$$$$$) {my ($self, $new_title, $new_author, $new_reviewers, $new_cc,$new_repository, $new_bug_ids, $new_projectid, $new_description,$new_state) = @_;# First check that the version matches the current topic version in the# database.my $dbh = Codestriker::DB::DBI->get_connection();my $select_topic =$dbh->prepare_cached('SELECT version ' .'FROM topic WHERE id = ?');my $success = defined $select_topic;my $rc = $Codestriker::OK;# Make sure that the topic still exists, and is therefore valid.$success &&= $select_topic->execute($self->{topicid});my $current_version;if ($success &&! (($current_version) =$select_topic->fetchrow_array())) {# Invalid topic id.$success = 0;$rc = $Codestriker::INVALID_TOPIC;}$success &&= $select_topic->finish();# Check the version number.if ($success && $self->{version} != $current_version) {$success = 0;$rc = $Codestriker::STALE_VERSION;}# Get the modified date to the current time.my $modified_ts = Codestriker->get_timestamp(time);# Map the new state to its number.my $new_stateid;for ($new_stateid = 0; $new_stateid <= $#Codestriker::topic_states;$new_stateid++) {last if ($Codestriker::topic_states[$new_stateid] eq $new_state);}if ($new_stateid > $#Codestriker::topic_states) {die "Unable to change topic to invalid state: \"$new_state\"";}# Update the topic object's properties.$self->{title} = $new_title;$self->{author} = $new_author;$self->{repository} = $new_repository;$self->{project_id} = $new_projectid;$self->{description} = $new_description;$self->{modified_ts} = $modified_ts;$self->{topic_state} = $new_state;$self->{topic_state_id} = $new_stateid;# Now update the database with the new properties.my $update_topic =$dbh->prepare_cached('UPDATE topic SET version = ?, state = ?, ' .'modified_ts = ?, ' .'title = ?, author = ?, ' .'repository = ?, projectid = ?, ' .'description = ? WHERE id = ?');$success &&= defined $update_topic;# If the state hasn't changed, don't do anything, otherwise update the# topic.if ($success) {$self->{version} = $self->{version} + 1;$success &&= $update_topic->execute($self->{version}, $new_stateid,$modified_ts,$new_title, $new_author,$new_repository, $new_projectid,$new_description,$self->{topicid});}# Now delete all bugs associated with this topic, and recreate them again# if they have changed.if ($success && $self->{bug_ids} ne $new_bug_ids) {$success &&= $self->_delete_bug_ids($dbh);$success &&= $self->_insert_bug_ids($dbh, $new_bug_ids);$self->{bug_ids} = $new_bug_ids;}# Now delete all reviewers associated with this topic, and recreate# them again, if they have changed.if ($success && $self->{reviewers} ne $new_reviewers) {$success &&=$self->_delete_participants($dbh,$Codestriker::PARTICIPANT_REVIEWER);$success &&=$self->_insert_participants($dbh,$Codestriker::PARTICIPANT_REVIEWER,$new_reviewers, $modified_ts);$self->{reviewers} = $new_reviewers;}# Now delete all CCs associated with this topic, and recreate# them again, if they have changed.if ($success && $self->{cc} ne $new_cc) {$success &&=$self->_delete_participants($dbh, $Codestriker::PARTICIPANT_CC);$success &&=$self->_insert_participants($dbh, $Codestriker::PARTICIPANT_CC,$new_cc, $modified_ts);$self->{cc} = $new_cc;}Codestriker::DB::DBI->release_connection($dbh, $success);if ($success == 0 && $rc == $Codestriker::OK) {# Unexpected DB error.die $dbh->errstr;}return $rc;}# Return back the list of topics which match the specified parameters.sub query($$$$$$$$$$$$$$\@\@\@) {my ($type, $sauthor, $sreviewer, $scc, $sbugid, $sstate, $sproject, $stext,$stitle, $sdescription, $scomments, $sbody, $sfilename, $sort_order) = @_;# Obtain a database connection.my $database = Codestriker::DB::Database->get_database();my $dbh = $database->get_connection();# If there are wildcards in the author, reviewer, or CC fields,# replace them with the appropriate SQL wildcards.$sauthor =~ s/\*/%/g if $sauthor ne "";$sreviewer =~ s/\*/%/g if $sreviewer ne "";$scc =~ s/\*/%/g if $scc ne "";# Automatically surround the search term term in wildcards, and replace# any wildcards appropriately.if ($stext ne "") {$stext =~ s/\*/%/g;if (! ($stext =~ /^%/o) ) {$stext = "%${stext}";}if (! ($stext =~ /%$/o) ) {$stext = "${stext}%";}}# Build up the query conditions.my $author_part = $sauthor eq "" ? "" :$database->case_insensitive_like("topic.author", $sauthor);my $reviewer_part = $sreviewer eq "" ? "" :($database->case_insensitive_like("participant.email", $sreviewer) ." AND type = $Codestriker::PARTICIPANT_REVIEWER");my $cc_part = $scc eq "" ? "" :($database->case_insensitive_like("participant.email", $scc) ." AND type = $Codestriker::PARTICIPANT_CC");my $bugid_part = $sbugid eq "" ? "" :("topicbug.bugid = " . $dbh->quote($sbugid));# Build up the state condition.my $state_part = "";if ($sstate ne "") {$state_part = "topic.state IN ($sstate)";}# Build up the project condition.my $project_part = "";if ($sproject ne "") {$project_part = "topic.projectid IN ($sproject)";}my $text_title_part =$database->case_insensitive_like("topic.title", $stext);my $text_description_part =$database->case_insensitive_like("topic.description", $stext);my $text_body_part =$database->case_insensitive_like("topic.document", $stext);my $text_filename_part =$database->case_insensitive_like("topicfile.filename", $stext);my $text_comment_part =$database->case_insensitive_like("commentdata.commentfield", $stext);# Build up the base query.my $query ="SELECT topic.id, topic.title, topic.description, " ."topic.author, topic.creation_ts, " ."topic.state, topicbug.bugid, participant.email, participant.type, " ."topic.version ";# Since Oracle < 9i can't handle LEFT OUTER JOIN, determine what tables# are required in this query and add them in.my $using_oracle = $Codestriker::db =~ /^DBI:Oracle/i;if ($using_oracle) {my @fromlist = ("topic", "topicbug", "participant");if ($stext ne "" && $scomments) {push @fromlist, "commentstate";push @fromlist, "commentdata";}if ($stext ne "" && $sfilename) {push @fromlist, "topicfile";}$query .= "FROM " . (join ', ', @fromlist) . " WHERE ";}else {$query .= "FROM topic ";}# Add the join to topicbug and participant.if ($using_oracle) {$query .= "topic.id = topicbug.topicid(+) AND " ."topic.id = participant.topicid(+) ";}else {$query .= "LEFT OUTER JOIN topicbug ON topic.id = topicbug.topicid " ."LEFT OUTER JOIN participant ON topic.id = participant.topicid ";}# Join with the comment table if required - GACK!if ($stext ne "" && $scomments) {if ($using_oracle) {$query .=' AND topic.id = commentstate.topicid(+) AND '.'commentstate.id = commentdata.commentstateid(+) ';}else {$query .='LEFT OUTER JOIN commentstate ON ' .'topic.id = commentstate.topicid '.'LEFT OUTER JOIN commentdata ON ' .'commentstate.id = commentdata.commentstateid ';}}# Join with the file table if required.if ($stext ne "" && $sfilename) {if ($using_oracle) {$query .= ' AND topic.id = topicfile.topicid(+) ';}else {$query .= 'LEFT OUTER JOIN topicfile ON ' .'topicfile.topicid = topic.id ';}}# Combine the "AND" conditions together. Note for Oracle, the 'WHERE'# keyword has already been used.my $first_condition = $using_oracle ? 0 : 1;$query = _add_condition($query, $author_part, \$first_condition);$query = _add_condition($query, $reviewer_part, \$first_condition);$query = _add_condition($query, $cc_part, \$first_condition);$query = _add_condition($query, $bugid_part, \$first_condition);# Handle the state set.if ($state_part ne "") {$query = _add_condition($query, $state_part, \$first_condition);}# Handle the project set.if ($project_part ne "") {$query = _add_condition($query, $project_part, \$first_condition);}# Handle the text searching part, which is a series of ORs.if ($stext ne "") {my @text_cond = ();push @text_cond, $text_title_part if $stitle;push @text_cond, $text_description_part if $sdescription;push @text_cond, $text_body_part if $sbody;push @text_cond, $text_filename_part if $sfilename;push @text_cond, $text_comment_part if $scomments;if ($#text_cond >= 0) {my $cond = join ' OR ', @text_cond;$query = _add_condition($query, $cond, \$first_condition);}}# Order the result by the creation date field.if (scalar( @$sort_order ) == 0) {# no sort order, defaults to topic creation.$query .= " ORDER BY topic.creation_ts ";}else {my @sort_terms;foreach my $sortItem (@$sort_order) {if ($sortItem eq "+title") {push @sort_terms, "topic.title";}elsif ($sortItem eq "-title") {push @sort_terms, "topic.title DESC";}elsif ($sortItem eq "+author") {push @sort_terms, "topic.author ";}elsif ($sortItem eq "-author") {push @sort_terms, "topic.author DESC";}elsif ($sortItem eq "+created") {push @sort_terms, "topic.creation_ts ";}elsif ($sortItem eq "-created") {push @sort_terms, "topic.creation_ts DESC";}elsif ($sortItem eq "+state") {push @sort_terms, "topic.state ";}elsif ($sortItem eq "-state") {push @sort_terms, "topic.state DESC";}else {die "unknown sort key $sortItem";}}$query .= " ORDER BY " . join(',',@sort_terms) . " ";}my $select_topic = $dbh->prepare_cached($query);my $success = defined $select_topic;$success &&= $select_topic->execute();my $lastid;my @topic_list;if ($success) {my ($id, $title, $author, $description, $creation_ts, $state, $bugid,$email, $type, $version);while (($id, $title, $description, $author, $creation_ts, $state,$bugid, $email, $type, $version) =$select_topic->fetchrow_array()) {# This is a bit heavy, but the search screen does need much# of the information in the topic object, it is much cleaner# to just return a fully formed topic object, rather than a# array tunned. If performace is an issue, then the topic# object should use lazy instatation to don't pull data from# the database unless it is needed.if ( !defined($lastid) || $id ne $lastid ) {my $new_topic = Codestriker::Model::Topic->new($id);push @topic_list,$new_topic;}$lastid = $id;}$select_topic->finish();}$database->release_connection();die $dbh->errstr unless $success;return @topic_list;}# Add the condition to the specified query string, returning the new query.sub _add_condition($$\$) {my ($query, $condition, $first_cond_ref) = @_;return $query if ($condition eq ""); # Nothing to do.if ($$first_cond_ref) {$$first_cond_ref = 0;$query .= " WHERE (" . $condition . ") ";} else {$query .= " AND (" . $condition . ") ";}return $query;}# Delete the specified topic.sub delete($) {my ($self) = @_;# Obtain a database connection.my $dbh = Codestriker::DB::DBI->get_connection();# Create the prepared statements.my $delete_topic = $dbh->prepare_cached('DELETE FROM topic WHERE id = ?');my $select = $dbh->prepare_cached('SELECT id FROM commentstate ' .'WHERE topicid = ?');my $delete_comments =$dbh->prepare_cached('DELETE FROM commentdata ' .'WHERE commentstateid = ?');my $delete_commentstate_metric =$dbh->prepare_cached('DELETE FROM commentstatemetric ' .'WHERE id = ?');my $delete_commentstate =$dbh->prepare_cached('DELETE FROM commentstate ' .'WHERE topicid = ?');my $delete_file =$dbh->prepare_cached('DELETE FROM topicfile WHERE topicid = ?');my $delete_delta =$dbh->prepare_cached('DELETE FROM delta WHERE topicid = ?');my $topic_metrics =$dbh->prepare_cached('DELETE FROM topicmetric WHERE topicid = ?');my $user_metrics =$dbh->prepare_cached('DELETE FROM topicusermetric WHERE topicid = ?');my $topic_history =$dbh->prepare_cached('DELETE FROM topichistory WHERE topicid = ?');my $topic_view_history =$dbh->prepare_cached('DELETE FROM topicviewhistory WHERE topicid = ?');my $commentstate_history =$dbh->prepare_cached('DELETE FROM commentstatehistory WHERE id = ?');my $obsolete_records =$dbh->prepare_cached('DELETE FROM topicobsolete WHERE ' .'topicid = ? OR obsoleted_by = ?');my $success = defined $delete_topic && defined $delete_comments &&defined $delete_commentstate && defined $select &&defined $delete_file && defined $delete_delta &&defined $topic_metrics && defined $user_metrics &&defined $topic_history && defined $topic_view_history &&defined $commentstate_history && $delete_commentstate_metric &&defined $obsolete_records;# Now do the deed.$success &&= $select->execute($self->{topicid});if ($success) {foreach my $commentstate (@{$select->fetchall_arrayref()}) {my $commentstateid = $commentstate->[0];$success &&= $delete_comments->execute($commentstateid);$success &&= $commentstate_history->execute($commentstateid);$success &&= $delete_commentstate_metric->execute($commentstateid);}}$success &&= $delete_commentstate->execute($self->{topicid});$success &&= $delete_topic->execute($self->{topicid});$success &&= $delete_comments->execute($self->{topicid});$success &&= $delete_file->execute($self->{topicid});$success &&= $delete_delta->execute($self->{topicid});$success &&= $topic_metrics->execute($self->{topicid});$success &&= $user_metrics->execute($self->{topicid});$success &&= $self->_delete_bug_ids($dbh);$success &&=$self->_delete_participants($dbh, $Codestriker::PARTICIPANT_REVIEWER);$success &&=$self->_delete_participants($dbh, $Codestriker::PARTICIPANT_CC);$success &&= $topic_history->execute($self->{topicid});$success &&= $topic_view_history->execute($self->{topicid});$success &&= $obsolete_records->execute($self->{topicid},$self->{topicid});Codestriker::DB::DBI->release_connection($dbh, $success);# Indicate the success of the operation.return $success ? $Codestriker::OK : $Codestriker::INVALID_TOPIC;}1;