diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt
index 1f745128b..54584bbc3 100644
--- a/docs/changelog/7.x.x.txt
+++ b/docs/changelog/7.x.x.txt
@@ -5,6 +5,8 @@
- fixed #10011: Wrong spelling of "Descendant" in Navigation Asset Help function (Rob Schipper/NFIA India)
- rfe #9098: Thingy Thing-copy function (SDH Consulting Group)
- rfe #9099: Thingy field-copy function (SDH Consulting Group)
+ - Keywords are now comma separated rather than space separated.
+ - added Keywords form control with autocomplete
7.7.0
- fixed #9913: New Content Side Bar missing in Asset window
diff --git a/docs/gotcha.txt b/docs/gotcha.txt
index 4e0e2a486..a624bbf7b 100644
--- a/docs/gotcha.txt
+++ b/docs/gotcha.txt
@@ -7,6 +7,14 @@ upgrading from one version to the next, or even between multiple
versions. Be sure to heed the warnings contained herein as they will
save you many hours of grief.
+7.7.1
+--------------------------------------------------------------------
+ * Keywords are now comma separated rather than space separated. Quotes are
+ no longer treated specially and will become part of keywords. Keywords
+ cannot contain commas. Keyword searching is still case insensitive, but
+ keyword will preserve the case of what is entered.
+
+
7.7.0
--------------------------------------------------------------------
* WebGUI now requires Params::Validate version 0.81 or greater.
diff --git a/lib/WebGUI/Asset.pm b/lib/WebGUI/Asset.pm
index 76729aec2..8cd70a616 100644
--- a/lib/WebGUI/Asset.pm
+++ b/lib/WebGUI/Asset.pm
@@ -930,7 +930,7 @@ sub getEditForm {
label => $i18n->get('keywords'),
hoverHelp => $i18n->get('keywords help'),
value => $self->get('keywords'),
- fieldType => 'text',
+ fieldType => 'keywords',
tab => 'meta',
}
);
diff --git a/lib/WebGUI/AssetExportHtml.pm b/lib/WebGUI/AssetExportHtml.pm
index 9ded76b92..6be9e950c 100644
--- a/lib/WebGUI/AssetExportHtml.pm
+++ b/lib/WebGUI/AssetExportHtml.pm
@@ -317,8 +317,6 @@ sub exportAsHtml {
# tell the user which asset we're exporting.
unless ($quiet) {
my $message = sprintf $i18n->get('exporting page'), $fullPath;
- $exportSession->var->end;
- $exportSession->close;
$self->session->output->print($message);
}
@@ -541,7 +539,7 @@ sub exportGetUrlAsPath {
my $fileTypes = $config->get('exportBinaryExtensions');
# get the asset's URL as a URI::URL object for easy parsing of components
- my $url = URI::URL->new($config->get("sitename")->[0] . $self->getUrl);
+ my $url = URI::URL->new($self->session->url->getSiteURL . $self->getUrl);
my @pathComponents = $url->path_components;
shift @pathComponents; # first item is the empty string
my $filename = pop @pathComponents;
diff --git a/lib/WebGUI/Form/Keywords.pm b/lib/WebGUI/Form/Keywords.pm
new file mode 100644
index 000000000..01d8692b0
--- /dev/null
+++ b/lib/WebGUI/Form/Keywords.pm
@@ -0,0 +1,173 @@
+package WebGUI::Form::Keywords;
+
+=head1 LEGAL
+
+ -------------------------------------------------------------------
+ WebGUI is Copyright 2001-2009 Plain Black Corporation.
+ -------------------------------------------------------------------
+ Please read the legal notices (docs/legal.txt) and the license
+ (docs/license.txt) that came with this distribution before using
+ this software.
+ -------------------------------------------------------------------
+ http://www.plainblack.com info@plainblack.com
+ -------------------------------------------------------------------
+
+=cut
+
+use strict;
+use base 'WebGUI::Form::Text';
+use WebGUI::International;
+use JSON ();
+use WebGUI::Keyword;
+
+=head1 NAME
+
+Package WebGUI::Form::Group
+
+=head1 DESCRIPTION
+
+Creates a group chooser field.
+
+=head1 SEE ALSO
+
+This is a subclass of WebGUI::Form::SelectList.
+
+=head1 METHODS
+
+The following methods are specifically available from this class. Check the superclass for additional methods.
+
+=cut
+
+#-------------------------------------------------------------------
+
+=head2 getDatabaseFieldType ( )
+
+Returns "CHAR(22) BINARY".
+
+=cut
+
+sub getDatabaseFieldType {
+ return "CHAR(255)";
+}
+
+#-------------------------------------------------------------------
+
+=head2 getName ( session )
+
+Returns the human readable name of this control.
+
+=cut
+
+sub getName {
+ my ($self, $session) = @_;
+ return WebGUI::International->new($session, 'Asset')->get('keywords');
+}
+
+#-------------------------------------------------------------------
+
+=head2 isDynamicCompatible ( )
+
+A class method that returns a boolean indicating whether this control is compatible with the DynamicField control.
+
+=cut
+
+sub isDynamicCompatible {
+ return 1;
+}
+
+#-------------------------------------------------------------------
+
+=head2 toHtml ( )
+
+Returns a group pull-down field. A group pull down provides a select list that provides name value pairs for all the groups in the WebGUI system.
+
+=cut
+
+sub toHtml {
+ my $self = shift;
+ my $session = $self->session;
+ my $style = $session->style;
+ my $url = $session->url;
+
+ $style->setLink($url->extras("yui/build/autocomplete/assets/skins/sam/autocomplete.css"), {rel=>"stylesheet", type=>"text/css"});
+ $style->setScript($url->extras("yui/build/yahoo-dom-event/yahoo-dom-event.js"), {type=>"text/javascript"});
+ $style->setScript($url->extras("yui/build/datasource/datasource-min.js"), {type=>"text/javascript"});
+ $style->setScript($url->extras("yui/build/autocomplete/autocomplete-min.js"), {type=>"text/javascript"});
+ $style->setRawHeadTags('');
+
+ my $name = $self->generateIdParameter($self->get('name'));
+ my $autocompleteDiv = $self->privateName('autocomplete');
+ my $pageUrl = $url->page;
+ my $output
+ = '
' . $self->SUPER::toHtml
+ . '
'
+ . '
';
+(function() {
+ var oDS = new YAHOO.util.XHRDataSource('$pageUrl');
+ oDS.responseType = YAHOO.util.XHRDataSource.TYPE_JSON;
+ oDS.responseSchema = {
+ resultsList : "keywords",
+ };
+
+ var oAC = new YAHOO.widget.AutoComplete("$name", "$autocompleteDiv", oDS);
+ oAC.queryDelay = 0.5;
+ oAC.maxResultsDisplayed = 20;
+ oAC.minQueryLength = 3;
+ oAC.delimChar = [','];
+
+ oAC.generateRequest = function(sQuery) {
+ return "?op=formHelper;class=Keywords;sub=searchAsJSON;search=" + sQuery ;
+ };
+})();
+END_SCRIPT
+ return $output;
+}
+
+sub www_searchAsJSON {
+ my $session = shift;
+ my $search = $session->form->param('search');
+ my $keyword = WebGUI::Keyword->new($session);
+
+ my $keywords = $keyword->findKeywords({search => $search, limit => 20});
+ $session->http->setMimeType('application/json');
+
+ return JSON::to_json({keywords => $keywords});
+}
+
+sub getDefaultValue {
+ my $self = shift;
+ return _formatKeywordsAsWanted($self->SUPER::getDefaultValue(@_));
+}
+
+sub getOriginalValue {
+ my $self = shift;
+ return _formatKeywordsAsWanted($self->SUPER::getOriginalValue(@_));
+}
+
+sub getValue {
+ my $self = shift;
+ return _formatKeywordsAsWanted($self->SUPER::getValue(@_));
+}
+
+sub _formatKeywordsAsWanted {
+ my @keywords;
+ if (@_ == 1 && ref $_[0] eq 'ARRAY') {
+ @keywords = @{ $_[0] };
+ }
+ else {
+ for my $param (@_) {
+ for my $keyword (split /,/, $param) {
+ $keyword =~ s/^\s+//;
+ $keyword =~ s/\s+$//;
+ push @keywords, $keyword;
+ }
+ }
+ }
+ if (wantarray) {
+ return @keywords;
+ }
+ return join(', ', @keywords);
+}
+
+1;
+
diff --git a/lib/WebGUI/Keyword.pm b/lib/WebGUI/Keyword.pm
index 8293f89e5..624226c0f 100644
--- a/lib/WebGUI/Keyword.pm
+++ b/lib/WebGUI/Keyword.pm
@@ -88,6 +88,39 @@ sub deleteKeyword {
$self->session->db->write("delete from assetKeyword where keyword=?", [$options->{keyword}]);
}
+sub findKeywords {
+ my $self = shift;
+ my $options = shift;
+
+ my $sql = 'SELECT keyword FROM assetKeyword';
+ my @where;
+ my @placeholders;
+ my $parentAsset;
+ if ($options->{asset}) {
+ $parentAsset = $options->{asset};
+ }
+ if ($options->{assetId}) {
+ $parentAsset = WebGUI::Asset->new($self->session, $options->{assetId});
+ }
+ if ($parentAsset) {
+ $sql .= ' INNER JOIN asset USING (assetId)';
+ push @where, 'lineage LIKE ?';
+ push @placeholders, $parentAsset->get('lineage') . '%';
+ }
+ if ($options->{search}) {
+ push @where, 'keyword LIKE ?';
+ push @placeholders, '%' . $options->{search} . '%';
+ }
+ if (@where) {
+ $sql .= ' WHERE ' . join(' AND ', @where);
+ }
+ $sql .= ' GROUP BY keyword';
+ if ($options->{limit}) {
+ $sql .= ' LIMIT ' . $options->{limit};
+ }
+ my $keywords = $self->session->db->buildArrayRef($sql, \@placeholders);
+ return $keywords;
+}
#-------------------------------------------------------------------
@@ -123,14 +156,32 @@ sub generateCloud {
my $self = shift;
my $options = shift;
my $display = $options->{displayAsset} || $options->{startAsset};
- my $sth = $self->session->db->read("select count(*) as keywordTotal, keyword from assetKeyword
- left join asset using (assetId) where lineage like ? group by keyword order by keywordTotal desc limit 50",
- [ $options->{startAsset}->get("lineage").'%' ]);
+ my $includeKeywords = $options->{includeOnlyKeywords};
+ my $maxKeywords = $options->{maxKeywords} || 50;
+ if ($maxKeywords > 100) {
+ $maxKeywords = 100;
+ }
+ my $urlCallback = $options->{urlCallback};
+ my $extraWhere = '';
+ my @extraPlaceholders;
+ if ($includeKeywords) {
+ $extraWhere .= ' AND keyword IN (' . join(',', ('?') x @{$includeKeywords}) . ')';
+ push @extraPlaceholders, @{$includeKeywords};
+ }
+ my $sth = $self->session->db->read("SELECT COUNT(*) as keywordTotal, keyword FROM assetKeyword
+ LEFT JOIN asset USING (assetId) WHERE lineage LIKE ? $extraWhere
+ GROUP BY keyword ORDER BY keywordTotal DESC LIMIT ?",
+ [ $options->{startAsset}->get("lineage").'%', @extraPlaceholders, $maxKeywords ]);
my $cloud = HTML::TagCloud->new(levels=>$options->{cloudLevels} || 24);
while (my ($count, $keyword) = $sth->array) {
- $cloud->add($keyword, $display->getUrl("func=".$options->{displayFunc}.";keyword=".$keyword), $count);
+ my $url
+ = $urlCallback ? $display->$urlCallback($keyword)
+ : $options->{displayFunc} ? $display->getUrl("func=".$options->{displayFunc}.";keyword=".$keyword)
+ : $display->getUrl("keyword=".$keyword)
+ ;
+ $cloud->add($keyword, $url, $count);
}
- return $cloud->html_and_css($options->{maxKeywords});
+ return $cloud->html_and_css($maxKeywords);
}
#-------------------------------------------------------------------
@@ -152,13 +203,14 @@ A boolean, that if set to 1 will return the keywords as an array reference rathe
sub getKeywordsForAsset {
my ($self, $options) = @_;
- my @keywords = $self->session->db->buildArray("select keyword from assetKeyword where assetId=?",
- [$options->{asset}->getId]);
+ my $assetId = $options->{asset} ? $options->{asset}->getId : $options->{assetId};
+ my $keywords = $self->session->db->buildArrayRef("select keyword from assetKeyword where assetId=?",
+ [$assetId]);
if ($options->{asArrayRef}) {
- return \@keywords;
+ return $keywords;
}
else {
- return join(" ", map({ (m/\s/) ? '"' . $_ . '"' : $_ } @keywords));
+ return join(', ', @$keywords);
}
}
@@ -317,9 +369,9 @@ Either a string of space-separated keywords, or an array reference of keywords t
sub setKeywordsForAsset {
my $self = shift;
my $options = shift;
- my $keywords = [];
+ my $keywords;
if (ref $options->{keywords} eq "ARRAY") {
- $keywords = $options->{keywords};
+ $keywords = $options->{keywords};
}
else {
$keywords = string2list($options->{keywords});
@@ -335,7 +387,7 @@ sub setKeywordsForAsset {
next
if $found_keywords{$keyword};
$found_keywords{$keyword}++;
- $sth->execute([$assetId, lc($keyword)]);
+ $sth->execute([$assetId, $keyword]);
}
}
}
@@ -348,35 +400,18 @@ Returns an array reference of phrases.
=head3 string
-A scalar containing space separated phrases.
+A scalar containing comma separated phrases.
=cut
sub string2list {
my $text = shift;
return if (ref $text);
- my @words = ();
- my $word = '';
- my $errorFlag = 0;
- while ( defined $text and length $text and not $errorFlag) {
- if ($text =~ s/\A(?: ([^\"\s\\]+) | \\(.) )//mx) {
- $word .= $1;
- }
- elsif ($text =~ s/\A"((?:[^\"\\]|\\.)*)"//mx) {
- $word .= $1;
- }
- elsif ($text =~ s/\A\s+//m){
- push(@words, $word);
- $word = '';
- }
- elsif ($text =~ s/\A"//) {
- $errorFlag = 1;
- }
- else {
- $errorFlag = 1;
- }
+ my @words = split /,/, $text;
+ for my $word (@words) {
+ $word =~ s/^\s+//;
+ $word =~ s/\s+$//;
}
- push(@words, $word);
return \@words;
}