Has anyone had any experience of implementing search on Liferay that required (a moderately complex) security model? How do you deal with the fact that not all of the results you get back from search engine will have permissions to view the content? Does the built-in search in Liferay already do this? If yes, how?
Because filtering the potentially thousands of results after they have been returned can be quite expensive. And if you don't pass all the results through the filter, you don't know how many total results (hits) your search got that you can as a logged in user, 'see'.
I think it first searches from lucene and then checks whether a user has view permission on that. To check the same if you have access to source code see BaseIndexer.search. I am attaching small snippet below to show you how it does it?
PermissionChecker permissionChecker =
PermissionThreadLocal.getPermissionChecker();
int start = searchContext.getStart();
int end = searchContext.getEnd();
if (isFilterSearch() && (permissionChecker != null)) {
searchContext.setStart(0);
searchContext.setEnd(end + INDEX_FILTER_SEARCH_LIMIT);
}
Hits hits = SearchEngineUtil.search(searchContext, fullQuery);
searchContext.setStart(start);
searchContext.setEnd(end);
if (isFilterSearch() && (permissionChecker != null)) {
hits = filterSearch(hits, permissionChecker, searchContext);
}
Related
Apologies if this question has been answered elsewhere, I have had trouble finding any resources on this.
The scenario is this. I have created a custom field in the Tax Preferences screen called Usrapikey.
This value holds an api key for a call that gets done in some custom business logic.
The custom business logic however occurs on the Taxes screen.
So within the event handler I need to access that API key value from the other screen. I have tried instantiating graphs and using linq and bql but to no avail.
below is what I have currently and my error is: No overload for method 'GetExtension' takes 1 arguments
If I am going about this the wrong way please let me know if there is a more civilized way to do this
protected virtual void _(Events.FieldUpdated<TaxRev, TaxRev.startDate> e)
{
var setup = PXGraph.CreateInstance<TXSetupMaint>();
var TXSetupEX = setup.GetExtension<PX.Objects.TX.TXSetupExt>(setup);
var rateObj = GetValues(e.Row.TaxID, TXSetupEX.Usrapikey);
decimal rate;
var tryRate = (Decimal.TryParse(rateObj.rate.combined_rate, out rate));
row.TaxRate = (decimal)rate * (decimal)100;
row.TaxBucketID = 1;
}
Many Thanks!
Well, acumatica support got back. It seems if you want to access the base page use:
TXSetup txsetup = PXSetup<TXSetup>.Select(Base);
To get the extension use:
TXSetupExt rowExt = PXCache<TXSetup>.GetExtension<TXSetupExt>(txsetup);
Then you can access the extension fields like so:
var foo = rowExt.Usrfield;
I am using new Sitecore search, and the issue I ran into is having results come for words that have nothing to do with my search term.
For example, searching for "lies" will find "applies". And this is not what I am looking for.
This is an example of search I am doing (simplified). It is a direct LINQ check for "Contains" on the "Content" property of the SearchResultItem, and most likely not what I supposed to do. It is just happen to be that samples I find online are practically doing so.
Example of my code (simplified). In here I break down the search sentence to separate keywords. By the way, I am looking for a way to show full sentence match first.
using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
var results = context.GetQueryable<SearchResultItem>()
.Filter(i => i.Path.StartsWith(Home.Paths.FullPath))
.Filter(GetTermPredicate(Term));
// use results here
}
protected Expression<Func<SearchResultItem, bool>> GetTermPredicate(string term)
{
var predicate = PredicateBuilder.True<SearchResultItem>();
foreach (var tempTerm in term.Split(' '))
{
predicate = predicate.And(p => p.Content.Contains(tempTerm));
}
return predicate;
}
Thank you in advance!!
All right. I got help from Sitecore Support.
In my version of Sitecore I can use the following to acheive search for a whole word instead of partial:
instead of:
predicate = predicate.And(p => p.Content.Contains(tempTerm));
use
predicate = predicate.And(p => p.Content.Equals(tempTerm));
Issue solved.
Replace Filter in your code by Where, it should be fine,
here is an example :
var currentIndex = ContentSearchManager.GetIndex("sitecore_web_index");
using (var context = currentIndex.CreateSearchContext())
{
var predicate = PredicateBuilder.True();
foreach (var currentWord in term.Split(‘ ‘))
{
predicate = predicate.Or(x => x.Content.Contains(currentWord ));
}
var results = context.GetQueryable().Where(predicate).GetResults();
}
As Ahmed notes, you should use Where instead of Filter, since Filter has no effect on search rank. The classic use case for filters is to apply a facet chosen by the user without distorting the ordering of results, as would happen if you used a Where clause.
Filtering is similar to using Where to restrict the result list. Both methods will affect the result in the same
result list, but when you use a Filter the scoring/ranking of the search hits is not affected by the filters.
Developer's Guide to Item Buckets and Search
There's a good dicussion of when to use Filter on the Sitecore 7 team blog:
Making Good Part 4: Filters, Paging and I'm feeling lucky.
If you only want to search for whole words, you could prefix and postfix the searchterm with a space. Allthough this doesn't catch all situations.
I'm using the search portlet in certain areas of my website, but I'd like to restrict the results to only search for a specific content type: for example only search the news items, or only show Faculty Staff Directory profiles.
I know you can do this after you get to the ##search form through that "filter" list, but is there a way to start with the filter on, so that the "Live Search" results only show the relevant results (i.e. only news items or only profiles).
I suspect you know it already, but just to be sure: You can globally define which types should be allowed to show up in searchresults in the navigations-settings of the controlpanel, and then export and include the relevant parts to your product's GS-profile-propertiestool.xml.
However, if you would like to have some types excluded only in certain sections, you can customize Products.CMFPlone/skins/plone_scripts/livesearch_reply, which already filters the types, to only show "friendly_types" around line 38 (version 4.3.1) and add a condition like this:
Edit:
I removed the solution to check for the absolute_url of the context, because the context is actually the livesearch_reply in this case, not the current section-location. Instead the statement checks now, if the referer is our section:
REQUEST = context.REQUEST
current_location = REQUEST['HTTP_REFERER']
location_to_filter = '/fullpath/relative/to/siteroot/sectionId'
url_to_filter = str(portal_url) + location_to_filter
types_to_filter = ['Event', 'News Item']
if current_location.find(url_to_filter) != -1 or current_location.endswith(url_to_filter):
friendly_types = types_to_filter
else:
friendly_types = ploneUtils.getUserFriendlyTypes()
Yet, this leaves the case open, if the user hits the Return- or Enter-key or the 'Advanced search...'-link, landing on a different result-page than the liveresults have.
Update:
An opportunity to apply the filtering to the ##search-template can be to register a Javascript with the following content:
(function($) {
$(document).ready(function() {
// Let's see, if we are coming from our special section:
if (document.referrer.indexOf('/fullpath/relative/to/siteroot/sectionId') != -1) {
// Yes, we have the button to toggle portal_type-filter:
if ($('#pt_toggle').length>0) {
// If it's checked we uncheck it:
if ($('#pt_toggle').is(':checked')) {
$('#pt_toggle').click();
}
// If for any reason it's not checked, we check and uncheck it,
// which results in NO types to filter, for now:
else {
$('#pt_toggle').click();
$('#pt_toggle').click();
}
// Then we check types we want to filter:
$("input[value='Event']").click();
$("input[value='News Item']").click();
}
}
})
})(jQuery);
Also, the different user-actions result in different, inconsistent behaviours:
Livesearch accepts terms which are not sharp, whereas the ##search-view only accepts sharp terms or requires the user to know, that you can append an asterix for unsharp results.
When hitting the Enter/Return-key in the livesearch-input, the searchterm will be transmitted to the landing-page's (##search) input-element, whilst when clicking on 'Advanced search...' the searchterm gets lost.
Update:
To overcome the sharp results, you can add this to the JS right after the if-statement:
// Get search-term and add an asterix for blurry results:
var searchterm = decodeURI(window.location.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURI('SearchableText').replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1")) + '*';
// Insert new searchterm in input-text-field:
$('input[name=SearchableText]').val(searchterm);
Update2:
In this related quest, Eric Brehault provides a better solution for passing the asterix during submit: Customize Plone search
Of course you can also customize the target of advanced-search-link in livesearch_reply, respectively in the JS for ##search, yet this link is rather superfluous UI-wise, imho.
Also, if you're still with Archetypes and have more use-cases for pre-filtered searchresults depending on the context, I can recommend to have a look at collective.formcriteria, which allows to define search-criteria via the UI. I love it for it's generic and straightforward plone-ish approach: catalogued indizi and collections. In contradiction to eea.facetednavigation it doesn't break accessibility and can be enhanced progressively with some live-search-js-magic with a little bit of effort, too. Kudos to Ross Patterson here! Simply turn a collection (old-style) into a searchform by changing it's view and it can be displayed as a collection-portlet, as well. And you can decide which criteria the user should be able to change or not (f.e. you hide the type-filter and offer a textsearch-input).
Watch how the query string changes when you use the filter mechanism on the ##search page. You're simply adding/subtracting catalog query criteria.
You may any of those queries in hidden fields in a search form. For example:
<form ...>
....
<input type="hidden" name="portal_type" value="Document" />
</form>
The form on the query string when you use filter is complicated a bit by its record mechanism, which allows for some min/max queries. Simple filters are much easier.
I'm stumbling a bit with my CouchDB knowledge.
I have a database of content that is tagged with an array of tags and has a created date.
I want to create a view that pulls a limited number of newest stories tagged with a specific tag.
For example, the newest 6 stories tagged "Business."
Ran across this question, which seems to get me almost to where I need to go, but I'm missing one key element, which I think is how to craft the query string to sort by one key while searching by the other.
Here's my map function.
function(doc) {
if (doc.published == "yes" && doc.type == "news") {
for (var i = 0; i < doc.tags.length; i++) {
if (doc.tags[i]) {
emit([doc.created, doc.tags[i]], doc);
}
}
}
}
So how do I query that view for a all documents tagged "Business" that are the newest documents based on created.
The created attribute is a date sortable format.
First, I would switch the order of your emit:
emit([doc.tags[i], doc.created]);
(leave out doc as well, you can just add include_docs=true to get the entire document, and your view won't take up so much disk-space in the process)
Now you can query for the all the stories tagged as "Business" by using the following querystring:
startkey=["Business"]&endkey=["Business",{}]
You'll get all the documents with the tag business, and they'll be sorted by date.
This takes advantage of view collation, which basically is the rules governing how indexes are sorted/queried. For complex keys like this, the sorting is done for each item of the array separately. (ie. the first key is sorted first, the second key is sorted second, etc) This is why the order matters, as you must always move from left to right when querying a view index.
If you want the 6 most recent, your querystring will need to change:
descending=true&limit=6&endkey=["Business"]&startkey=["Business",{}]
NOTICE You need to swap the startkey/endkey values, due to how the descending parameter works. See the View reference page on the wiki for further explanation.
OK, I think I figured this out, but I'm not quite certain I fully understand it.
I found this story about complex keys and searching and sorting.
My map function looks like this:
function(doc) {
if (doc.published == "yes" && doc.type == "news") {
for (var i = 0; i < doc.tags.length; i++) {
if (doc.tags[i]) {
emit([doc.tags[i], doc.created], doc);
}
}
}
}
And to query and sort using it, the query looks like this.
http://localhost:5984/database/_design/story/_view/tagged?limit=10&startkey=["Business"]&endkey=["Business",{}]&descending=false
I'm getting the results I want, but I'm not entirely certain I understand it all.
I am making a page which pulls from the user's browser their preferred language, via the Request.UserLanguages....which returns a two letter code (ex. "en") or detailed code (ex. "en-GB") .
I basically get the string of user languages (they are in order of preference) and store them in a string array. Then I use a loop to check if the language code in the first position of the string array is any of the codes for a certain language (another string array hard coded in).
Is there a better way to do this? I'm noticing increased load time and am worried additional languages will further slow the page load...
if (!IsPostBack)
{ //Holds possible user languages preferences to check client machine against
String[] compJapaneseLang = { "ja-jp","ja","jp","jpn","euc","shift-jis" };
}
//Get client machines langugage preferences
String[] userLang = Request.UserLanguages;
//Loop through variation of preferences from possible user langugaes
for (int i = 0; i < compJapaneseLang.Length; i++)
{
//IF JAPANESE
if (userLang.GetValue(0).ToString().ToLowerInvariant().Equals(compJapaneseLang.GetValue(i).ToString().ToLowerInvariant()))
cc.JapeneseObject();
}
Thanks!
Storing them in a list turned out best, not really much else one can do....