Sitecore display name url does not work without language code - sitecore

On this Sitecore website (6.5.0 rev. 120706), I have this sitecore item called XYZ. So I have http://example.com/XYZ/.
I've added french localization, so using display names I now have:
http://example.com/XYZ-en/
http://example.com/XYZ-fr/
The english version works well, but the french does not and resolves to 404 unless I go to the english first, click on my language switcher button first. When I click on it, I'm redirected to http://example.com/fr-CA/XYZ-fr/, which works. From then on, the english url stops working, the french one works. As I switch languages like that, I always only have one of the two that work.
That button runs this code:
protected void LanguageLinkClick(object sender, EventArgs e)
{
var lang = (Sitecore.Context.Language.Name == "en") ? "fr-ca" : "en";
Tools.RedirectToLanguage(lang, Response);
}
That Tools function runs the following code:
public static void RedirectToLanguage(string pStrLangToSet, HttpResponse pResponse)
{
if (!string.IsNullOrEmpty(pStrLangToSet))
{
var newLang = Language.Parse(pStrLangToSet);
if (newLang != null)
{
Sitecore.Context.SetLanguage(newLang, true);
var itm = Sitecore.Context.Item;
if (Sitecore.Context.Item != null)
{
var itemInLang = Sitecore.Context.Database.Items[itm.ID, newLang];
if (itemInLang != null)
{
pResponse.Redirect(BuildUrl(itemInLang));
}
}
}
}
}
This is somewhat old code that is in this old project.
Is there anything I should look for that would intercept default display name behavior? Or isthis behavior with display names something that's not managed out of the box?
Thanks for any help!

This is the expected behaviour. This article from John West describes the steps involved in the language resolving process and details of the ItemResolver process can be found here.
What I assume you have set in the LinkProvider for your site is useDisplayName=true and maybe languageEmbedding=false, something like the following:
<add name="sitecore" type="Sitecore.Links.LinkProvider, Sitecore.Kernel"
alwaysIncludeServerUrl="false" addAspxExtension="true" encodeNames="true"
languageLocation="filePath" lowercaseUrls="false" shortenUrls="true"
languageEmbedding="never" useDisplayName="true" />
This tells the LinkManager to build URLs using the display name of the Item, hence you have multilingual URLs. Your RedirectToLanguage method switches the context language, from EN to FR-CA and vice versa, which puts Sitecore into that specific language mode for the user.
The following pipeline in <httpRequestBegin> tries to resolve an Item from your requested URL:
<processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel" />
And one of the attempts in that method is to ResolveUsingDisplayName(args). So it will be trying to resolve an Item with an EN display name, but the Context Language is set to FR-CA so in fact it will never find the item, hence the 404.
You have 2 options here:
Set languageEmbedding="always" in your LinkProvider, which mean your URLs will be formatted like "/en/Nice-Product-With-Lots-Of-Options" and "/fr-ca/Mon-Produit-Avec-Plusieurs-Options".
Add a custom pipeline processor after the default ItemResolver which tries to resolve using the alternate language, and if it finds a match sets the Context Item and switches language.
The following will call the default Sitecore ItemResolver after you switch to the alternate language and then attempt to resolve, which will try to find the Item using display name:
public class AlternateLanguageItemResolver : HttpRequestProcessor
{
public override void Process(HttpRequestArgs args)
{
Assert.ArgumentNotNull(args, "args");
if (Context.Item != null || Context.Database == null || args.Url.ItemPath.Length == 0)
return;
var origLanguage = Sitecore.Context.Language;
Sitecore.Context.Language = AltLanguage;
// try to find it using default ItemResolver with alternate language
var itemResolver = new Sitecore.Pipelines.HttpRequest.ItemResolver();
itemResolver.Process(args);
if (Context.Item == null)
{
// well we didn't find it, so switch the context back so everyting can continue as normal
Sitecore.Context.Language = origLanguage;
return;
}
// We found the Item! Switch the User Language for future requests
Sitecore.Context.SetLanguage(AltLanguage, true);
}
private Language _altLanguage = null;
private Language AltLanguage
{
get
{
if (_altLanguage == null)
{
var altLang = (Sitecore.Context.Language.Name == "en") ? "fr-ca" : "en";
_altLanguage = Language.Parse(altLang);
}
return _altLanguage;
}
}
}
And patch it in after the default ItemResolver:
<processor type="Sitecore.Sample.AlternateLanguageItemResolver, Sitecore.Sample"
patch:after="processor[#type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"/>
This is an untested prototype, check that subsequent requests are returned in the correct switched language. Otherwise redirect back to itself like you in your original code.

