How to handle unused Managed Metadata Terms without a WssId? - sharepoint

The Problem
We upload (large amounts of) files to SharePoint using FrontPage RPC (put documents call). As far as we've been able to find out, setting the value of taxonomy fields through this protocol requires their WssId.
The problem is that unless terms have been explicitly used before on a listitem, they don´t seem to have a WSS ID. This causes uploading documents with previously unused metadata terms to fail.
The Code
The call TaxonomyField.GetWssIdsOfTerm in the code snippet below simply doesn´t return an ID for those terms.
SPSite site = new SPSite( "http://some.site.com/foo/bar" );
SPWeb web = site.OpenWeb();
TaxonomySession session = new TaxonomySession( site );
TermStore termStore = session.TermStores[new Guid( "3ead46e7-6bb2-4a54-8cf5-497fc7229697" )];
TermSet termSet = termStore.GetTermSet( new Guid( "f21ac592-5e51-49d0-88a8-50be7682de55" ) );
Guid termId = new Guid( "a40d53ed-a017-4fcd-a2f3-4c709272eee4" );
int[] wssIds = TaxonomyField.GetWssIdsOfTerm( site, termStore.Id, termSet.Id, termId, false, 1);
foreach( int wssId in wssIds )
{
Console.WriteLine( wssId );
}
We also tried querying the taxonomy hidden list directly, with similar results.
The Cry For Help
Both confirmation and advice on how to tackle this would be appreciated. I see three possible routes to a solution:
Change the way we are uploading, either by uploading the terms in a different way, or by switching to a different protocol.
Query for the metadata WssIds in a different way. One that works for unused terms.
Write/find a tool to preresolve WssIds for all terms. Suggestions on how to do this elegantly are most welcome.

setting the WssID value to -1 should help you. I had similar problem (copying documents containing metadata fields) between two different web applications. I've spent many hours on solving strange metadata issues. In the end, setting the value to -1 have solved all my issues. Even if the GetWssIdsOfTerm returns a value, I've used -1 and it works correctly.
Probably there is some background logic that will tak care of the WssId.
Radek

Related

How can I manipulate the Audit screen (SM205510) through code

I'm trying to manipulate the Audit screen (SM205510) through code, using a graph object. The operation of the screen has processes that seem to work when a screen ID is selected in the header. This is my code to create a new record:
Using PX.Data;
Using PX.Objects.SM;
var am = PXGraph.CreateInstance<AUAuditMaintenance>();
AUAuditSetup auditsetup = new AUAuditSetup();
auditsetup.ScreenID = "GL301000";
auditsetup = am.Audit.Insert(auditsetup);
am.Actions.PressSave();
Now, when I execute the code above, it creates a record in the AUAuditSetup table just fine - but it doesn't automatically create the AUAuditTable records the way they are auto-generated in the screen (I realize that the records aren't in the database yet) - but how can I get the graph object to auto-generate the AUAuditTable records in the cache the way they are in the screen?
I've tried looking at the source code for the Audit screen - but it just shows blank, like there's nothing there. I look in the code repository in Visual Studio and I don't see any file for AUAuditMaintenance either, so I can't see any process that I could run in the graph object that would populate those AUAuditTable records.
Any help would be appreciated.
Thanks...
If I had such a need, to manipulate Audit screen records, I'd rather create my own graph and probably generate DAC class. Also I'd add one more column UsrIsArtificial and set it to false by default. And then manage them as ordinary records, but each time I'll add something, I'd set field UsrIsArtificial to false.
You can hardly find how that records are managed at graph level because that records are created and handled on on Graph level, but on framework level. Also think twice or even more about design, as direct writing into Audit history may cause confusion for users in the system of what was caused by user, and what was caused by your code. From that point of view I would rather add one more additional table, then add confusion to existing one.
Acumatica support provided this solution, which works beautifully (Hat tip!):
var screenID = "GL301000"; //"SO303000";
var g = PXGraph.CreateInstance<AUAuditMaintenance>();
//Set Header Current
g.Audit.Current = g.Audit.Search<AUAuditSetup.screenID>(screenID);
if (g.Audit.Current == null) //If no Current then insert
{
var header = new AUAuditSetup();
header.ScreenID = screenID;
header.Description = "Test Audit";
header = g.Audit.Insert(header);
}
foreach (AUAuditTable table in g.Tables.Select())
{
table.IsActive = true;
//Sets Current for Detail
g.Tables.Current = g.Tables.Update(table);
foreach (AUAuditField field in g.Fields.Select())
{
field.IsActive = false;
g.Fields.Update(field);
}
}
g.Actions.PressSave();

Making multiple NSUserActivity instances searchable

