Tuesday, February 18, 2014

Sitecore Upgrade Process

Here is a high-level look at the upgrade process of a simple development environment

1) Backup web.config file and all other config files such as the ones in app_config folder.

2) Backup Website/sitecore folder (preferably this folder is not source controlled)

3) Backup master, core, and web databases (backup analytics db also if used)

4) The actual upgrade process is very incremental.  There is a Sitecore update package plus configurations changes for each increment.  For example, going from 6.6 Inital Release to 6.6 Update 2 would require installing two packages and perform two sets of config changes which must be done separately.

5) Follow the steps carefully.  For example: Follow the steps carefully on the correct upgrade process here: http://sdn.sitecore.net/Products/Sitecore%20V5/Sitecore%20CMS%206/Update/6_5_0_rev_120427.aspx.  Sometimes SQL scripts have to be run.

6) Clear out DLLs in the website/bin folder.  This should remove all references to old assemblies and prepare to be refreshed with new ones from lib (if used) upon building.

7) 7zip open the .Update file to obtain latest DLLs and place into the shared lib/sitecore folder.  Since all the references should be made to assemblies here, placing new DLLs here will cause all the references to be updated and copied over to the Website/bin folder upon building.  Add references to new DLLs in Visual Studio.

8) Rebuild solution

9) Test Sitecore UI and website

If things are acting funky in the Sitecore UI, chances are, that the sitecore folder does not have all necessary files.  Download a zip archive of the entire upgrade and copy over the entire Sitecore directory.   The archive should be more "pure" and has all the necessary files.

If you have to upgrade a multi-server production environment, there are a few additional steps in between.  We are assuming that the environment is a multi-server single database environment and there is also a different environment for production content authoring.  The production content authoring environment upgrade would be exactly the same as above since its also a simple single server setup.

10) For the server that is to be upgraded, you must take it off the load balancer.  Since the production content authoring environment has already been set up, that means the databases have already been upgraded.  Now it is just a code deploy along with bug fixes.  Deploy the code to this server.  Test for errors and fix.  Otherwise, if results are desirable, the upgrade process is done.  If results are desirable, put it back on the load balancer.  Repeat for each remaining server.

Thursday, February 6, 2014

Solr Simple Boosting

This is a very simple method to implement boosting the relevancy of search results.  You can append a boost value after query parameters.  You can do that directly in the "q" value like this:




The "^2" is the boost value.  The higher the number, the higher the relevancy if the search term is matched in the corresponding field.  In this example, we are searching for the term "new york" in the fields, "_city" and "_institute_name".  All results that match "new york" in the "_institute_name" field will rank higher with a greater score and will be at the top of the result set.


The second way to approach this is to enable "dismax" and put the search fields and boost values in the "qf" field and put the search term in the "q" field like this:






If you look at the "fl" field, you will see a few column names.  This is the list of fields to display on the result set.  The "score" field is a calculated field that is available for all indexed documents.  It is not defined in the schema.xml markup but it is available to use when analyzing relevancy.





Keep in mind that boosting will only work if text fields are assigned tokenized text fields such as "text_en".  Non-tokenized fields like "string" will not work.



Sunday, February 2, 2014

Implement Jcrop Image Cropping in Sitecore Admin UI

The purpose of this exercise is to add an image cropping tool to any image field in the Sitecore Admin UI.  It comprises of multiple steps such as setting up the UI to have a button on image fields to open up this tool and then developing the actual tool.  To make things simpler, we will create the tool as a standalone ASPX page that will reside in the same Website project.

Part 1: Set Up Admin UI


The goal here is to make sure we add the "Crop Image" button to the top of all image fields.  To do this we have to go to the Core database in content editor.  Open the node:

/sitecore/system/Field types/Simple Types/Image/Menu/

Here, you will see all the subitems as buttons that reflect the screenshot above.  You can insert a new item in this folder but it is best to just duplicate an existing item.  Give the new item a name and then open it for more options.  Give the message an appropriate value.  We will use:

contentimage:crop(id=$Target)

for this example.

Since the built-in Image field class has already been defined, we would have to create a derived object that mirrors the functionalities of the Image field and append more features to it.  Use this as an example:

using System.Web.UI;
using System;
using Sitecore.Web.UI.Sheer;
using Sitecore;
using Sitecore.Text;
using Sitecore.Web.UI.Framework.Scripts;
using Sitecore.Globalization;
using Sitecore.Resources;

namespace MyCoolAssembly.Fields
{
    public class Image : Sitecore.Shell.Applications.ContentEditor.Image
    {
        public override void HandleMessage(Message message)
        {
            if (!ShouldHandleMessage(message))
            {
                return;
            }

            if (IsCropClick(message))
            {
                OpenApp();
                return;
            }

            base.HandleMessage(message);
        }

        private bool ShouldHandleMessage(Message message)
        {
            return IsCurrentControl(message)
                    && !string.IsNullOrWhiteSpace(message.Name);
        }

        private bool IsCurrentControl(Message message)
        {
            return AreEqualIgnoreCase(message["id"], ID);
        }

        private static bool IsCropClick(Message message)
        {
            return AreEqualIgnoreCase(message.Name, "contentimage:crop");
        }

