Bug 46221, Bug 70605 - use Lingua::Stem::Snowball instead of hard-coded russian stemmer, store tsvectors in PostgreSQL fulltext tables for ts_rank() performance
git-svn-id: svn://svn.office.custis.ru/3rdparty/bugzilla.org/trunk@1344 6955db30-a419-402b-8a0d-67ecbb4d7f56master
parent
3ed6d7df0e
commit
710e7747d2
|
@ -1141,42 +1141,38 @@ sub _extract_multi_selects {
|
|||
# Should be called any time you update short_desc or change a comment.
|
||||
sub _sync_fulltext
|
||||
{
|
||||
use utf8;
|
||||
my ($self, $new_bug) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my ($short_desc) = $dbh->selectrow_array(
|
||||
"SELECT short_desc FROM bugs WHERE bug_id=?", undef, $self->id
|
||||
);
|
||||
my @comments = @{ $dbh->selectall_arrayref(
|
||||
my ($nopriv, $priv) = ([], []);
|
||||
for (@{ $dbh->selectall_arrayref(
|
||||
"SELECT thetext, isprivate FROM longdescs WHERE bug_id=?",
|
||||
undef, $self->id
|
||||
) || [] };
|
||||
my @no_private = grep { !$_->[1] } @comments;
|
||||
my $all = join "\n", map { $_->[0] } @comments;
|
||||
my $nopriv = join "\n", map { $_->[0] } @no_private;
|
||||
# Bug 46221 - Russian Snowball Stemmer in Bugzilla fulltext search
|
||||
# Not for PostgreSQL: it has built-in Snowball stemmer
|
||||
if (!$dbh->isa('Bugzilla::DB::Pg'))
|
||||
) || [] })
|
||||
{
|
||||
use utf8;
|
||||
$short_desc = stem_text($short_desc);
|
||||
$all = stem_text($all);
|
||||
$nopriv = stem_text($nopriv);
|
||||
$_->[1] ? push @$priv, $_->[0] : push @$nopriv, $_->[0];
|
||||
}
|
||||
# O_o как оно может быть здесь tainted - непонятно, но иногда стреляет
|
||||
trick_taint($short_desc);
|
||||
trick_taint($all);
|
||||
trick_taint($nopriv);
|
||||
my @bind = ($short_desc, $all, $nopriv, $self->id);
|
||||
$nopriv = join "\n", @$nopriv;
|
||||
$priv = join "\n", @$priv;
|
||||
my $row = [ $short_desc, $nopriv, $priv ];
|
||||
$_ = $dbh->quote_fulltext($_) for @$row;
|
||||
## O_o Don't know how can it be tainted here, sometimes it was. Checking if it goes away.
|
||||
#trick_taint($row);
|
||||
my $sql;
|
||||
if ($new_bug)
|
||||
{
|
||||
$sql = "INSERT INTO bugs_fulltext (short_desc, comments, comments_noprivate, bug_id) VALUES (?, ?, ?, ?)";
|
||||
$sql = "INSERT INTO bugs_fulltext (bug_id, short_desc, comments, comments_private)".
|
||||
" VALUES (".join(',', $self->id, @$row).")";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sql = "UPDATE bugs_fulltext SET short_desc=?, comments=?, comments_noprivate=? WHERE bug_id=?";
|
||||
$sql = "UPDATE bugs_fulltext SET short_desc=$row->[0],".
|
||||
" comments=$row->[1], comments_private=$row->[2] WHERE bug_id=".$self->id;
|
||||
}
|
||||
return $dbh->do($sql, undef, @bind);
|
||||
return $dbh->do($sql);
|
||||
}
|
||||
|
||||
# This is the correct way to delete bugs from the DB.
|
||||
|
|
|
@ -71,8 +71,13 @@ sub get_param_list {
|
|||
name => 'specific_search_allow_empty_words',
|
||||
type => 'b',
|
||||
default => 1
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
name => 'stem_language',
|
||||
type => 't',
|
||||
default => 'en',
|
||||
},
|
||||
);
|
||||
return @param_list;
|
||||
}
|
||||
|
|
|
@ -176,8 +176,11 @@ use Cwd qw(abs_path);
|
|||
|
||||
PASSWORD_DIGEST_ALGORITHM
|
||||
PASSWORD_SALT_LENGTH
|
||||
|
||||
|
||||
CGI_URI_LIMIT
|
||||
|
||||
LANG_ISO_FULL
|
||||
LANG_FULL_ISO
|
||||
);
|
||||
|
||||
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
|
||||
|
@ -510,6 +513,29 @@ use constant PASSWORD_SALT_LENGTH => 8;
|
|||
# can be safely done or not based on the web server's URI length setting.
|
||||
use constant CGI_URI_LIMIT => 8000;
|
||||
|
||||
# Full language names corresponding to 2-letter ISO codes
|
||||
# Used to select stemming language in fulltext search
|
||||
use constant LANG_ISO_FULL => {
|
||||
da => 'danish',
|
||||
nl => 'dutch',
|
||||
en => 'english',
|
||||
fi => 'finnish',
|
||||
fr => 'french',
|
||||
de => 'german',
|
||||
hu => 'hungarian',
|
||||
it => 'italian',
|
||||
no => 'norwegian',
|
||||
pt => 'portuguese',
|
||||
ro => 'romanian',
|
||||
ru => 'russian',
|
||||
es => 'spanish',
|
||||
sv => 'swedish',
|
||||
tr => 'turkish',
|
||||
};
|
||||
|
||||
# The reverse of LANG_ISO_FULL
|
||||
use constant LANG_FULL_ISO => { reverse %{LANG_ISO_FULL()} };
|
||||
|
||||
sub bz_locations {
|
||||
# We know that Bugzilla/Constants.pm must be in %INC at this point.
|
||||
# So the only question is, what's the name of the directory
|
||||
|
|
|
@ -96,6 +96,20 @@ sub connect_main {
|
|||
$lc->{db_sock}, $lc->{db_user}, $lc->{db_pass});
|
||||
}
|
||||
|
||||
sub connect_sphinx
|
||||
{
|
||||
return undef unless Bugzilla->params->{use_sphinx};
|
||||
my $host = Bugzilla->params->{sphinxql_host};
|
||||
my $port = Bugzilla->params->{sphinxql_port};
|
||||
my $socket = Bugzilla->params->{sphinxql_socket};
|
||||
$host = [ $host ? "host=$host" : () ];
|
||||
push @$host, "port=$port" if $port;
|
||||
push @$host, "mysql_socket=$socket" if $socket;
|
||||
my $sphinx = DBI->connect("DBI:mysql:".join(';', @$host), undef, undef, { mysql_auto_reconnect => 1 });
|
||||
die $DBI::errstr if !$sphinx;
|
||||
return $sphinx;
|
||||
}
|
||||
|
||||
sub _connect {
|
||||
my ($driver, $host, $dbname, $port, $sock, $user, $pass) = @_;
|
||||
|
||||
|
@ -361,6 +375,10 @@ sub sql_fulltext_search
|
|||
# standard ANSI SQL, without real full text search support. DB specific
|
||||
# modules should override this, as this will be always much slower.
|
||||
|
||||
# stem text
|
||||
my $lang = LANG_FULL_ISO->{lc(Bugzilla->params->{stem_language}||'')} || 'en';
|
||||
$text = stem_text($text, $lang);
|
||||
|
||||
# make the string lowercase to do case insensitive search
|
||||
my $lower_text = lc($text);
|
||||
|
||||
|
@ -396,6 +414,16 @@ sub sql_fulltext_search
|
|||
return ($term, $term);
|
||||
}
|
||||
|
||||
# Prepare string for inserting into full-text table and return the SQL expression
|
||||
# Individual DB implementations should override this if they have built-in stemmer
|
||||
sub quote_fulltext
|
||||
{
|
||||
my $self = shift;
|
||||
my ($a) = @_;
|
||||
my $lang = LANG_FULL_ISO->{lc(Bugzilla->params->{stem_language}||'')} || 'en';
|
||||
return $self->quote(stem_text($a, $lang));
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# General Info Methods
|
||||
#####################################################################
|
||||
|
|
|
@ -178,6 +178,10 @@ sub sql_fulltext_search
|
|||
{
|
||||
my ($self, $column, $text) = @_;
|
||||
|
||||
# stem text
|
||||
my $lang = LANG_FULL_ISO->{lc(Bugzilla->params->{stem_language}||'')} || 'en';
|
||||
$text = stem_text($text, $lang);
|
||||
|
||||
# quote un-quoted compound words
|
||||
my @words = quotewords('[\s()]+', 'delimiters', $text);
|
||||
if ($text =~ /(?:^|\W)[+\-<>~"()]/ || $text =~ /[()"*](?:$|\W)/)
|
||||
|
|
|
@ -44,6 +44,7 @@ package Bugzilla::DB::Pg;
|
|||
use strict;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Constants qw(LANG_ISO_FULL);
|
||||
use DBD::Pg;
|
||||
|
||||
# This module extends the DB interface via inheritance
|
||||
|
@ -187,19 +188,26 @@ sub sql_fulltext_search
|
|||
{
|
||||
my $self = shift;
|
||||
my ($column, $text) = @_;
|
||||
my $language = Bugzilla->localconfig->{postgres_fulltext_language} || 'english';
|
||||
$language = $self->quote($language).',';
|
||||
$text = $self->quote($text);
|
||||
# Try to_tsquery, and use plainto_tsquery if the syntax is incorrect
|
||||
# FIXME reporting errors to user would be useful here
|
||||
# FIXME reporting query parse errors to user would be useful here
|
||||
eval { $self->do("SELECT to_tsquery($language$text)") };
|
||||
my $op = $@ ? 'plainto_tsquery' : 'to_tsquery';
|
||||
return (
|
||||
"(to_tsvector($language$column) \@\@ $op($language$text))",
|
||||
"(ts_rank(to_tsvector($language$column), $op($language$text)))",
|
||||
"($column \@\@ $op($language$text))",
|
||||
"(ts_rank($column, $op($language$text)))",
|
||||
);
|
||||
}
|
||||
|
||||
# PostgreSQL has built-in stemmers
|
||||
sub quote_fulltext
|
||||
{
|
||||
my $self = shift;
|
||||
my ($a) = @_;
|
||||
my $lang = LANG_ISO_FULL->{Bugzilla->params->{stem_language}} || 'english';
|
||||
return "to_tsvector('$lang',".$self->quote($a).")";
|
||||
}
|
||||
|
||||
sub real_table_list
|
||||
{
|
||||
my $self = shift;
|
||||
|
|
|
@ -315,24 +315,30 @@ use constant ABSTRACT_SCHEMA => {
|
|||
|
||||
bugs_fulltext => {
|
||||
FIELDS => [
|
||||
bug_id => {TYPE => 'INT3', NOTNULL => 1, PRIMARYKEY => 1,
|
||||
REFERENCES => {TABLE => 'bugs',
|
||||
COLUMN => 'bug_id',
|
||||
DELETE => 'CASCADE'}},
|
||||
bug_id => {TYPE => 'INT3', NOTNULL => 1, PRIMARYKEY => 1,
|
||||
REFERENCES => {TABLE => 'bugs',
|
||||
COLUMN => 'bug_id',
|
||||
DELETE => 'CASCADE'}},
|
||||
short_desc => {TYPE => 'varchar(255)', NOTNULL => 1},
|
||||
# Comments are stored all together in one column for searching.
|
||||
# This allows us to examine all comments together when deciding
|
||||
# the relevance of a bug in fulltext search.
|
||||
comments => {TYPE => 'LONGTEXT'},
|
||||
comments_noprivate => {TYPE => 'LONGTEXT'},
|
||||
comments => {TYPE => 'LONGTEXT'},
|
||||
# Original Bugzilla stored each non-private comment 2 times:
|
||||
# one time in comments and one time in comments_noprivate.
|
||||
# As most of comments are non-private, this leads to fulltext
|
||||
# index being approx. 2 times larger than it must be. That's
|
||||
# not really good. So we store non-private comments in 'comments'
|
||||
# fields and private ones in 'comments_private'.
|
||||
comments_private => {TYPE => 'LONGTEXT'},
|
||||
],
|
||||
INDEXES => [
|
||||
bugs_fulltext_short_desc_idx => {FIELDS => ['short_desc'],
|
||||
TYPE => 'FULLTEXT'},
|
||||
bugs_fulltext_comments_idx => {FIELDS => ['comments'],
|
||||
TYPE => 'FULLTEXT'},
|
||||
bugs_fulltext_comments_noprivate_idx => {
|
||||
FIELDS => ['comments_noprivate'], TYPE => 'FULLTEXT'},
|
||||
bugs_fulltext_comments_private_idx => {
|
||||
FIELDS => ['comments_private'], TYPE => 'FULLTEXT'},
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
@ -72,15 +72,61 @@ sub _initialize {
|
|||
} #eosub--_initialize
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
sub _get_create_table_ddl
|
||||
{
|
||||
my ($self, $table) = @_;
|
||||
|
||||
my $thash = $self->{schema}{$table};
|
||||
die "Table $table does not exist in the database schema."
|
||||
unless ref $thash;
|
||||
|
||||
# Find fulltext fields
|
||||
my %is_ft;
|
||||
for (my $i = 1; $i < @{$thash->{INDEXES}}; $i += 2)
|
||||
{
|
||||
if ($thash->{INDEXES}->[$i]->{TYPE} eq 'FULLTEXT')
|
||||
{
|
||||
$is_ft{$_} = 1 for @{$thash->{INDEXES}->[$i]->{FIELDS}};
|
||||
}
|
||||
}
|
||||
|
||||
my $create_table = "CREATE TABLE $table \(\n";
|
||||
|
||||
my @fields = @{ $thash->{FIELDS} };
|
||||
while (@fields)
|
||||
{
|
||||
my $field = shift @fields;
|
||||
my $finfo = shift @fields;
|
||||
$create_table .= "\t$field\t";
|
||||
if ($is_ft{$field})
|
||||
{
|
||||
# Don't store the contents of fulltext fields in PostgreSQL,
|
||||
# just store the real tsvector's. This allows optimal performance
|
||||
# while ranking results.
|
||||
$create_table .= 'tsvector';
|
||||
}
|
||||
else
|
||||
{
|
||||
$create_table .= $self->get_type_ddl($finfo);
|
||||
}
|
||||
$create_table .= "," if @fields;
|
||||
$create_table .= "\n";
|
||||
}
|
||||
|
||||
$create_table .= "\)";
|
||||
|
||||
return $create_table;
|
||||
}
|
||||
|
||||
sub _get_create_index_ddl
|
||||
{
|
||||
my $self = shift;
|
||||
my ($table, $name, $index_fields, $index_type) = @_;
|
||||
if ($index_type && $index_type eq 'FULLTEXT')
|
||||
{
|
||||
$index_fields = @$index_fields > 1 ? join(" || ' ' || ", @$index_fields) : $index_fields->[0];
|
||||
my $language = Bugzilla->localconfig->{postgres_fulltext_language} || 'english';
|
||||
return "CREATE INDEX $name ON $table USING gin(to_tsvector('$language', $index_fields))";
|
||||
# Override fulltext index creation clause
|
||||
$index_fields = join(" || ", @$index_fields);
|
||||
return "CREATE INDEX $name ON $table USING gin($index_fields)";
|
||||
}
|
||||
return $self->SUPER::_get_create_index_ddl(@_);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ use Bugzilla::Series;
|
|||
use Date::Parse;
|
||||
use Date::Format;
|
||||
use IO::File;
|
||||
use Time::HiRes qw(time);
|
||||
|
||||
# NOTE: This is NOT the function for general table updates. See
|
||||
# update_table_definitions for that. This is only for the fielddefs table.
|
||||
|
@ -444,7 +445,7 @@ sub update_table_definitions {
|
|||
{TYPE => 'varchar(16)', PRIMARYKEY => 1, NOTNULL => 1});
|
||||
|
||||
_clean_control_characters_from_short_desc();
|
||||
|
||||
|
||||
# 2005-12-07 altlst@sonic.net -- Bug 225221
|
||||
$dbh->bz_add_column('longdescs', 'comment_id',
|
||||
{TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1});
|
||||
|
@ -532,7 +533,7 @@ sub update_table_definitions {
|
|||
|
||||
# 2007-08-21 wurblzap@gmail.com - Bug 365378
|
||||
_make_lang_setting_dynamic();
|
||||
|
||||
|
||||
# 2007-11-29 xiaoou.wu@oracle.com - Bug 153129
|
||||
_change_text_types();
|
||||
|
||||
|
@ -544,6 +545,16 @@ sub update_table_definitions {
|
|||
{TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0});
|
||||
|
||||
$dbh->bz_drop_index('longdescs', 'longdescs_thetext_idx');
|
||||
|
||||
# Change fulltext index type to new version (comments/comments_private)
|
||||
# We'll have to reindex anyway, so recreate the table
|
||||
if ($dbh->bz_column_info(bugs_fulltext => 'comments_noprivate'))
|
||||
{
|
||||
$dbh->bz_drop_table('bugs_fulltext');
|
||||
$dbh->bz_add_table('bugs_fulltext');
|
||||
}
|
||||
|
||||
# Populate fulltext index
|
||||
_populate_bugs_fulltext();
|
||||
|
||||
# 2008-01-18 xiaoou.wu@oracle.com - Bug 414292
|
||||
|
@ -572,7 +583,7 @@ sub update_table_definitions {
|
|||
# 2009-01-16 oreomike@gmail.com - Bug 302420
|
||||
$dbh->bz_add_column('whine_events', 'mailifnobugs',
|
||||
{ TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
|
||||
|
||||
|
||||
_convert_disallownew_to_isactive();
|
||||
|
||||
$dbh->bz_alter_column('bugs_activity', 'added',
|
||||
|
@ -3236,7 +3247,7 @@ sub _add_foreign_keys_to_multiselects {
|
|||
$dbh->bz_add_fk("bug_$name", "bug_id", {TABLE => 'bugs',
|
||||
COLUMN => 'bug_id',
|
||||
DELETE => 'CASCADE',});
|
||||
|
||||
|
||||
$dbh->bz_add_fk("bug_$name", "value", {TABLE => $name,
|
||||
COLUMN => 'value',
|
||||
DELETE => 'RESTRICT',});
|
||||
|
@ -3252,8 +3263,11 @@ sub _populate_bugs_fulltext
|
|||
my $bug_ids = shift;
|
||||
$bug_ids = undef if $bug_ids && !@$bug_ids;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $fulltext = $dbh->selectrow_array
|
||||
("SELECT 1 FROM bugs_fulltext ".$dbh->sql_limit(1));
|
||||
my $sphinx;
|
||||
my ($table, $limit1, $id) = ('bugs_fulltext', $dbh->sql_limit(1), 'bug_id');
|
||||
my $fulltext = $dbh->selectrow_array("SELECT $id FROM $table $limit1");
|
||||
my ($datasize, $time) = (0, time);
|
||||
my ($lastdata, $lasttime) = ($datasize, $time);
|
||||
# We only populate the table if it's empty or if we've been given a
|
||||
# set of bug ids.
|
||||
if ($bug_ids || !$fulltext)
|
||||
|
@ -3262,13 +3276,10 @@ sub _populate_bugs_fulltext
|
|||
$bug_ids ||= $dbh->selectcol_arrayref("SELECT bug_id FROM bugs");
|
||||
return if !$bug_ids;
|
||||
|
||||
# Bug 46221 - Russian Stemming in Bugzilla fulltext search
|
||||
# We can't use GROUP_CONCAT because we need to stem each word
|
||||
# And there could be tons of bugs, so we'll use N-bug portions
|
||||
print "Populating bugs_fulltext... (this can take a long time.)\n";
|
||||
# There could be tons of bugs, so we'll use 256-bug portions
|
||||
print "Populating full-text index... (this can take a long time.)\n";
|
||||
my ($portion, $done, $total) = (256, 0, scalar @$bug_ids);
|
||||
my ($short, $all, $nopriv, $wh, $rows);
|
||||
my ($sth, $sth_del, $sthn) = (undef, undef, 0);
|
||||
while (my @ids = splice @$bug_ids, 0, $portion)
|
||||
{
|
||||
$rows = {};
|
||||
|
@ -3280,35 +3291,43 @@ sub _populate_bugs_fulltext
|
|||
"SELECT bug_id, thetext, isprivate FROM longdescs WHERE $wh",
|
||||
undef, @ids
|
||||
);
|
||||
$rows->{$_->[0]} = [ $_->[1], '', '' ] for @$short;
|
||||
for (@$all)
|
||||
# Local block with 'use bytes' for counting data size in MB
|
||||
{
|
||||
$rows->{$_->[0]}->[1] .= $_->[1] . "\n";
|
||||
$rows->{$_->[0]}->[2] .= $_->[1] . "\n"
|
||||
unless $_->[2];
|
||||
}
|
||||
if (!$dbh->isa('Bugzilla::DB::Pg'))
|
||||
{
|
||||
for (keys %$rows)
|
||||
use bytes;
|
||||
for (@$short)
|
||||
{
|
||||
$_ = stem_text($_) for @{$rows->{$_}};
|
||||
$rows->{$_->[0]} = [ $_->[1], '', '' ];
|
||||
$datasize += length $_->[1];
|
||||
}
|
||||
# Comments divide into non-private and private
|
||||
for (@$all)
|
||||
{
|
||||
$rows->{$_->[0]}->[$_->[2] ? 2 : 1] .= $_->[1] . "\n";
|
||||
$datasize += length($_->[1])+1;
|
||||
}
|
||||
}
|
||||
if ($sthn != @ids)
|
||||
for (keys %$rows)
|
||||
{
|
||||
# Optimization: cache prepared statements
|
||||
$sthn = @ids;
|
||||
$sth_del = $dbh->prepare("DELETE FROM bugs_fulltext WHERE bug_id IN (".join(",", ("?") x $sthn).")");
|
||||
$sth = $dbh->prepare(
|
||||
"INSERT INTO bugs_fulltext (bug_id, short_desc, comments, comments_noprivate)" .
|
||||
" VALUES " . join(",", ("(?,?,?,?)") x $sthn)
|
||||
);
|
||||
Encode::_utf8_off($_) for @{$rows->{$_}};
|
||||
}
|
||||
$sth_del->execute(@ids);
|
||||
$sth->execute(map { ($_, @{$rows->{$_}}) } @ids);
|
||||
for (keys %$rows)
|
||||
{
|
||||
# CustIS Bug 46221 - Snowball stemmers in Bugzilla fulltext search
|
||||
$rows->{$_} = join ', ', map { $dbh->quote_fulltext($_) } @{$rows->{$_}};
|
||||
}
|
||||
$dbh->do("DELETE FROM $table WHERE $id IN (".join(',', @ids).')');
|
||||
$dbh->do(
|
||||
"INSERT INTO $table ($id, short_desc, comments, comments_private) VALUES ".
|
||||
join(", ", map { "($_, $rows->{$_})" } @ids)
|
||||
);
|
||||
$done += @ids;
|
||||
print "\r$done / $total ...";
|
||||
print "\r$done / $total, ".sprintf("%.2f MB, %d KB/s", $datasize/1048576, ($datasize-$lastdata)/1024/(time-$lasttime));
|
||||
print " ...";
|
||||
($lastdata, $lasttime) = ($datasize, time);
|
||||
}
|
||||
$time = time-$time;
|
||||
print sprintf("\nTime: %02d min %02d sec, %d KB/s",
|
||||
int($time/60), $time % 60, $datasize/1024/$time);
|
||||
print "\n";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -214,14 +214,6 @@ EOT
|
|||
# such as bug changes. A random string is generated by default.
|
||||
# It's very important that this key is kept secret. It also must be
|
||||
# very long.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'postgres_fulltext_language',
|
||||
default => 'english',
|
||||
desc => <<EOT
|
||||
# Value of this variable is used as the language for full-text search
|
||||
# morphology, only for PostgreSQL database.
|
||||
EOT
|
||||
},
|
||||
);
|
||||
|
|
|
@ -294,6 +294,14 @@ sub OPTIONAL_MODULES {
|
|||
version => '0.05',
|
||||
feature => ['rand_security'],
|
||||
},
|
||||
|
||||
# Snowball stemmers in full-text search
|
||||
{
|
||||
package => 'Lingua-Stem-Snowball',
|
||||
module => 'Lingua::Stem::Snowball',
|
||||
version => 0,
|
||||
feature => ['fulltext_stem'],
|
||||
},
|
||||
);
|
||||
|
||||
my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
|
||||
|
|
|
@ -2000,20 +2000,21 @@ sub _content_matches
|
|||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $table = "bugs_fulltext_".$self->{sequence};
|
||||
my $comments_col = "comments";
|
||||
$comments_col = "comments_noprivate" unless $self->{user}->is_insider;
|
||||
|
||||
# Create search terms to add to the SELECT and WHERE clauses.
|
||||
my $text = stem_text($self->{value});
|
||||
my ($term1, $rterm1) = $dbh->sql_fulltext_search("bugs_fulltext.$comments_col", $text);
|
||||
my ($term2, $rterm2) = $dbh->sql_fulltext_search("bugs_fulltext.short_desc", $text);
|
||||
# These are (search term, rank term, search term, rank term, ...)
|
||||
my $text = $self->{value};
|
||||
my @terms = (
|
||||
$dbh->sql_fulltext_search("bugs_fulltext.short_desc", $text),
|
||||
$dbh->sql_fulltext_search("bugs_fulltext.comments", $text),
|
||||
);
|
||||
push @terms, $dbh->sql_fulltext_search("bugs_fulltext.comments_private", $text) if $self->{user}->is_insider;
|
||||
|
||||
# Bug 46221 - Russian Stemming in Bugzilla fulltext search
|
||||
# MATCH(...) OR MATCH(...) is very slow in MySQL (and probably in other DBs):
|
||||
# -- it does no fulltext index merge optimization. So use JOIN to UNION.
|
||||
$self->{term} = {
|
||||
table => "(SELECT bug_id FROM bugs_fulltext WHERE $term1 UNION ".
|
||||
"SELECT bug_id FROM bugs_fulltext WHERE $term2) $table",
|
||||
table => "(".join(" UNION ", map { "SELECT bug_id FROM bugs_fulltext WHERE $terms[$_]" } grep { !($_&1) } 0..$#terms).") $table",
|
||||
bugid_field => "$table.bug_id",
|
||||
};
|
||||
|
||||
|
@ -2025,7 +2026,7 @@ sub _content_matches
|
|||
# this adds more terms to the relevance sql.
|
||||
if (!$self->{negated})
|
||||
{
|
||||
push @{COLUMNS->{relevance}->{bits}}, $rterm1, $rterm2;
|
||||
push @{COLUMNS->{relevance}->{bits}}, @terms[grep { $_&1 } 0..$#terms];
|
||||
COLUMNS->{relevance}->{name} = "(SELECT ".join("+", @{COLUMNS->{relevance}->{bits}}).
|
||||
" FROM bugs_fulltext WHERE bugs_fulltext.bug_id=bugs.bug_id)";
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ use Text::Wrap;
|
|||
use Text::TabularDisplay::Utf8;
|
||||
use JSON;
|
||||
|
||||
use Lingua::Stem::RuUTF8;
|
||||
eval { require 'Lingua/Stem/Snowball.pm' };
|
||||
|
||||
sub trick_taint
|
||||
{
|
||||
|
@ -733,30 +733,34 @@ sub disable_utf8 {
|
|||
}
|
||||
}
|
||||
|
||||
# Bug 46221 - Russian Stemming in Bugzilla fulltext search
|
||||
# CustIS Bug 46221 - Snowball Stemmers in MySQL fulltext search
|
||||
my $snowballs = {};
|
||||
sub stem_text
|
||||
{
|
||||
my ($text, $allow_verbatim) = @_;
|
||||
my ($text, $lang, $allow_verbatim) = @_;
|
||||
return '' if !defined $text || $text =~ /^\s*$/so;
|
||||
return $text if !$INC{'Lingua/Stem/Snowball.pm'};
|
||||
$lang ||= 'en';
|
||||
Encode::_utf8_on($text) if Bugzilla->params->{utf8};
|
||||
$text = [ split /(?<=\w)(?=\W)|(?<=\W)(?=\w)/, $text ];
|
||||
# CustIS Bug 66033 - _ is wanted to also be a delimiter
|
||||
$text = [ split /(\PL+)/, $text ];
|
||||
my $word = 1;
|
||||
if ($text->[0] eq '')
|
||||
{
|
||||
$word = 0;
|
||||
shift @$text;
|
||||
}
|
||||
my $q = 0;
|
||||
my $cache = (Bugzilla->request_cache->{stem_cache} ||= {});
|
||||
%$cache = () if keys(%$cache) > 65536;
|
||||
my $stem = ($snowballs->{$lang} ||= Lingua::Stem::Snowball->new(lang => $lang, encoding => 'UTF-8'));
|
||||
my $r = '';
|
||||
for (@$text)
|
||||
{
|
||||
unless (/\W/)
|
||||
if ($word)
|
||||
{
|
||||
# $q = 1 means verbatim
|
||||
unless ($q)
|
||||
{
|
||||
if (/_/)
|
||||
{
|
||||
# CustIS Bug 66033
|
||||
$_ = join ' ', map { Lingua::Stem::RuUTF8::stem_word($_) } ($_, split(/_/, $_));
|
||||
}
|
||||
else
|
||||
{
|
||||
$_ = Lingua::Stem::RuUTF8::stem_word($_);
|
||||
}
|
||||
}
|
||||
# $q = 1 means we're inside quotes
|
||||
$r .= ($cache->{$_} ||= $stem->stem($_)) unless $q;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -765,13 +769,12 @@ sub stem_text
|
|||
# If $allow_verbatim is TRUE then text in "double quotes" doesn't stem
|
||||
$q = ($q + tr/\"/\"/) % 2;
|
||||
}
|
||||
if (!/\s$/so)
|
||||
{
|
||||
$_ .= ' ';
|
||||
}
|
||||
$r .= $_;
|
||||
$r .= ' ' if !/\s$/o;
|
||||
}
|
||||
$word = !$word;
|
||||
}
|
||||
return join '', @$text;
|
||||
return $r;
|
||||
}
|
||||
|
||||
sub intersect
|
||||
|
|
|
@ -1,252 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
# Lingua::Stem::Ru - UTF-8 стеммер Портера
|
||||
|
||||
package Lingua::Stem::RuUTF8;
|
||||
|
||||
use utf8;
|
||||
use strict;
|
||||
use Exporter;
|
||||
use Carp;
|
||||
use vars qw (@ISA @EXPORT_OK @EXPORT %EXPORT_TAGS $VERSION);
|
||||
|
||||
BEGIN {
|
||||
@ISA = qw (Exporter);
|
||||
@EXPORT = ();
|
||||
@EXPORT_OK = qw (stem stem_word clear_stem_cache stem_caching);
|
||||
%EXPORT_TAGS = ();
|
||||
}
|
||||
$VERSION = "0.01";
|
||||
|
||||
my $Stem_Caching = 0;
|
||||
my $Stem_Cache = {};
|
||||
|
||||
my $VOWEL = qr/аеиоуыэюя/;
|
||||
my $PERFECTIVEGROUND = qr/((ив|ивши|ившись|ыв|ывши|ывшись)|((?<=[ая])(в|вши|вшись)))$/;
|
||||
my $REFLEXIVE = qr/(с[яь])$/;
|
||||
my $ADJECTIVE = qr/(ее|ие|ые|ое|ими|ыми|ей|ий|ый|ой|ем|им|ым|ом|его|ого|ему|ому|их|ых|ую|юю|ая|яя|ою|ею)$/;
|
||||
my $PARTICIPLE = qr/((ивш|ывш|ующ)|((?<=[ая])(ем|нн|вш|ющ|щ)))$/;
|
||||
my $VERB = qr/((ила|ыла|ена|ейте|уйте|ите|или|ыли|ей|уй|ил|ыл|им|ым|ен|ило|ыло|ено|ят|ует|уют|ит|ыт|ены|ить|ыть|ишь|ую|ю)|((?<=[ая])(ла|на|ете|йте|ли|й|л|ем|н|ло|но|ет|ют|ны|ть|ешь|нно)))$/;
|
||||
my $NOUN = qr/(а|ев|ов|ие|ье|е|иями|ями|ами|еи|ии|и|ией|ей|ой|ий|й|иям|ям|ием|ем|ам|ом|о|у|ах|иях|ях|ы|ь|ию|ью|ю|ия|ья|я)$/;
|
||||
my $RVRE = qr/^(.*?[$VOWEL])(.*)$/;
|
||||
my $DERIVATIONAL = qr/[^$VOWEL][$VOWEL]+[^$VOWEL]+[$VOWEL].*(?<=о)сть?$/;
|
||||
|
||||
sub stem {
|
||||
return [] if ($#_ == -1);
|
||||
my $parm_ref;
|
||||
if (ref $_[0]) {
|
||||
$parm_ref = shift;
|
||||
} else {
|
||||
$parm_ref = { @_ };
|
||||
}
|
||||
|
||||
my $words = [];
|
||||
my $locale = 'ru';
|
||||
my $exceptions = {};
|
||||
foreach (keys %$parm_ref) {
|
||||
my $key = lc ($_);
|
||||
if ($key eq '-words') {
|
||||
@$words = @{$parm_ref->{$key}};
|
||||
} elsif ($key eq '-exceptions') {
|
||||
$exceptions = $parm_ref->{$key};
|
||||
} elsif ($key eq '-locale') {
|
||||
$locale = $parm_ref->{$key};
|
||||
} else {
|
||||
croak (__PACKAGE__ . "::stem() - Unknown parameter '$key' with value '$parm_ref->{$key}'\n");
|
||||
}
|
||||
}
|
||||
|
||||
local( $_ );
|
||||
foreach (@$words) {
|
||||
# Flatten case
|
||||
$_ = lc $_;
|
||||
|
||||
# Check against exceptions list
|
||||
if (exists $exceptions->{$_}) {
|
||||
$_ = $exceptions->{$_};
|
||||
next;
|
||||
}
|
||||
|
||||
# Check against cache of stemmed words
|
||||
my $original_word = $_;
|
||||
if ($Stem_Caching && exists $Stem_Cache->{$original_word}) {
|
||||
$_ = $Stem_Cache->{$original_word};
|
||||
next;
|
||||
}
|
||||
|
||||
$_ = stem_word($_);
|
||||
|
||||
$Stem_Cache->{$original_word} = $_ if $Stem_Caching;
|
||||
}
|
||||
$Stem_Cache = {} if ($Stem_Caching < 2);
|
||||
|
||||
return $words;
|
||||
}
|
||||
|
||||
sub stem_word {
|
||||
my $word = lc shift;
|
||||
|
||||
# Check against cache of stemmed words
|
||||
if ($Stem_Caching && exists $Stem_Cache->{$word}) {
|
||||
return $Stem_Cache->{$word};
|
||||
}
|
||||
|
||||
my ($start, $RV) = $word =~ /$RVRE/;
|
||||
return $word unless $RV;
|
||||
|
||||
# Step 1
|
||||
unless ($RV =~ s/$PERFECTIVEGROUND//) {
|
||||
$RV =~ s/$REFLEXIVE//;
|
||||
|
||||
if ($RV =~ s/$ADJECTIVE//) {
|
||||
$RV =~ s/$PARTICIPLE//;
|
||||
} else {
|
||||
$RV =~ s/$NOUN// unless $RV =~ s/$VERB//;
|
||||
}
|
||||
}
|
||||
|
||||
# Step 2
|
||||
$RV =~ s/и$//;
|
||||
|
||||
# Step 3
|
||||
$RV =~ s/ость?$// if $RV =~ /$DERIVATIONAL/;
|
||||
|
||||
# Step 4
|
||||
unless ($RV =~ s/ь$//) {
|
||||
$RV =~ s/ейше?//;
|
||||
$RV =~ s/нн$/н/;
|
||||
}
|
||||
|
||||
return $start.$RV;
|
||||
}
|
||||
|
||||
sub stem_caching {
|
||||
my $parm_ref;
|
||||
if (ref $_[0]) {
|
||||
$parm_ref = shift;
|
||||
} else {
|
||||
$parm_ref = { @_ };
|
||||
}
|
||||
my $caching_level = $parm_ref->{-level};
|
||||
if (defined $caching_level) {
|
||||
if ($caching_level !~ m/^[012]$/) {
|
||||
croak(__PACKAGE__ . "::stem_caching() - Legal values are '0','1' or '2'. '$caching_level' is not a legal value");
|
||||
}
|
||||
$Stem_Caching = $caching_level;
|
||||
}
|
||||
return $Stem_Caching;
|
||||
}
|
||||
|
||||
sub clear_stem_cache {
|
||||
$Stem_Cache = {};
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Lingua::Stem::RuUTF8 - Porter's stemming algorithm for Russian (UTF-8 only)
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Lingua::Stem::RuUTF8;
|
||||
my $stems = Lingua::Stem::RuUTF8::stem({
|
||||
-words => $word_list_reference,
|
||||
-locale => 'ru',
|
||||
-exceptions => $exceptions_hash,
|
||||
});
|
||||
|
||||
my $stem = Lingua::Stem::RuUTF8::stem_word( $word );
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module applies the Porter Stemming Algorithm to its parameters,
|
||||
returning the stemmed words.
|
||||
|
||||
The algorithm is implemented exactly as described in:
|
||||
|
||||
http://snowball.tartarus.org/russian/stemmer.html
|
||||
|
||||
The code is carefully crafted to work in conjunction with the L<Lingua::Stem>
|
||||
module by Benjamin Franz. This stemmer is also based
|
||||
on the work of Aldo Capini, see L<Lingua::Stem::It>.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over 4
|
||||
|
||||
=item stem({ -words => \@words, -locale => 'ru', -exceptions => \%exceptions });
|
||||
|
||||
Stems a list of passed words. Returns an anonymous list reference to the stemmed
|
||||
words.
|
||||
|
||||
Example:
|
||||
|
||||
my $stemmed_words = Lingua::Stem::RuUTF8::stem({
|
||||
-words => \@words,
|
||||
-locale => 'ru',
|
||||
-exceptions => \%exceptions,
|
||||
});
|
||||
|
||||
=item stem_word( $word );
|
||||
|
||||
Stems a single word and returns the stem directly.
|
||||
|
||||
Example:
|
||||
|
||||
my $stem = Lingua::Stem::RuUTF8::stem_word( $word );
|
||||
|
||||
=item stem_caching({ -level => 0|1|2 });
|
||||
|
||||
Sets the level of stem caching.
|
||||
|
||||
'0' means 'no caching'. This is the default level.
|
||||
|
||||
'1' means 'cache per run'. This caches stemming results during a single
|
||||
call to 'stem'.
|
||||
|
||||
'2' means 'cache indefinitely'. This caches stemming results until
|
||||
either the process exits or the 'clear_stem_cache' method is called.
|
||||
|
||||
=item clear_stem_cache;
|
||||
|
||||
Clears the cache of stemmed words
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
=head2 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 HISTORY
|
||||
|
||||
=over 8
|
||||
|
||||
=item *
|
||||
|
||||
0.01 (2004-05-21)
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Aleksandr Guidrevitch <pillgrim@mail.ru>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
Lingua::Stem
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (C) 2003 by Aldo Calpini <dada@perl.it>
|
||||
|
||||
Copyright (C) 2004 by Aleksandr Guidrevitch <pillgrim@mail.ru>
|
||||
|
||||
This software may be freely copied and distributed under the same
|
||||
terms and conditions as Perl itself, either Perl version 5.8.3
|
||||
or, at your option, any later version of Perl 5 you may
|
||||
have available..
|
||||
|
||||
=cut
|
|
@ -1,2 +1,2 @@
|
|||
#!/bin/sh
|
||||
perl checksetup.pl --no-chmod --no-templates
|
||||
LD_PRELOAD=/usr/lib/i386-linux-gnu/libstdc++.so.6:/lib/libuuid.so.1 perl checksetup.pl --no-chmod --no-templates
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Canonical Ltd.
|
||||
# Portions created by Canonical Ltd. are Copyright (C) 2008
|
||||
# Canonical Ltd. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
my $modules = Bugzilla->hook_args->{modules};
|
||||
if (exists $modules->{Example}) {
|
||||
$modules->{Example} = 'extensions/example/lib/AuthLogin.pm';
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Canonical Ltd.
|
||||
# Portions created by Canonical Ltd. are Copyright (C) 2008
|
||||
# Canonical Ltd. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
my $modules = Bugzilla->hook_args->{modules};
|
||||
if (exists $modules->{Example}) {
|
||||
$modules->{Example} = 'extensions/example/lib/AuthVerify.pm';
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Canonical Ltd.
|
||||
# Portions created by Canonical Ltd. are Copyright (C) 2008
|
||||
# Canonical Ltd. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Elliotte Martin <elliotte_martin@yahoo.com>
|
||||
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
|
||||
my $columns = Bugzilla->hook_args->{'columns'};
|
||||
push (@$columns, "delta_ts AS example")
|
|
@ -0,0 +1,35 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is ITA Software
|
||||
# Portions created by the Initial Developer are Copyright (C) 2009
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
|
||||
# This code doesn't actually *do* anything, it's just here to show you
|
||||
# how to use this hook.
|
||||
my $args = Bugzilla->hook_args;
|
||||
my $bug = $args->{'bug'};
|
||||
my $timestamp = $args->{'timestamp'};
|
||||
|
||||
my $bug_id = $bug->id;
|
||||
# Uncomment this line to see a line in your webserver's error log whenever
|
||||
# you file a bug.
|
||||
# warn "Bug $bug_id has been filed!";
|
|
@ -0,0 +1,56 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Everything Solved, Inc.
|
||||
# Portions created by Everything Solved are Copyright (C) 2008
|
||||
# Everything Solved, Inc. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
use Bugzilla::Status;
|
||||
|
||||
# This code doesn't actually *do* anything, it's just here to show you
|
||||
# how to use this hook.
|
||||
my $args = Bugzilla->hook_args;
|
||||
my $bug = $args->{'bug'};
|
||||
my $timestamp = $args->{'timestamp'};
|
||||
my $changes = $args->{'changes'};
|
||||
|
||||
foreach my $field (keys %$changes) {
|
||||
my $used_to_be = $changes->{$field}->[0];
|
||||
my $now_it_is = $changes->{$field}->[1];
|
||||
}
|
||||
|
||||
my $status_message;
|
||||
if (my $status_change = $changes->{'bug_status'}) {
|
||||
my $old_status = new Bugzilla::Status({ name => $status_change->[0] });
|
||||
my $new_status = new Bugzilla::Status({ name => $status_change->[1] });
|
||||
if ($new_status->is_open && !$old_status->is_open) {
|
||||
$status_message = "Bug re-opened!";
|
||||
}
|
||||
if (!$new_status->is_open && $old_status->is_open) {
|
||||
$status_message = "Bug closed!";
|
||||
}
|
||||
}
|
||||
|
||||
my $bug_id = $bug->id;
|
||||
my $num_changes = scalar keys %$changes;
|
||||
my $result = "There were $num_changes changes to fields on bug $bug_id"
|
||||
. " at $timestamp.";
|
||||
# Uncomment this line to see $result in your webserver's error log whenever
|
||||
# you update a bug.
|
||||
# warn $result;
|
|
@ -0,0 +1,27 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Canonical Ltd.
|
||||
# Portions created by Canonical Ltd. are Copyright (C) 2008
|
||||
# Canonical Ltd. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Elliotte Martin <elliotte_martin@yahoo.com>
|
||||
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
|
||||
my $fields = Bugzilla->hook_args->{'fields'};
|
||||
push (@$fields, "example")
|
|
@ -0,0 +1,26 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
## The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Canonical Ltd.
|
||||
# Portions created by Canonical Ltd. are Copyright (C) 2008
|
||||
# Canonical Ltd. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Elliotte Martin <elliotte_martin@yahoo.com>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
|
||||
my $columns = Bugzilla->hook_args->{'columns'};
|
||||
$columns->{'example'} = { 'name' => 'bugs.delta_ts' , 'title' => 'Example' };
|
|
@ -0,0 +1,27 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Canonical Ltd.
|
||||
# Portions created by Canonical Ltd. are Copyright (C) 2008
|
||||
# Canonical Ltd. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Elliotte Martin <elliotte_martin@yahoo.com>
|
||||
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
|
||||
my $columns = Bugzilla->hook_args->{'columns'};
|
||||
push (@$columns, "example")
|
|
@ -0,0 +1,25 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Canonical Ltd.
|
||||
# Portions created by Canonical Ltd. are Copyright (C) 2008
|
||||
# Canonical Ltd. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
my $modules = Bugzilla->hook_args->{panel_modules};
|
||||
$modules->{Example} = "extensions::example::lib::ConfigExample";
|
|
@ -0,0 +1,32 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Canonical Ltd.
|
||||
# Portions created by Canonical Ltd. are Copyright (C) 2008
|
||||
# Canonical Ltd. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
my $panels = Bugzilla->hook_args->{panels};
|
||||
|
||||
# Add the "Example" auth methods.
|
||||
my $auth_params = $panels->{'auth'}->{params};
|
||||
my ($info_class) = grep($_->{name} eq 'user_info_class', @$auth_params);
|
||||
my ($verify_class) = grep($_->{name} eq 'user_verify_class', @$auth_params);
|
||||
|
||||
push(@{ $info_class->{choices} }, 'CGI,Example');
|
||||
push(@{ $verify_class->{choices} }, 'Example');
|
|
@ -0,0 +1,26 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Canonical Ltd.
|
||||
# Portions created by Canonical Ltd. are Copyright (C) 2008
|
||||
# Canonical Ltd. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
my $config = Bugzilla->hook_args->{config};
|
||||
$config->{Example} = "extensions::example::lib::ConfigExample";
|
|
@ -0,0 +1,42 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Everything Solved, Inc.
|
||||
# Portions created by Everything Solved are Copyright (C) 2008
|
||||
# Everything Solved, Inc. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
use Bugzilla::Util qw(diff_arrays);
|
||||
|
||||
# This code doesn't actually *do* anything, it's just here to show you
|
||||
# how to use this hook.
|
||||
my $args = Bugzilla->hook_args;
|
||||
my ($bug, $timestamp, $old_flags, $new_flags) =
|
||||
@$args{qw(bug timestamp old_flags new_flags)};
|
||||
my ($removed, $added) = diff_arrays($old_flags, $new_flags);
|
||||
my ($granted, $denied) = (0, 0);
|
||||
foreach my $new_flag (@$added) {
|
||||
$granted++ if $new_flag =~ /\+$/;
|
||||
$denied++ if $new_flag =~ /-$/;
|
||||
}
|
||||
my $bug_id = $bug->id;
|
||||
my $result = "$granted flags were granted and $denied flags were denied"
|
||||
. " on bug $bug_id at $timestamp.";
|
||||
# Uncomment this line to see $result in your webserver's error log whenever
|
||||
# you update flags.
|
||||
# warn $result;
|
|
@ -0,0 +1,28 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Canonical Ltd.
|
||||
# Portions created by Canonical Ltd. are Copyright (C) 2008
|
||||
# Canonical Ltd. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Elliotte Martin <elliotte_martin@yahoo.com>
|
||||
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
|
||||
my $silent = Bugzilla->hook_args->{'silent'};
|
||||
|
||||
print "Install-before_final_checks hook\n" unless $silent;
|
|
@ -0,0 +1,28 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Everything Solved, Inc.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2008
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
my $email = Bugzilla->hook_args->{email};
|
||||
# If you add a header to an email, it's best to start it with
|
||||
# 'X-Bugzilla-<Extension>' so that you don't conflict with
|
||||
# other extensions.
|
||||
$email->header_set('X-Bugzilla-Example-Header', 'Example');
|
|
@ -0,0 +1,33 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Example Plugin.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Canonical Ltd.
|
||||
# Portions created by Canonical Ltd. are Copyright (C) 2009
|
||||
# Canonical Ltd. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
|
||||
my %args = %{ Bugzilla->hook_args };
|
||||
my ($vars, $page) = @args{qw(vars page_id)};
|
||||
|
||||
# You can see this hook in action by loading page.cgi?id=example.html
|
||||
if ($page eq 'example.html') {
|
||||
$vars->{cgi_variables} = { Bugzilla->cgi->Vars };
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/perl -w
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Testopia System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Greg Hendricks.
|
||||
# Portions created by Greg Hendricks are Copyright (C) 2008
|
||||
# Novell. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Greg Hendricks <ghendricks@novell.com>
|
||||
|
||||
use strict;
|
||||
|
||||
my $vars = Bugzilla->hook_args->{vars};
|
||||
|
||||
$vars->{'example'} = 1
|
|
@ -0,0 +1,25 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Everything Solved, Inc.
|
||||
# Portions created by Everything Solved, Inc. are Copyright (C) 2008
|
||||
# Everything Solved, Inc. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
my $error_map = Bugzilla->hook_args->{error_map};
|
||||
$error_map->{'example_my_error'} = 10001;
|
|
@ -0,0 +1,25 @@
|
|||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Everything Solved, Inc.
|
||||
# Portions created by Everything Solved, Inc. are Copyright (C) 2007
|
||||
# Everything Solved, Inc. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Bugzilla;
|
||||
my $dispatch = Bugzilla->hook_args->{dispatch};
|
||||
$dispatch->{Example} = "extensions::example::lib::WSExample";
|
|
@ -320,7 +320,7 @@ sub install_update_db
|
|||
$dbh->do('INSERT INTO setting_value (name, value, sortindex) VALUES (\'silent_affects_flags\', \'send\', 10), (\'silent_affects_flags\', \'do_not_send\', 20)');
|
||||
}
|
||||
|
||||
# Специальные группы
|
||||
# New system groups
|
||||
my $special_groups = [
|
||||
[ 'bz_editcheckers', 'Users who can edit Bugzilla Correctness Checkers', [ 'admin' ] ],
|
||||
[ 'editfields', 'Users who can edit Bugzilla field parameters', [ 'admin' ] ],
|
||||
|
|
|
@ -11,7 +11,7 @@ my ($xmlrpc, $jsonrpc, $config) = get_rpc_clients();
|
|||
|
||||
use constant INVALID_BUG_ID => -1;
|
||||
use constant INVALID_BUG_ALIAS => 'aaaaaaa12345';
|
||||
use constant PRIVATE_BUG => 40933;
|
||||
use constant PRIVATE_BUG => 40934;
|
||||
use constant PUBLIC_BUG => 58878;
|
||||
|
||||
use constant TEST_COMMENT => '--- Test Comment From QA Tests ---';
|
||||
|
|
|
@ -24,35 +24,42 @@
|
|||
%]
|
||||
|
||||
[% param_descs = {
|
||||
quip_list_entry_control => "Controls how easily users can add entries to the quip list.
|
||||
<ul>
|
||||
<li>
|
||||
open - Users may freely add to the quip list, and
|
||||
their entries will immediately be available for viewing.
|
||||
</li>
|
||||
<li>
|
||||
moderated - quips can be entered, but need to be approved
|
||||
by an admin before they will be shown.
|
||||
</li>
|
||||
<li>
|
||||
closed - no new additions to the quips list are allowed.
|
||||
</li>
|
||||
</ul>",
|
||||
quip_list_entry_control =>
|
||||
"Controls how easily users can add entries to the quip list.
|
||||
<ul>
|
||||
<li>
|
||||
open - Users may freely add to the quip list, and
|
||||
their entries will immediately be available for viewing.
|
||||
</li>
|
||||
<li>
|
||||
moderated - quips can be entered, but need to be approved
|
||||
by an admin before they will be shown.
|
||||
</li>
|
||||
<li>
|
||||
closed - no new additions to the quips list are allowed.
|
||||
</li>
|
||||
</ul>",
|
||||
|
||||
mostfreqthreshold => "The minimum number of duplicates $terms.abug needs to show up on the " _
|
||||
"<a href=\"duplicates.cgi\">most frequently reported $terms.bugs page</a>. " _
|
||||
"If you have a large database and this page takes a long time to " _
|
||||
"load, try increasing this number.",
|
||||
mostfreqthreshold =>
|
||||
"The minimum number of duplicates $terms.abug needs to show up on the " _
|
||||
"<a href=\"duplicates.cgi\">most frequently reported $terms.bugs page</a>. " _
|
||||
"If you have a large database and this page takes a long time to " _
|
||||
"load, try increasing this number.",
|
||||
|
||||
mybugstemplate => "This is the URL to use to bring up a simple 'all of my $terms.bugs' " _
|
||||
"list for a user. %userid% will get replaced with the login name of a user.",
|
||||
mybugstemplate =>
|
||||
"This is the URL to use to bring up a simple 'all of my $terms.bugs' " _
|
||||
"list for a user. %userid% will get replaced with the login name of a user.",
|
||||
|
||||
defaultquery => "This is the default query that initially comes up when you " _
|
||||
"access the advanced query page. It's in URL parameter " _
|
||||
"format, which makes it hard to read. Sorry!",
|
||||
defaultquery =>
|
||||
"This is the default query that initially comes up when you " _
|
||||
"access the advanced query page. It's in URL parameter " _
|
||||
"format, which makes it hard to read. Sorry!",
|
||||
|
||||
specific_search_allow_empty_words =>
|
||||
specific_search_allow_empty_words =>
|
||||
"Whether to allow a search on the 'Simple Search' page with an empty"
|
||||
_ " 'Words' field.",
|
||||
|
||||
stem_language => "Language for stemming words in full-text search, 2-letter code" _
|
||||
" (one of: da, de, en, es, fi, fr, hu, it, nl, no, pt, ro, ru, sv, tr)",
|
||||
|
||||
} %]
|
||||
|
|
|
@ -66,6 +66,7 @@ END
|
|||
feature_smtp_auth => 'SMTP Authentication',
|
||||
feature_updates => 'Automatic Update Notifications',
|
||||
feature_xmlrpc => 'XML-RPC Interface',
|
||||
feature_fulltext_stem => 'Snowball stemmers in full-text search',
|
||||
|
||||
header => "* This is Bugzilla ##bz_ver## on perl ##perl_ver##\n"
|
||||
. "* Running on ##os_name## ##os_ver##",
|
||||
|
|
Loading…
Reference in New Issue