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-67ecbb4d7f56
master
vfilippov 2011-08-23 15:27:12 +00:00
parent 3ed6d7df0e
commit 710e7747d2
38 changed files with 829 additions and 389 deletions

View File

@ -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.

View File

@ -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;
}

View File

@ -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

View File

@ -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
#####################################################################

View File

@ -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)/)

View File

@ -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;

View File

@ -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'},
],
},

View File

@ -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(@_);
}

View File

@ -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";
}
}

View File

@ -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
},
);

View File

@ -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');

View File

@ -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)";
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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';
}

View File

@ -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';
}

View File

@ -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")

View File

@ -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!";

View File

@ -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;

View File

@ -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")

View File

@ -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' };

View File

@ -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")

View File

@ -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";

View File

@ -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');

View File

@ -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";

View File

@ -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;

View File

@ -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;

View File

@ -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');

View File

@ -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 };
}

View File

@ -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

View File

@ -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;

View File

@ -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";

View File

View File

@ -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' ] ],

View File

@ -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 ---';

View File

@ -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)",
} %]

View File

@ -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##",