        private void OpenApp()
        {
            UrlString urlString = new UrlString("/croptool.aspx");
            SheerResponse.Eval(new ShowEditorTab()
            {
                Header = "Image Cropper",
                Icon = Images.GetThemedImageSource("Business/32x32/data_replace.png"),
                Url = urlString.ToString(),
                Id = "CropTool",
                Closeable = true,
                Activate = true
            }.ToString());
        }

        private static bool AreEqualIgnoreCase(string one, string two)
        {
            return string.Equals(one, two, StringComparison.CurrentCultureIgnoreCase);
        }
    }
}

Notice that in the IsCropClick method, we check the message name to make sure this value matches the message defined earlier for this new button. 

Now, since we have overridden the default Image field and made a custom derived object, we need to make sure that Sitecore Admin knows about it.  Let's go to:

/sitecore/system/Field types/Simple Types/Image/

In the control field, modify it to point to the class that you just created.  In this example, it would be:

mca:Image

But what is mca?  We have to make sure that Sitecore knows mca is the prefix for the MyCoolAssembly library.  We can do this in the web.config:

<system.web>
    <pages>
      <controls>
        ...
        <add tagPrefix="mca" namespace="MyCoolAssembly" assembly="MyCoolAssembly"/>
      ...
      </controls>
   </pages>
</system.web>

Save all your work, rebuild you code and open up content editor.  Open an item with image fields.  If all is well, you should see the new button on top of each image field and clicking the button should open a new tab with the ASPX page inside.


Part 2: Set Up ASPX Page

This page will be a regular web form page with a reference to the item that was referenced by the image field that opened this page.  You would have to decide if you want to pass that value as a property when calling this tab in the OpenApp method above or to append the item id as part of the url parameter.

As for the front end, I suggest using Jcrop.  You can read more and download the tool here:

http://deepliquid.com/content/Jcrop.html

Jcrop will provide all the UI elements and data capturing you will need.  To work with Jcrop, you simply use an <img> tag to reference the image of this media library content item.  To call Jcrop on any <img> tag, all you have to do is call:


$(function(){ $('#jcrop_target').Jcrop(); });


If you need additonal parameters, you would have to consult the Jcrop manual and API for more details.  Also, Jcrop provides the GUI for cropping and capturing the cropped image coordinates and new dimensions. It does not perform the actual image manipulation and processing.  This would have to be done in the codebehind.  Jcrop provides some good examples of capturing the coordinates and dimensions.  This is a good example:

 $(function(){
    $('#jcrop_target').Jcrop({
        onChange: showCoords,
        onSelect: showCoords
     });
});

function showCoords(c)
{
    $('#x').val(c.x);
    $('#y').val(c.y);
    $('#x2').val(c.x2);
    $('#y2').val(c.y2);
    $('#w').val(c.w);
    $('#h').val(c.h);
};

Of course, it is best if the 6 values are stored using hidden input controls.  These values can be passed back to the codebehind upon form submission. 

In the codebehind, you need a method similar to this:

using SD = System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
...
static byte[] Crop(string Img, int Width, int Height, int X, int Y)
{
        try
        {
            using (SD.Image OriginalImage = SD.Image.FromFile(Img))
            {
                using (SD.Bitmap bmp = new SD.Bitmap(Width, Height))
                {
                    bmp.SetResolution(OriginalImage.HorizontalResolution, OriginalImage.VerticalResolution);
                    using (SD.Graphics Graphic = SD.Graphics.FromImage(bmp))
                    {
                        Graphic.SmoothingMode = SmoothingMode.AntiAlias;
                        Graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        Graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
                        Graphic.DrawImage(OriginalImage, new SD.Rectangle(0, 0, Width, Height), X, Y, Width, Height, SD.GraphicsUnit.Pixel);
                        MemoryStream ms = new MemoryStream();
                        bmp.Save(ms, OriginalImage.RawFormat);
                        bmp.Save(ms, OriginalImage.RawFormat);
                        return ms.GetBuffer();
                    }
                }
            }
        }
        catch (Exception Ex)
        {
            throw (Ex);
        }
}

Now save it to a temp location:

using (MemoryStream ms = new MemoryStream(CropImage, 0, CropImage.Length))
{
                ms.Write(CropImage, 0, CropImage.Length);
                using (SD.Image CroppedImage = SD.Image.FromStream(ms, true))
                {
                    if (ImageName.IndexOf("_t.jpg") != -1)
                    {
                        SaveTo = path + ImageName;
                    }
                    CroppedImage.Save(SaveTo, CroppedImage.RawFormat);
                }
}

The above code is just a sample and may need modifications to suit your needs.

Now we have to make sure we create a new media library content item with the new image and associate the original content item image field with this new media library item.  Also, we do not want to overwrite any existing images in the media library.  Follow these suggested steps:

1) Get the item name of original image, create new item in the same folder with the same base template and name it "originalimagename-currentdatetime".  This will ensure the new item does not have a duplicate name. 
2) Upload the new image and associate with this new media library content item.
3) Go back to the original content item that called this ASPX tool and change the image field value to this new media library item content id.
4) Reload content item.