Customized Tridion Search Index Handler: Custom vs Standard field for page url? - search

I was playing around with custom Search Indexing Handlers for SDL Tridion 2011 (GA). I got something working, using the very helpful information provided by Arjen, however I am not sure if my execution is the best option.
The requirement is to be able to search for pages in the CMS by url (eg www.example.com/news/index.html). In order to do this I have the created a class using the ISearchIndexingHandler interface (code below). I am indexing the url in the ContentText field of the item, however I am not sure if this would normally contain something else for a page (I think a page only has metadata so this should be OK). The advantage of using this over a custom field is that I can simply type the url in the search box without having to use <url> IN <fieldname> or something like that.
So my question is, is there any reason not to use ContentText for Pages, and is there any advantage in using a custom field? Also bonus marks go to anyone with good ideas on how to handle BluePrinting (if I create a page in a parent publication, I want the local urls also to be indexed in the child publications), and the case where a Structure group path is altered (I guess I can somehow trigger a re-index of child page items from within my indexing handler).
The code:
using System;
using Tridion.ContentManager.Search;
using Tridion.ContentManager.Search.Indexing.Handling;
using Tridion.ContentManager.Search.Indexing.Service;
using Tridion.ContentManager.Search.Indexing;
using Tridion.ContentManager.Search.Fields;
namespace ExampleSearchIndexHandler
{
public class PageUrlHandler : ISearchIndexingHandler
{
public void Configure(SearchIndexingHandlerSettings settings)
{
}
public void ExtractIndexFields(IdentifiableObjectData subjectData, Item item, CoreServiceProxy serviceProxy)
{
PageData data = subjectData as PageData;
if (data != null)
{
PublishLocationInfo info = data.LocationInfo as PublishLocationInfo;
string url = GetUrlPrefix(data) + info.PublishLocationUrl;
item.ContentText = url;
}
}
private string GetUrlPrefix(PageData page)
{
//hardcoded for now, but will be read from publication metadata
return "www.example.com";
}
}
}

You can store the url in the ContextText Property. Thies field is used to index Template content data.
Tridion does not index shared item(s) of child publication.
Indexing is triggered on Item modification(create, update, delete, localize and unlocalize).
Or you can use reindexing tool to reindex ur item. but there is no way to index shared items in child publication.

I don't think you can include the URL prefix in neither your search query as the indexed item. Because shared items are not indexed, you will probably index the Page from the Website Structure layer, which is never published.
When a Structure Group is moved you would have to make an event handler that triggers re-indexing all child pages using a protected method of the TOM.NET API. This method is not part of the public API, so posting the code for that solution would probably declare me a persona non grata with R&D :)
Before you re-index anything you should store the original publish location url of the Structure Group in the TcmEventArgs.ContextVariables property, so you can verify whether or not a re-indexing action is necessary.

Related

Is there a way to get the related pages from a left side of the page relationship that is not the CurrentDocument in Kentico