So if I understand correctly you'd like these urls to resolve the language for you:
http://example.com/XYZ-en/
http://example.com/XYZ-fr/
The LanguageResolver will resolve the language in this order:
1. language can be distilled from the url (cannot be done with above urls)
2. a language cookie is available for this site (sitename#lang)
3. fall back to default configured language
When you switch, using your switcher, the url paths start with the language code, then the resolver is able to resolve the language by url and persists the language in the language cookie. This is why you experience this behavior of one working language at a time.
The best you can do is always use language in urls (linkprovider configuration), otherwise you'd have to hook in the languageresolver and do some funky check on displayname for each language, but that will probably get very expensive.

Related

How to configure EXM to create new messages with a custom language version, as default

I am trying to configure the EXM root for an Austrian website to create the new messages with a de-AT language version and to have the de-AT language selected as default.
My question is: How can I configure EXM to automatically create a language version for de-AT when a new message is created?
What I've done so far ..
I managed to achieve having the de-AT selected automatically by playing around with the Language - Select the target language field from the Message Context section of the Standard fields - but the actual message item that is created does not contain the de-AT version - and I am getting an error when trying to save the message.
Error message: Edited language version 'German (Austria)' could not be found. It may have been deleted by another user.
As it can be seen in this screnshot, when I open EXM and I create a new message, the de-AT language version is automatically selected. The problem is that the message has no de-AT language version assigned, so it won't allow to save the item.
I think you missed to add language version to standard values of your message template.
Templates in EXM works in a same way as anywhere in Sitecore. You should have language versions for your emails under:
/sitecore/templates/Email Campaign/Messages
/sitecore/templates/Branches/Email Campaign/Messages
None of the 'tricks' worked to automatically add a new language version when you create a new message inside EXM, therefore I've added a new OnItemSave event which checks if the Item is derived from the base Message template and creates a new language version - based on the own business logic.
Config:
<configuration xmlns:x="http://www.sitecore.net/xmlconfig/">
<sitecore>
<events>
<event name="item:added">
<handler type="ABC.SitecoreExtensions.Handlers.EmailExperienceExtensions, ABC" method="OnItemAdded" />
</event>
</events>
</sitecore>
Code
namespace ABC.SitecoreExtensions.Handlers
{
class EmailExperienceExtensions
{
readonly Sitecore.Data.Database masterDb = Sitecore.Configuration.Factory.GetDatabase("master");
private const string EXM_BASE_EMAIL_TEMPLATE_ID = "{A0EA9681-5C86-43AB-80F7-C522DADF6F12}";
public void OnItemAdded(object sender, EventArgs args)
{
Assert.ArgumentNotNull((object)args, "args");
Item obj1 = Event.ExtractParameter(args, 0) as Item;
if (obj1 == null)
return;
if (obj1.IsDerived(new Sitecore.Data.ID(EXM_BASE_EMAIL_TEMPLATE_ID )))
{
//logic to determine the context site and to pickup the language
....
if (rootItem == null)
{
return;
}
var siteContext = SiteContext.GetSite(rootItem.Name);
var lang = LanguageManager.GetLanguage(siteContext.Language);
Item ca = masterDb.GetItem(obj1.Paths.FullPath, lang);
using (new Sitecore.SecurityModel.SecurityDisabler())
{
try
{
if (0 == ca.Versions.Count)
{
ca.Versions.AddVersion();
}
}
catch (Exception ex)
{
// catch exception
}
}
}
}
}
}

How Do I Reset Language to Default Language

I added code to change the language to default website language if there is no language in the URL. so if i am on danish website : http://mywebsite/da
then I removed the language code "da", i am switching to the default website language which is English.
The issue is on some pages it needs second page refresh to set the language to default website language, even in the cookie language is changed correctly. this is my code:
I created module for that, so in web.config I added this int the end under system.webServer/modules :
<add name="ResetLanguageModule" type="MyWebsite.Modules.ResetLanguageModule, MyWebsite.Web" />
My Code :
public void Init(HttpApplication app)
{
app.BeginRequest += Application_BeginRequest;
}
private static void Application_BeginRequest(object sender, EventArgs e)
{
// if user is on the root or the url does not contians language in url
if (HttpContext.Current.Request.RawUrl == "/" || !HttpContext.Current.Request.RawUrl.Contains(string.Format("/{0}/", Sitecore.Context.Language.Name)))
{
ResetLanguage();
}
}
private static void ResetLanguage()
{
// change language to default one if the comming request is a page.
if (Sitecore.Context.Page != null &&
Sitecore.Context.Site != null &&
Sitecore.Context.Language.Name != Sitecore.Context.Site.Language)
{
Language currentSiteLanugage;
if (Language.TryParse(Sitecore.Context.Site.Language, out currentSiteLanugage))
{
Sitecore.Context.SetLanguage(currentSiteLanugage, true);
}
}
}
You should convert your module into a processor and add it to the httpRequestBegin pipeline.
Make sure you you add it before default Sitecore LanguageResolver.

Implementing Sitecore Multisite Robots.txt files

How to implement to have different robots.txt files for each website hosting on the same Sitecore solution. I want to read dinamically robots.txt from sitecore items.
you need to follow next steps:
1) Create and implement your custom generic (.ashx) handler.
2) In the web.config file add the following line to the section
3) Navigate to the section and add here
4) On home item you will have "Robots" field (memo, or multi line field, not richText field)
Your custom generic handler will look like :
public class Robots : IHttpHandler
{
public virtual void ProcessRequest(HttpContext context)
{
private string defaultRobots = "your default robots.txt content ";
string robotsTxt = defaultRobots;
if ((Sitecore.Context.Site == null) || (Sitecore.Context.Database == null))
{
robotsTxt = defaultRobots;
}
Item itmHomeNode = Sitecore.Context.Database.GetItem(Sitecore.Context.Site.StartPath);
if (itmHomeNode != null)
{
if ((itmHomeNode.Fields["Robots"] != null) && (itmHomeNode.Fields["Robots"].Value != ""))
{
robotsTxt = itmHomeNode.Fields["Robots"].Value;
}
}
context.Response.ContentType = "text/plain";
context.Response.Write(robotsTxt);
}
We had similar problems especially in the multi site environment, so we used the handlers for implementing robots.txt
Create a new class inheriting from IHTTPHandler and implement the logic within the process method. Write the XML ouput to the context object.
context.Response.ContentType = "text/plain";
context.Response.Output.Write({XML DATA});
Add the custom handler and trigger.
<handler trigger="~/Handlers/" handler="robots.txt"/>
<add name="{Name}" path="robots.txt" verb="*" type="{Assembly Name and Type}" />
It seems that if you want to access Sitecore Context, and any items, you need to wait untill this stuff is resolved. The aboce method will always give you a null in the Site definition, as this isnt resolved when the filehandler kicks in.
It seems that to get the Sitecore.Context, you should implement a HttpRequestProcessor in Sitecore, that renderes the robots.txt, example on this website:
http://darjimaulik.wordpress.com/2013/03/06/how-to-create-handler-in-sitecore/
You can refer to this blog post for step-by-step explanation on how to do it with a custom HttpRequestProcessor and a custom robots settings template : http://nsgocev.wordpress.com/2014/07/30/handling-sitecore-multi-site-instance-robots-txt/

Can Glass.Mapper V3 support language fallback (field-level and item-level)?

We just updated our project to use Glass.Mapper V3. We LOVE it. But we've encountered an issue. It doesn't seem to respect language fallback.
We have our site set up so that if a user picks a non-default language, they will see the item of that language if it exists. If not, they will see the default ("fallback") language version. We have also set this up at the field level, so that if there is a non-default version of an item but not all the fields are changed, any unchanged fields will fall back to the default language version's value for that field.
Is there anything we can do to enable Glass to use language fallback?
I am updating this with a bit of background on why we do the check. If you ask for a Sitecore item that doesn't exist you get a null value, so that is simple to handle. However if you ask for a Sitecore item that doesn't exist in that particular language returns an item with no versions. This means we have to do this check because otherwise Glass would end up returning empty class which I don't think makes much sense.
This answer will get a little experimental.
First in the the Spherical.cs file you need to disable the check:
protected void Application_BeginRequest()
{
Sitecore.Context.Items["Disable"] = new VersionCountDisabler();
}
We can then move the check to later on to the Object Construction pipeline. First create a task:
public class FallbackCheckTask : IObjectConstructionTask
{
public void Execute(ObjectConstructionArgs args)
{
if (args.Result == null)
{
var scContext = args.AbstractTypeCreationContext as SitecoreTypeCreationContext;
if (scContext.Item == null)
{
args.AbortPipeline();
return;
}
//this checks to see if the item was created by the fallback module
if (scContext.Item is Sitecore.Data.Managers.StubItem)
{
return;
}
// we could be trying to convert rendering parameters to a glass model, and if so, just return.
if (String.Compare(scContext.Item.Paths.FullPath, "[orphan]/renderingParameters", true) == 0)
{
return;
}
if (scContext.Item.Versions.Count == 0)
{
args.AbortPipeline();
return;
}
}
}
}
Then finally register this task in the GlassMapperScCustom class:
public static void CastleConfig(IWindsorContainer container){
var config = new Config();
container.Register(
Component.For<IObjectConstructionTask>().ImplementedBy<FallbackCheckTask>().LifestyleTransient()
);
container.Install(new SitecoreInstaller(config));
}
I haven't tested this but it should in theory work <- disclaimer ;-)
There are few potential issues with provided solution when sitecore 7 (7.2) + IoC + solr + mvc is used.
When using IoC ex Winsdor please make sure that your Global.asax looks like this one <%# Application Codebehind="Global.asax.cs" Inherits="Sitecore.ContentSearch.SolrProvider.CastleWindsorIntegration.WindsorApplication" Language="C#" %>. Once, by mistake this file has been changed to <%# Application Codebehind="Global.asax.cs" Inherits="Merck.Manuals.Web.Global" Language="C#" %> and language fallback was not working. Also the errors we were getting wasn't descriptive as we thought that solr schema is incorrect.
Code Sitecore.Context.Items["Disable"] = new VersionCountDisabler(); can be added to PreprocessRequestProcessor and it works fine which is better solution that modifying global.asax.

Language fallback for media library item in sitecore

I have a number of PDFs in English language. I have web pages in English and German lang.
If in a German Page I want to display PDF of English lang, it is not possible as that German version PDF is not available, so I tried to do fallback for media library item, even then no help.
So can someone please tell me any alternative for this.
NOTE: I don't want to upload english document in German version, as there are other languages available and customers cannot upload those many times in all lang.
I need to upload a document in Only English but display in all other Languages irrespective of that document is there in that lang or not.
It's ok even if I need to make changes through code.
Thanks in advance
Which template are you using to upload your PDF? If you are using /sitecore/templates/System/Media/Unversioned/Pdf then this inherits from /sitecore/templates/System/Media/Unversioned/File and the blob field for this is marked as shared anyway:
Shared fields are shared across language versions, so if you upload an English PDF and link to that same media item from a German item then it will link to the original English PDF.
In Sitecore, when adding a field to a template, there's a checkbox called "shared". What's it for?
There are a couple of ways to do this. You can do it in the code for your rendering, or you can use the language fallback module from the Sitecore marketplace.
To do it in code you would need to create a new MediaProvider. Create a class that inherits from Sitecore.Resources.Media.MediaProvider and override the protected virtual MediaData GetMediaData(MediaUri mediaUri) method.
This method gets the sitecore item for the context language or the language in the Uri. So you can implement the fall back here:
public class MediaProviderWithFallback : Sitecore.Resources.Media.MediaProvider
{
protected override Sitecore.Resources.Media.MediaData GetMediaData(Sitecore.Resources.Media.MediaUri mediaUri)
{
Assert.ArgumentNotNull((object)mediaUri, "mediaUri");
Database database = mediaUri.Database;
if (database == null)
{
return null;
}
string mediaPath = mediaUri.MediaPath;
if (string.IsNullOrEmpty(mediaPath))
{
return null;
}
Language language = mediaUri.Language;
if (language == null)
{
language = Context.Language;
}
Sitecore.Data.Version version = mediaUri.Version;
if (version == null)
{
version = Sitecore.Data.Version.Latest;
}
Sitecore.Data.Items.Item mediaItem = database.GetItem(mediaPath, language, version);
if (mediaItem == null)
{
return (MediaData)null;
}
// Check for language fallback
if (mediaItem.Versions.Count == 0)
{
// Workout your language fallback here from config or sitecore settings etc...
language = Language.Parse("en");
// Try and get the media item in the fallback language
mediaItem = database.GetItem(mediaPath, language, version);
if (mediaItem == null)
{
return null;
}
}
return MediaManager.Config.ConstructMediaDataInstance(mediaItem);
}
}
Please note - this is untested code. You should store your fallback in config or modify the language template in sitecore.
Once you have that class you will need to update your web.config to use your provider over Sitecores. So find this section in the web.config and change the type to be your class and assembley:
<!-- MEDIA PATH -->
<mediaPath defaultProvider="default">
<providers>
<clear />
<add name="default" type="Sitecore.Resources.Media.MediaPathProvider, Sitecore.Kernel" />
</providers>
</mediaPath>
It's best practice to try to identify whether media will need to be versioned ahead of time. If you know media is going to need to be versioned based on language, you should make sure to update in your web.config the following attribute:
<!--By default, Media items are not versionable and the below setting is set to false in the web.config.
If you upload an image in one language, it will persist across all language versions.
If you change this to true, then versioning will apply and you would have to set the media item into all language versions,
or enable fallback, but if enforce version presence is turned on and media template guids are included in EnforceVersionPresenceTemplates,
then you'll have to make sure all language versions at least exist-->
<setting name="Media.UploadAsVersionableByDefault">
<patch:attribute name="value">true</patch:attribute>
</setting>
Alex Shyba's Partial Language Fallback module will successfully work with this. I would recommend making sure not to enforce version presence on any media templates (don't want to force admins to have to create blank language versions). Then they can create english versions and then only create a language version when they need to override it.
You will need, in the case of using partial language fallback, to make sure the enable fallback checkboxes are checked on the media versionable template fields.
I also recommend updating the media provider so that it embeds language into the media url so that caching doesn't come into play. EG: if you create a pdf named Directions.pdf and it loads at www.site.com/media/Directions.pdf, when you switch between languages, it very well could cache it. So you would want to update the media provider to encode the media url with the context language.
You can see a demo here:
https://github.com/Verndale-Corp/Sitecore-Fallback-FullDemo
public class CustomMediaProvider : MediaProvider
{
public override string GetMediaUrl(MediaItem item, MediaUrlOptions options)
{
Assert.ArgumentNotNull((object)item, "item");
Assert.ArgumentNotNull((object)options, "options");
string result = base.GetMediaUrl(item, options);
// Added by Verndale, check if language should be embedded
UrlOptions urlOptions = UrlOptions.DefaultOptions;
urlOptions = LanguageHelper.CheckOverrideLanguageEmbedding(urlOptions);
if (urlOptions.LanguageEmbedding == LanguageEmbedding.Always && options.UseItemPath)
{
result = "/" + Sitecore.Context.Language.Name.ToLowerInvariant() + Sitecore.StringUtil.EnsurePrefix('/', result);
}
return result;
}
public static UrlOptions CheckOverrideLanguageEmbedding(UrlOptions urlOptions)
{
var thisSite = Sitecore.Context.Site;
if (urlOptions.Site != null)
thisSite = urlOptions.Site;
if (!String.IsNullOrEmpty(thisSite.SiteInfo.Properties["languageEmbedding"]))
{
if (thisSite.SiteInfo.Properties["languageEmbedding"].ToLower() == "never")
urlOptions.LanguageEmbedding = LanguageEmbedding.Never;
else if (thisSite.SiteInfo.Properties["languageEmbedding"].ToLower() == "always")
urlOptions.LanguageEmbedding = LanguageEmbedding.Always;
else if (thisSite.SiteInfo.Properties["languageEmbedding"].ToLower() == "asneeded")
urlOptions.LanguageEmbedding = LanguageEmbedding.AsNeeded;
}
return urlOptions;
}
}