I'm planning to make some of my app content publicly indexable, and for that I am using NSUserActivity. From my experiments so far, I've discovered that apparently the only activity that appears in the search results is the last one to get becomeCurrent called on. Is there a way to make all my activities searchable?
The following code is on my appDelegate:
for (Shop* shop in shopManager)
{
NSUserActivity* activity = [[NSUserActivity alloc] initWithActivityType:ACTIVITY_OPEN_SHOP];
activity.userInfo = #{#"additional1": shop.name};
activity.eligibleForPublicIndexing = YES;
activity.eligibleForSearch = YES;
activity.keywords = shop.indexableKeywords;
CSSearchableItemAttributeSet* attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString*)kUTTypeText];
attributeSet.title = shop.name;
attributeSet.contentDescription = shop.indexableDescription;
attributeSet.keywords = [shop.indexableKeywords allObjects];
[activity setContentAttributeSet:attributeSet];
[activity becomeCurrent];
[activities addObject:activity];
}
self.userActivities = [[NSSet alloc] initWithArray:activities];
Hey I have code that is very similar to yours and spotlight is able to index all of my NSUserActivity objects. My guess is that your NSUserActivity objects go out of reference as soon as the next iteration of the loop occurs. Try adding a strong property.
From this source on Apple Forums:
https://forums.developer.apple.com/message/13640#13640
In my case, I had code that was allocating the NSUA, setting some
properties on it, calling becomeCurrent, but then the object would go
out of scope and deallocated. If you're doing this, try tossing the
activity into a strong property to see if you can then see the results
when you search.
Let me know if it still doesn't work.
RequiredUserInfoKeys is the property of NSUserActivity that you have to set in order to work properly in search results.
activity.requiredUserInfoKeys = [NSSet setWithArray:#[#"additional1"]];
I have met the same problem. I think the reason of this is that previous user activity has not enough time for indexing its metadata by system, the next user activity have became current user activity, so only last one is searchable.
My solution is put latter one into a dispatch_after block and delay 1.5 second, making each of them has time to be indexed.
If someone has a better solution, I would be grateful.

Sitecore 7 Search, cannot access a disposed object

I've been working with some Sitecore 7 search code. Example below.
using (var context = Index.CreateSearchContext())
{
// ....Build predicates
var query = context.GetQueryable<SearchResultItem>().Where(predicate);
return query.GetResults();
}
This works fine in SOLR, but when used with standard Lucene, whenever I reference a property in the SearchResults<SearchResultItem> returned by GetResults(), Sitecore errors with "Cannot access a disposed object". It appears that GetResults() doesn't enumerate and still hangs on to the searchcontext.
Anyone come across this before and know how to fix? I've seen some articles suggesting having the SearchContext in application state, but ideally I want to avoid this.
Thanks
Ian
It seems that SearchResults<T> holds reference to SearchHit and the LuceneSearchProvider doesn't hold a reader open. The new version of Lucene automatically closes the reader. I think you might be returning the wrong type. You should probably do like this:
var query = context.GetQueryable<SearchResultItem>().Where(predicate);
return query.ToList();
However make sure, that don't return too many. You should probably use take() etc.
Is GetResults() returning a List or IEnumerable/IQueryable?
Try to return a list in case it isn't already.
return query.GetResults().ToList();
Cheers

Indexing Sitecore Item security and restricting returned search results

I have several roles defined, each with different restrictions to content and media items and I would like to restrict the search results that are returned based on the access rights of the currently logged in user, rather than displaying the result and the user then presented with an "Access Denied" page. Some content will obviously be accessible to extranet\anonymous so they should be returned for all users regardless.
The security follows the standard Sitecore practices so Role inheritance (roles within roles) will be used, so it will need to take this into account also.
I couldn't see anything in the Advanced Database Crawler module that would help and I've looked through the Sitecore Search and Indexing Guide (version 6.6 and version 7) but couldn't find any information about indexing the security applied to items. The following articles have some suggestions:
How can I set up a Lucene index in Sitecore that handles security correctly?
This feels "dirty" and has the potential for performance issues, particularly when there are a large number of items returned. Also, (see in the comments) the issue with paging results.
Security (aka Permissions) and Lucene - How ? Should it be done?
The above looks more realistic, and would filter out the results based on indexed security roles, there would obviously be a need to expand the roles to handle roles within roles. My concern here would be that we would need to handle denied permissions, when we specifically need to deny/restrict access for certain roles to content items (I know this is not recommended practice, but there is a very specific need to always deny).
I'm at the planning stage at the moment so with the release of Sitecore 7 today there is also the possibility to use the updated Lucene libraries and/or SOLR if that makes life easier - assuming of course that some of the modules like WebForms for Marketers and Email Campaign Manager are updated before too long.
What are the solutions that people are using for returning search results taking into account security? Any alternatives than the linked questions above? Maybe something in Sitecore 7 that I can leverage, the updated Lucene libraries or SOLR?
I'd prefer to keep this all "out of the box" Sitecore and not use other third party search products if at all possible.
A slight alternative to the suggestion from Klaus:
In Sitecore.ContentSeach.config you'll find a pipeline called contentSearch.getGlobalSearchFilters
Processors added to this pipeline will be applied to any query, so if we drop in one that applies a filter based on roles we're good.
ComputedField
To start, we want a computed field added to our index configuration:
<fields hint="raw:AddComputedIndexField">
<field fieldName="read_roles" returnType="stringCollection">Sitecore.ContentSearch.ComputedFields.ReadItemRoles,Sitecore.ContentSearch</field>
</fields>
NOTE the stored type is a collection of strings. We'll use it to index all the names of roles that can read an item.
Implementation
We have a base abstract class to handle the extraction of item security details
public abstract class ItemPermissions: IComputedIndexField
{
public string FieldName { get; set; }
public string ReturnType { get; set; }
public object ComputeFieldValue(IIndexable indexable)
{
var indexableItem = indexable as SitecoreIndexableItem;
if (indexableItem == null) return null;
var security = indexableItem.Item.Security;
return GetPermissibles(security);
}
protected abstract object GetPermissibles(ItemSecurity security);
}
We implement the above with the abstracted method
public class ReadItemRoles : ItemPermissions
{
protected override object GetPermissibles(ItemSecurity security)
{
var roles = RolesInRolesManager.GetAllRoles();
return roles.Where(security.CanRead).Select(r => r.Name);
}
}
NOTE There's obviously a performance impact here, this will reduce your indexing speed.
To reduce the impact, only add the the computed field to the index configuration for the index that contains secured content. E.g. If your web content is only accessed by the anonymous user it will add no benefit.
Pipeline
Add the entry in to the config
<contentSearch.getGlobalSearchFilters>
<processor type="Sitecore.ContentSearch.Pipelines.GetGlobalFilters.ApplyGlobalReadRolesFilter, Sitecore.ContentSearch" />
</contentSearch.getGlobalSearchFilters>
Implementation
Implement the pipeline filter to check the roles of the context user
public class ApplyGlobalReadRolesFilter : GetGlobalFiltersProcessor
{
public override void Process(GetGlobalFiltersArgs args)
{
var query = (IQueryable<SitecoreUISearchResultItem>)args.Query;
var userRoles = Context.User.Roles.Select(r => r.Name.Replace(#"\", #"\\"));
var predicate = PredicateBuilder.True<SitecoreUISearchResultItem>();
predicate = userRoles.Aggregate(predicate, (current, role) => current.Or(i => i["read_roles"].Contains(role)));
if(predicate.Body.NodeType != ExpressionType.Constant)
args.Query = query.Filter(predicate);
}
}
Summary
Create a ComputedField that returns a list of all valid roles for a given access right
Apply a pipeline processor to contentSearch.getGlobalSearchFilters to add a query filter to each search request.
Use the PredicateBuilder class to ensure the role names are OR'ed together
The big benefit here is that you take the hit at index time and the handling of item restriction is handled through a search query as normal. No need to worry about the facet numbers or search counts being incorrect.
You can restrict the roles you are checking to compute the field and you can vary the application of the pipeline filter. You can even take out the pipeline filter and just update your queries to filter when you require it.
NOTE The biggest problem with this set up is the requirement to re-index your content when security restrictions change. Should you be applying security restrictions to users themselves, you'll have to include additional computed fields.
Edit 02/06/2013
I was just tinkering with this in a project and realised that it was AND'ing the roles in the query. If a user had multiple roles assigned then both roles would have to have declared rights to the item. I've updated the pipeline processor to use the PredicateBuilder class to OR the roles. A check is also added to ensure the predicate is not a constant, this ensures the query is updated only if we have a filter to apply.
After some more searching around, the Linq to Sitecore article pointed me to the following lines of code:
var index = SearchManager.GetIndex("sitecore_master_index");
var context = index.CreateSearchContext(SearchSecurityOptions.EnableSecurityCheck))
Digging through Sitecore.ContentSearch.dll and Sitecore.ContentSearch.LuceneProvider.dll in dotPeek decompiler and the mention of the indexing.filterIndex.outbound pipeline in the Sitecore 7 Search document I found the following code:
Sitecore.ContentSearch.LuceneProvider.LuceneSearchReults
public IEnumerable<SearchHit<TElement>> GetSearchHits()
{
for (int idx = this.startIndex; idx <= this.endIndex; ++idx)
{
Document doc = this.context.Searcher.IndexReader.Document(this.searchHits.ScoreDocs[idx].Doc, (FieldSelector) this.fieldSelector);
if (!this.context.SecurityOptions.HasFlag((Enum) SearchSecurityOptions.DisableSecurityCheck))
{
string secToken = doc.GetField("_uniqueid").StringValue;
string dataSource = doc.GetField("_datasource").StringValue;
if (!string.IsNullOrEmpty(secToken))
{
bool isExcluded = OutboundIndexFilterPipeline.CheckItemSecurity(new OutboundIndexFilterArgs(secToken, dataSource));
if (!isExcluded)
yield return new SearchHit<TElement>(this.searchHits.ScoreDocs[idx].Score, this.configuration.IndexDocumentPropertyMapper.MapToType<TElement>(doc, this.selectMethod, this.virtualFieldProcessors, this.context.SecurityOptions));
}
}
else
yield return new SearchHit<TElement>(this.searchHits.ScoreDocs[idx].Score, this.configuration.IndexDocumentPropertyMapper.MapToType<TElement>(doc, this.selectMethod, this.virtualFieldProcessors, this.context.SecurityOptions));
}
}
Sitecore.ContentSearch.Pipelines.IndexingFilters
public class ApplyOutboundSecurityFilter : OutboundIndexFilterProcessor
{
public override void Process(OutboundIndexFilterArgs args)
{
if (args.IndexableUniqueId == null || !(args.IndexableDataSource == "Sitecore"))
return;
ItemUri uri = new ItemUri(args.IndexableUniqueId);
if (args.AccessRight != AccessRight.ItemRead || Database.GetItem(uri) != null)
return;
args.IsExcluded = true;
}
}
So it looks like Sitecore 7 gives us the ability to filter the the search results using the security rights of the context user straight out of the box, albeit using a very similar method of checking the item read permissions that Mark Cassidy suggested. Which is good news, since if this is a requirement for Sitecore 6 implementations then we can easily update the Advanced Database Crawler to do the same thing.
I'm still not convinced on the performance of this though, given that Sitecore 7 comes with Item Buckets and the possibility of storing millions of items. It should be possible to create several indexes though, and only EnableSecurityCheck on indexes with security enabled content, but then we need to think about combining the results from several indexes for "global search" results and also take into acccount Boosting which will mean re-ordering the combined results.
well - your considerations seems quite on target.
The easy implementation is to check the item through the database lookup - but paging, facetting and other statistics will fail in that.
The approach we do is to index security tokens for roles - having an inclusion field for allow and an exclusion field for deny rights. You then need to build a query for this - and expand all roles in roles for the query.
There are two issues you might be facing.
One is very complex OR queries for all roles in roles memberships. Might be less then well-performing.
The other is indexing congestion - as you will need to index major parts of the contentitems when security and permissions change as then are inherited.
The alternative is to use join queries - but normally that would be bad performing.
Sitecore developers made a silly mistake, it will never work, because of that statement:
if ((args.IndexableUniqueId != null) && (args.IndexableDataSource == "Sitecore"))
as args.IndexableDataSource will always be equal to "sitecore" not "Sitecore".
I'm currently upgrading big project to latest 7.2 Update and found out that silly mistake, oh Sitecore Devs usual mistakes :)

Searching by related model field

I know there are plenty of topics on this but I searched&tried so many and it is still not working.
I have tables: Team and Worker. Any worker can be assigned to a Team. So at the Workers Manager I want to search Workers also by Team name.
I got the column etc. but when I type part of team name - search starts but the written text dissappears and search doesn't care about the field. I checked the AJAX call with Firebug and there is a field called teamName (I added public field to my Worker model class). But when I print_r criteria in my search method - there is no condition.
How is that possible? How can I perform the searching by related field?
EDIT (my serach() method):
public function dsearch()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('idWorker',$this->idWorker);
$criteria->compare('idLeaderType',$this->idLeaderType);
$criteria->compare('t.idTeam',$this->idTeam);
$criteria->compare('idVoip',$this->idVoip);
$criteria->compare('workLogin',$this->workLogin,true);
$criteria->compare('workPass',$this->workPass,true);
$criteria->compare('name',$this->name,true);
$criteria->compare('surname',$this->surname,true);
$criteria->compare('madeCalls',$this->madeCalls);
$criteria->compare('deleted',$this->deleted);
$criteria->compare('liveChanges',$this->liveChanges);
$criteria->compare('confirmer',$this->confirmer);
$criteria->compare('oldWorkerNum',$this->oldWorkerNum);
$criteria->compare('idDepart',$this->idDepart);
$criteria->compare('Team.name', $this->teamName, true);
$criteria->with=array('Team');
$criteria->together = true;
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
Use the mergeWith: Hope it works.
if($merge!==null){
$criteria->mergeWith($merge);
}
Reference:http://www.yiiframework.com/doc/api/1.1/CDbCriteria#mergeWith-detail
I found usefull extension to do that:
http://www.yiiframework.com/extension/relatedsearchbehavior/
I couldnt get it to work somehow. I downloaded new version and now its fine.
It works pretty well. Thanks for your time though.

Resources