I need to access to the Main Page information from any of the Secondary Pages (1,2,3,4...) using a page relationship, and then retrieve the relationship list from the main page, in this case SecondaryPage1,SecondaryPage2,SecondaryPage3, etc..
I have the following structure
MainPage (Page Type e.g. Article) isRelatedTo:
SecondaryPage1 (Page Type: e.g. Item)
SecondaryPage2 (Page Type: e.g. Item)
SecondaryPage3 (Page Type: e.g. Item)
SecondaryPage4 (Page Type: e.g. Item)
SecondaryPage5 (Page Type: e.g. Item)
Is there an easy way to do it? I am using a CMSRepeater to display the items. I am thinking on creating a custom CMSRepeater for this specific scenario but I would like to know if there is a different approach.
So, to recap, The SecondaryPage1 isRelatedTo MainPage on the right side.
MainPage -> isRelatedTo -> SecondaryPage1
I am trying the display whole list from the MainPage, I need to access to that information on any of the secondary pages.
I created this code, it works pretty well I am just trying to discover is there is simpler solution or if there is an alternative.
....
List<CMS.DocumentEngine.TreeNode> mainRelatedItems = new List<CMS.DocumentEngine.TreeNode>();
mainRelatedItems.AddRange(DocumentHelper.GetDocuments().Types(PageTypes.Split(';')).InRelationWith(CurrentDocument.NodeGUID, PageRelationship, relationshipSide));
List<CMS.DocumentEngine.TreeNode> secondaryRelatedItems = new List<CMS.DocumentEngine.TreeNode>();
foreach (CMS.DocumentEngine.TreeNode item in mainRelatedItems)
{
if(ExcludeCurrentDocument)
secondaryRelatedItems.AddRange(DocumentHelper.GetDocuments().Types(PageTypes.Split(';')).InRelationWith(item.NodeGUID, SecondLevelPageRelationship, secondaryRelationshipSide).Where(x => x.NodeGUID != CurrentDocument.NodeGUID));
else
secondaryRelatedItems.AddRange(DocumentHelper.GetDocuments().Types(PageTypes.Split(';')).InRelationWith(item.NodeGUID, SecondLevelPageRelationship, secondaryRelationshipSide));
}
....
CustomRepeater.ItemTemplate = TransformationHelper.LoadTransformation(CustomRepeater, TransformationName);
CustomRepeater.DataSource = secondaryRelatedItems;
CustomRepeater.DataBind();
....
In your document query, you can specify the relationship GUID as well as the side and relationship name. It doesn't have to be the current document.
DocumentHelper.GetDocuments().InRelationWith(guid, "reltionshipname", side)
** UPDATE **
Based on the most recent update of your question, you'd need to get the Main document id/guid by URL parameter or session or some other parameter, then look that node up using something like DocumentHelper.GetDocument(MainDocID). After you have that Main document, you can perform your lookup using the code I provided above.
It doesn't sound like you have a great way to get this info simply because one of those Secondary pages could be related to possibly many other pages or page types. One suggestion may be to create a very specific relationship name and simply query your Main document by that very specific relationship name and the Secondary page ID to get any Main pages that may be related to it.

In Orchard CMS, how can I get all items of a particular content type based in the values of an attached part's properties

I'm using Orchard 1.7.2.
I have created a new content type called PropertyImage of stereotype Media. I also created a part called PropertyPart and attached that part to my PropertyImage content type. This allows a user to pick a product when uploading a PropertyImage (ie to say 'This image is of this property').
So far so good.
Now what I'd like to do is query for all PropertyImages that have a PropertyPart attached to them where the associated property is x, y, or z.
This is what I have so far:
var images = _orchardServices.ContentManager
.Query<PropertyPart, PropertyRecord>()
.Where(p => p.PropertyId == id)
.ForType(new[] { "PropertyImage" });
This however will only return a collection of PropertyParts, which is not what I want, because I want the whole PropertyImage Content Item. How can I do this?
I should point out that properties come from an external source, and are therefore not content items.
Edit
As soon as I asked this question, I realised I could just append my query with this:
.List().Select(p=>p.ContentItem)
Sometimes it just helps to talk your problem through!
As soon as I asked this question, I realised I could just append my query with this:
.List().Select(p=>p.ContentItem)
Sometimes it just helps to talk your problem through!

Azure Table Storage - Customising table entity saves for persisting collections

