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