From 0cd851d779e893e24d0b26eedc10698adcd77d07 Mon Sep 17 00:00:00 2001 From: Doug Bell Date: Mon, 11 Oct 2010 16:40:09 -0500 Subject: [PATCH] search completed --- lib/WebGUI/Admin.pm | 170 +++++++++++++++++++++++++++++++++++++ www/extras/admin/admin.css | 4 + www/extras/admin/admin.js | 26 ++++-- 3 files changed, 195 insertions(+), 5 deletions(-) diff --git a/lib/WebGUI/Admin.pm b/lib/WebGUI/Admin.pm index 4c0b888ca..6999cb8e6 100644 --- a/lib/WebGUI/Admin.pm +++ b/lib/WebGUI/Admin.pm @@ -6,8 +6,10 @@ use Moose; use JSON qw( from_json to_json ); use namespace::autoclean; use Scalar::Util; +use Search::QueryParser; use WebGUI::Pluggable; use WebGUI::Macro; +use WebGUI::Search; has 'session' => ( is => 'ro', @@ -133,6 +135,42 @@ sub getAssetTypes { return %assetList; } + +#---------------------------------------------------------------------- + +=head2 getKeywordString ( keywordString ) + +Munge the keyword string from the user into something mysql will Do The +Right Thing with + +=cut + +# Stolen from WebGUI::Search->search +sub getKeywordString { + my ( $self, $keywords ) = @_; + + # do wildcards for people like they'd expect unless they are doing it themselves + unless ($keywords =~ m/"|\*/) { + # split into 'words'. Ideographic characters (such as Chinese) are + # treated as distinct words. Everything else is space delimited. + my @terms = grep { $_ ne q{} } split /\s+|(\p{Ideographic})/, $keywords; + for my $term (@terms) { + # we add padding to ideographic characters to avoid minimum word length limits on indexing + if ($term =~ /\p{Ideographic}/) { + $term = q{''}.$term.q{''}; + } + $term .= q{*}; + next if WebGUI::Search->_isStopword( $term ); + next + if $term =~ /^[+-]/; + $term = q{+} . $term; + } + $keywords = join q{ }, @terms; + } + + return $keywords; +} + #---------------------------------------------------------------------- =head2 getNewContentTemplateVars @@ -260,6 +298,80 @@ sub getNewContentTemplateVars { #---------------------------------------------------------------------------- +=head2 getSearchPaginator ( queryString ) + +Get a paginator for searching with the given queryString. + +=cut + +sub getSearchPaginator { + my ( $self, $queryString ) = @_; + my $session = $self->session; + + my $sql = 'SELECT assetId FROM assetIndex JOIN asset USING (assetId) WHERE ' + . ' ( ' . $self->getSqlFromQueryString( $queryString ) . ' ) ' + ; + + my $p = WebGUI::Paginator->new( $session ); + $p->setDataByQuery( $sql ); + return $p; +} + +#---------------------------------------------------------------------------- + +=head2 getSqlFromQueryString ( queryString ) + +Parse the query string and return a SQL boolean clause suitable to be used +as a WHERE clause. Does not return WHERE, as you could also use it for HAVING + +=cut + +sub getSqlFromQueryString { + my ( $self, $queryString ) = @_; + + my $sqp = Search::QueryParser->new( defField => 'keywords' ); + my $query = $sqp->parse( $queryString ); + + # Recursion is recursive + my $part = sub { + my ( $query, $conj ) = @_; + my @parts; + for my $part ( @$query ) { + if ( ref $part->{value} ) { + push @parts, $self->getSqlFromQueryString( $_ ); + } + elsif ( $part->{field} eq 'keywords' ) { + push @parts, "MATCH ($part->{field}) AGAINST ('" + . $self->getKeywordString( $part->{value} ) + . "')"; + } + else { + # TODO: Add op validation + # TODO: Add field quoting + # TODO: Add value quoting + if ( $part->{op} eq ':' ) { + my $value = '%' . $part->{value} . '%'; + push @parts, "$part->{field} LIKE '$value'"; + } + else { + push @parts, "$part->{field} $part->{op} '$part->{value}'" + } + } + } + return join " $conj ", @parts; + }; + my $must = $query->{'+'} ? '(' . $part->( $query->{'+'}, 'AND' ) . ')' : undef; + my $mustNot = $query->{'-'} ? 'NOT ( ' . $part->( $query->{'-'}, 'OR' ) . ')' : undef; + my $may = $query->{''} ? $part->( $query->{''}, 'OR' ) : undef; + + my $sql = $must . ( $must && $mustNot ? " AND " : '' ) . $mustNot + . ( $must || $mustNot ? " OR " : '' ) . $may + ; + return $sql; +} + +#---------------------------------------------------------------------------- + =head2 getTreePaginator ( $asset ) Get a page for the Asset Tree view. Returns a WebGUI::Paginator object @@ -518,6 +630,64 @@ sub www_processAssetHelper { #---------------------------------------------------------------------- +=head2 www_searchAssets ( ) + +Search the asset tree for the given keywords and filters + +=cut + +sub www_searchAssets { + my ( $self ) = @_; + my $session = $self->session; + my ( $user, $form ) = $session->quick(qw{ user form }); + + # Get the search + my $queryString = $form->get('query'); + return to_json( {} ) unless $queryString; + + my $i18n = WebGUI::International->new( $session, "Asset" ); + my $assetInfo = { assets => [] }; + my $p = $self->getSearchPaginator( $queryString ); + + for my $result ( @{ $p->getPageData } ) { + my $assetId = $result->{assetId}; + my $asset = WebGUI::Asset->newById( $session, $assetId ); + + # Populate the required fields to fill in + my %fields = ( + assetId => $asset->getId, + url => $asset->getUrl, + lineage => $asset->lineage, + title => $asset->menuTitle, + revisionDate => $asset->revisionDate, + childCount => $asset->getChildCount, + assetSize => $asset->assetSize, + lockedBy => ($asset->isLockedBy ? $asset->lockedBy->username : ''), + canEdit => $asset->canEdit && $asset->canEditIfLocked, + helpers => $asset->getHelpers, + ); + + $fields{ className } = {}; + # The asset icon + $fields{ icon } = $asset->getIcon("small"); + + # The asset type (i18n name) + $fields{ className } = $asset->getName; + + push @{ $assetInfo->{ assets } }, \%fields; + } + + $assetInfo->{ totalAssets } = $p->getRowCount; + $assetInfo->{ sort } = $session->form->get( 'orderByColumn' ); + $assetInfo->{ dir } = lc $session->form->get( 'orderByDirection' ); + + $session->http->setMimeType( 'application/json' ); + + return to_json( $assetInfo ); +} + +#---------------------------------------------------------------------- + =head2 www_view ( session ) Show the main Admin console wrapper diff --git a/www/extras/admin/admin.css b/www/extras/admin/admin.css index 835339822..1a1b7a049 100644 --- a/www/extras/admin/admin.css +++ b/www/extras/admin/admin.css @@ -138,6 +138,10 @@ input.disabled { margin: 1em 8% 0; } +.searchResults table { + width: 100%; +} + .searchKeywords { display: block; width: 90%; diff --git a/www/extras/admin/admin.js b/www/extras/admin/admin.js index a4e8abe6b..0f91c6e1a 100644 --- a/www/extras/admin/admin.js +++ b/www/extras/admin/admin.js @@ -179,6 +179,10 @@ WebGUI.Admin.prototype.editAsset */ WebGUI.Admin.prototype.gotoAsset = function ( url ) { + if ( this.tabBar.get('activeIndex') > 1 ) { + this.tabBar.selectTab( 0 ); + this.currentTab = "view"; + } if ( this.currentTab == "view" ) { window.frames[ "view" ].location.href = url; this.treeDirty = 1; @@ -1268,7 +1272,7 @@ WebGUI.Admin.AssetTable.prototype.showHelperMenu // destroy the old helper menu! this.helperMenu.destroy(); } - this.helperMenu = new YAHOO.widget.Menu( { + this.helperMenu = new YAHOO.widget.Menu( document.createElement('div'), { position : "dynamic", clicktohide : true, constraintoviewport : true, @@ -1939,11 +1943,13 @@ WebGUI.Admin.Search // Create a container for the datatable this.dataTableContainer = document.createElement('div'); + this.dataTableContainer.style.display = "none"; + this.dataTableContainer.className = "searchResults"; this.dataTableContainer.id = YAHOO.util.Dom.generateId(); this.formContainer.appendChild( this.dataTableContainer ); // Create a container for the paginator - // ... + // TODO WebGUI.Admin.Search.superclass.constructor.call( this, admin, { dataSourceUrl : '?op=admin;method=searchAssets;', dataTableId : this.dataTableContainer.id, @@ -2141,9 +2147,9 @@ WebGUI.Admin.Search.prototype.buildQueryString WebGUI.Admin.Search.prototype.requestSearch = function ( ) { // Build the new search URL - var searchUrl = ';keywords=' + encodeURIComponent( this.searchKeywords.value ); + var query = 'query=' + encodeURIComponent( this.searchKeywords.value ); for ( var i = 0; i < this.filters.length; i++ ) { - searchUrl += ';' + filter.type + '=' + filter.getValue(); + query += ' ' + encodeURIComponent( filter.type + ':' + filter.getValue() ); } var callback = { @@ -2157,10 +2163,20 @@ WebGUI.Admin.Search.prototype.requestSearch this.buildQueryString( this.dataTable.getState(), this.dataTable, - searchUrl + query ), callback ); }; +/** + * onDataReturnInitializeTable ( sRequest, oResponse, oPayload ) + * Initialize the table with a new response from the server + */ +WebGUI.Admin.Search.prototype.onDataReturnInitializeTable += function ( sRequest, oResponse, oPayload ) { + this.dataTableContainer.style.display = "block"; + WebGUI.Admin.Tree.superclass.onDataReturnInitializeTable.call( this, sRequest, oResponse, oPayload ); +}; +