There's already a lot of blog posts out there about being able to hook into the WritingEntity event to customise the XML that gets submitted to the server, such as this.
Has anything changed with this process in the newer versions of the SDK? I ask because I have the following simple entity:
public class Label : TableServiceEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public string ContactInfo { get; set; }
public List<string> Urls { get; set; }
public Label()
{
Urls = new List<string>():
}
}
I want to be able to persist that collection of URLs, and I'm already aware that the only thing that's supported directly in terms of arrays/collections is binary arrays. So I thought, fine, I'll just hook into that WritingEntity event and serialize that list to JSON/XML, then add that to the properties list as per that blog post. Then deserialize back to the list during the handling of the ReadEntity event.
However, when I do that, on the call to SaveChanges on the TableServiceContext I get a DataServiceRequest exception that contains an inner NotSupported exception with the message "Only collections of entities are supported". Is this because the String class doesn't inherit from TableEntity? The thing that's confusing me is, when I check the XML that it's written out, it has actually been able to successfully write the custom XML with the additionally added property containing the serialized list, despite the exception.
When I try to retrieve the label via CreateQuery, I get the same exception thrown.
Can anybody tell me what I'm doing wrong here, and what the best practice is for dealing with this situation? I've already came across Lokad Cloud for doing the persistence, but it doesn't seem ideal to me as the querying options for getting data back out are too limited for what I'm wanting to do.
I did have a look at past questions but none seem to address this issue directly.
Any advice would be appreciated!
Based on the response:
I don't know if you got the impression that I'm serializing the entire entity manually? The partition key is just "LABELX", where X is the first letter of the Name property of the label, and the row key is just the string representation of the GUID (I know it's wasteful to store both of those, but I'm just trying to get up and running at the moment).
If you set a breakpoint on the first line of the WritingEntity event and you inspect the XML that's in the e.Data property, there is nothing to represent the URLs collection in the XML. It doesn't matter whether the URLs list is empty, null, or it has entries in it - it doesn't appear at all in the XML, so it doesn't matter what list I pass in. So I think that should answer all 4 questions.
Inside the writing entity event, there really isn't anything special: just code to serialize the list to XML, and then code to add a property to the XML, as per the blog post - it all runs without any exceptions.
OK, sorry, I had neglected to mention that fact that I'm only using the development storage at the moment. The problem seemed to be the fact that I had created some Label entities that didn't have any URLs, before I had created ones that did, and so the schema information in the TableContainer table didn't have the additional URL property. After I cleaned out the database and added a fully populated object before adding anything else, everything worked OK!
I've got some code working on this - and it definitely seems to work with 1.4 SDK
My code is more based on generic entities and the sources I used for inspiration were:
Jai's post on http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/481afa1b-03a9-42d9-ae79-9d5dc33b9297/
with modifications from Yi-Lun Luo on http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/f57cb566-cc7a-4b31-b1ab-47b6d16604af/
and also a good some credit due to ideas from http://azuretablequery.codeplex.com/
I'm guessing that something is wrong in your WritingEntity event handler. Can you post any more of your code - especially:
how are you serialising the RowKey and PartitionKey?
are you removing the raw Urls list from the serialisation?
if you're using XML for the inner serialisation, then are you fully escaping that XML?
One further debugging idea is to just try to get the code working step by step - i.e.
start with an entity with no list,
then try adding another simple test property using a WritingEntity hook,
then try adding a Urls list, and removing it during WritingEntity.
then try serialising and adding this new property in WritingEntity

Updating the Internal Name using the Content Database

We currently have a field which has the wrong ID. the IDs and Internal Names of Sharepoint Fields are readonly on the domain model. I was wondering if there is a way to update them even by using the content database.
One sure way is to delete the field and recreate it. but it already has data and there are thousands of pages. I was wondering if there is a way just to update the IDs and Internal Name without doing the dropping and recreation of fields.
Thanks
Even if it may work, don't do it.
It's:
Dangerous, as you may skip dependencies
Not supported
Recreating the field using some script to keep data is safer.
We ran into the same problem. Messing with the DB was not an option for us (and shouldn't be for you either), but you can work around it. Unfortunately, it will require touching each page to update the metadata.
First, create the column like it should be in the list. Then, you can copy the data from the old column to the new column in a variety of ways:
DataSheet view
Programmatically via web services (don't need to have access to the server)
Programmatically via console app (will need to run locally on the server)
Honestly, writing a small console app will be the quickest. For example:
string siteUrl = "http://server/sitecollection/";
string webUrl = "subsite1/subsite2/";
string listName = "Your List Name";
using (SPSite site = new SPSite(siteUrl))
{
using (SPWeb web = site.OpenWeb(webUrl))
{
SPList list = web.Lists[listName];
foreach (SPListItem item in list.Items)
{
item["New_x0020_Column_x0020_Name"] = item["Old_x0020_Column_x0020_Name"];
}
}
}
Also, it would HIGHLY recommend trying this in a DEV environment first. Just do an STSADM restore from your production environment and test your console app. Then, validate your data and delete the old column!
I would not suggest modifying the content database, since it is:
unsupported (if you do it, Microsoft will not help you when you're in real trouble even if you have MS Premier Support)
very complicated to do. You'd have to update a number of tables and you would never be sure if you actually updated all the required things.
What you can try to do - see if the internal name property is actually "read only" or it is defined as "friend" and you cannot call it from a different code assembly. If it's the latter case, you can use .Net reflection to set the property value. See this MSDN documentation link for details.

Updating already-deployed SharePoint content types to handle additional item events

I have a site content type that was used for a handful of lists throughout my site collection. In that content type, I describe an event receiver to handle the ItemAdding event. This works fine. Now I need to update the content type so that ItemUpdating is also handled. Off the top of my head, I tried simply modifying the xml for my content type, since this seemed to allow for easy version tracking. This worked in the sense that my updates were applied to the site content type, but not to my lists that had been using this content type. This much was expected. Then I noticed that the SharePoint SDK takes a grim view of that:
Under no circumstances should you
update the content type definition
file for a content type after you have
installed and activated that content
type. Windows SharePoint Services does
not track changes made to the content
type definition file. Therefore, you
have no method for pushing down
changes made to site content types to
the child content types.
The SDK then points to a couple sections which describe how to use the UI or code to push changes. Since the UI offers no hook into event receivers, I guess I will be choosing the code path.
I thought I'd be able to do something like this and just add a new event receiver to the list's copy of the content type:
SPList list = web.Lists["My list"];
SPContentType ctype = list.ContentTypes["My content type"];
// Doesn't work -- EventReceivers is null below.
ctype.EventReceivers.Add(SPEventReceiverType.ItemUpdating,
"My assembly name", "My class name");
But the catch is that ctype.EventReceivers is null here, even though I have ItemAdding already hooked up to this list. It appears that it was moved to the list itself. So, the list has a valid EventReceivers collection.
SPList list = web.Lists["My list"];
list.EventReceivers.Add(SPEventReceiverType.ItemUpdating,
"My assembly name", "My class name");
So, I have a couple questions:
Is the correct way to do this just to add any new event receivers directly to the list and just forget about my content type altogether?
To accomplish this change, what's the best way to handle this in terms of configuration management? Should I create a simple console app to find all the appropriate lists and modify each of them? Or is somehow creating a Feature a better option? Either way, it seems like this change is going to be off on its own and difficult to discover by future devs who might need to work with this content type.
Did you call ctype.Update(true) after adding the EventReceiver? If you don't it won't be persisted .
And don't use the List content type, use SPWeb.ContentTypes instead.
This code works for me:
var docCt = web.ContentTypes[new SPContentTypeId("0x0101003A3AF5E5C6B4479191B58E78A333B28D")];
//while(docCt.EventReceivers.Count > 0)
// docCt.EventReceivers[docCt.EventReceivers.Count - 1].Delete();
docCt.EventReceivers.Add(SPEventReceiverType.ItemUpdated, "ASSEMBLYNAME, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c5b857a999fb347e", "CLASSNAME");
docCt.Update(true);
The true parameter means it gets pushed down to all child ContentTypes as well. (i.e. to all lists using the content type).
To answer the second part of your question, this a tricky thing because of the fact that changes to contenttypes on the sitecollection won´t be pushed down to the lists where it´s used. A "copy" is essentially made of the fields in the sitecollection and there is no more link between them after you add a contenttype to the list. I think this is due to the fact that you are supposed to make changes to lists without it affecting the sitecollection. Anyhow my contribution to this "problem", and how I have solved it, involves making the xml the "master" and in a featurereceiver I pull up the xml and find all places where the contenttype is used and from there update the contenttypes (really the fieldrefs) on list level to match the one in the xml. The code goes something like:
var elementdefinitions = properties.Feature.Definition.GetElementDefinitions();
foreach (SPElementDefinition elementDefinition in elementdefinitions)
{
if (elementDefinition.ElementType == "ContentType")
{
XmlNode ElementXML = elementDefinition.XmlDefinition;
// get all fieldrefs nodes in xml
XmlNodeList FieldRefs = ElementXML.ChildNodes[0].ChildNodes;
// get reference to contenttype
string ContentTypeID = ElementXML.Attributes["ID"].Value.ToString();
SPContentType ContentType =
site.ContentTypes[new SPContentTypeId(ContentTypeID)];
// Get all all places where the content type beeing used
IList<SPContentTypeUsage> ContentTypeUsages =
SPContentTypeUsage.GetUsages(ContentType);
}
}
The next thing is to compare the fieldrefs in xml xml with the fields on the list (done by the ID attribute) and making sure that they are equal. Unfortunately we can´t update all things on the SPFieldLink class (the fieldref) and (yes I know it´s not supported) here I have actually used reflection to update those values (f.e. ShowInEditForm ).
As far as the second part of your question, I wanted to pass along what we've done for similar situations in the past. In our situation, we needed a couple of different scrips: One that would allow us to propagate content type updates down to all of the lists in all webs and another that would reset Master Pages/Page Layouts to the site definition (un-customized form).
So, we created some custom stsadm commands for each of these actions. Doing it this way is nice because the scripts can be placed into source control and it implements the already-existing stsadm interface.
Custom SharePoint stsadm Commands

Resources