SharePoint Data View / Data Form Web Part - Group items by month on DateTime field

by James 9/26/2008 2:54:00 PM

This is one of the most popular requirements I always get from clients. Especially when they have large collection of documents and want to give their user an easy way to browse document items in Data Form Web Part (DFWP).

For example, you have a list of documents displayed in DFWP and each document has "Published Date" field. How can you group DFWP items by its "Published Date" month value?

Goal

James Tsai .Net SharePoint Blog - DFWP Group By Month Final

Solution

Luckily, all you need to do is to change a few lines in XSL that renders your DFWP. Here is step by step of how to do it.

1. Add "Group By" to Data Form Web Part.

Here is what your original DFWP should look like without any "Group by" on field

James Tsai .Net SharePoint Blog - DFWP default view without Group by field

In SharePoint Designer (SPD), open DFWP's Common Data View Tasks and select "Sort and Group"

James Tsai .Net SharePoint Blog - DFWP common data view tasks

Select DateTime field you want to Group by, In this example it is "Published Date". "Show group header" is also selected here because this way you can see what values are used to group items

James Tsai .Net SharePoint Blog - DFWP sort and group options

After above steps, you can see the DFWP is correctly grouped on "Published Date" field. But it treats each DateTime value as a different group value.

By default, DFWP group DateTime field based on their actual Date (YYYY-MM-DD) value. Not just year and month (YYYY-MM).

James Tsai .Net SharePoint Blog - DFWP Group By DateTime Field Default

Since requirement here is to group items with same month (item with "Publsihed Date" 08/16/2008 and 08/03/2008 in this example) in same group. Further steps are needed.

2. Modify XSL

Inside the XSL that renders your DFWP, search for "dvt_groupfield". If you use "Search All" within XSL, <xsl:when test="not ($dvt_groupfield)"> is the line you want.

James Tsai .Net SharePoint Blog - DFWP XSL Search For DVT_GroupField

And you will see this section,

<xsl:when test="not ($dvt_groupfield)">
        <xsl:value-of select="ddwrt:NameChanged(string(@PublishedDate), 0)" />
</xsl:when>

Change this to,

<xsl:when test="not ($dvt_groupfield)">
        <xsl:value-of select="ddwrt:NameChanged(string(substring(@PublishedDate,1,7)), 0)" />
</xsl:when>

Above change is critical, that's where you specify how you want to group items in DFWP.

The original @PublishedDate value is presented in format "YYYY-MM-DDTHH:MM:SSZ". substring(@PusblishedDate,1,7) gives us "YYYY-MM" which is what we want DFWP to group by. Note: In XSL, index starts from 1 not 0.

You will see this after above changes

James Tsai .Net SharePoint Blog - DFWP XSL After Change Group By Value

Items are now group correctly, but group header still displaying incorrect text. Because In DFWP XSL, group value and header value are generated from different template. You have changed first one (in above step), and now last step is to change header value.

Just scroll down from where you changed group value in XSL a bit, and you should see this line

<xsl:when test="not (@PublishedDate) and (@PublishedDate) != false()"><xsl:value-of select="' '" /></xsl:when>
        <xsl:otherwise>
                <xsl:value-of select="ddwrt:GenDisplayName(string(@PublishedDate))" />

Change ddwrt:GenDisplayName(string(@PublishedDate)) to substring(@PublishedDate,1,7). Like following,

<xsl:when test="not (@PublishedDate) and (@PublishedDate) != false()"><xsl:value-of select="' '" /></xsl:when>
        <xsl:otherwise>
                <xsl:value-of select="substring(@PublishedDate,1,7)" />

And you should get this as result

James Tsai .Net SharePoint Blog - DFWP XSL After Change Heading Values

You can also change Group Heading to display in format YYYY-MMM (like the one in first screen shot) , or anything you like by changing above XSL.

Currently rated 4.2 by 9 people

  • Currently 4.222222/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , ,

Programming | SharePoint

Understand SharePoint Permissions - Part 2. Check SharePoint user/group permissions with Permissions web service and JavaScript

by James 9/13/2008 9:34:00 PM

Goal

In Part 1. I have mentioned that you can check user permissions without using any of SharePoint OM, or you can perform permission check on remote site/application. In this post you are going see a simple example of how to do it.

The goal here is to create a new web application with web service reference to SharePoint Permission web service. This web application only has one default.aspx page with two drop down lists on it.

First drop down list (DDL) pre-populates data with SharePoint users/groups name returned by GetPermissionCollection method of Permissions web service. Second DDL contains list of SPBasePermissoions permissions.

JavaScript function will be used to check if user/group selected in first DDL has permission right selected in second DDL. An image will be displayed to indicates the result.

James Tsai .Net SharePoint Blog - Two Drop Down Lists

Project Setup

Create a new ASP.NET project with standard project template and then add Web Service reference. Two image files also included in project for displaying the result.

James Tsai .Net SharePoint Blog - New web application with WS reference

The web service URL is http://<site url>/_vti_bin/permissions.asmx and we named it SharePointPermissionsService

James Tsai .Net SharePoint blog - Add Permission Web Service Reference

Coding

Create Drop Down Lists


[code:html]
<!-- default.aspx -->

<div style="float:left;">

        <asp:DropDownList ID="ddlUserGroup" runat="server" />

        <select name="ddlPermissionSet" id="ddlPermissionSet" onChange="DoPermissionCheck()">

                <option value="0x0000000000000001">ViewListItems</option>

                <option value="0x0000000000000002">AddListItems</option>

                <!--....................more permissions here........-->

                <option value="0x0000010000000000">EditMyUserInfo</option>

        </select>

<div>

<div id="divFailed" style="display:none">

                <img src="Image/failed.gif" />

</div>

<div id="divPassed" style="display:none">

                <img src="Image/passed.gif" />

</div>

[/code]

First div contains two drop down lists. One is ASP DropDownList control and its options are loaded from server side. And other one is normal html drop down list with options from the permissions table from Part 1.

The first ASP DropDownList control is pre-populated with following code

/**default.aspx.cs**/

//Add reference to web serivce proxy we created earlier
using SharePointPermissionsService;
//Add other references here
public partial class _Default : System.Web.UI.Page
{   
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
        /*Create web service instance*/
            Permissions p = new Permissions();
        /*make call to GetPermissionCollection method with site name "Sandbox" and type "Web"*/
            XmlNode node = p.GetPermissionCollection("Sandbox", "Web");
            using (XmlNodeReader reader = new XmlNodeReader(node))
            {
        /*load dataset from xmlreader*/
                DataSet ds = new DataSet();               
                ds.ReadXml(reader);
        /*data binding*/
                ddlUserGroup.DataSource = ds.Tables[1];
                ddlUserGroup.DataTextField = "GroupName"; //bind display text to GroupName
                ddlUserGroup.DataValueField = "Mask";  // bind option value to Mask value
                ddlUserGroup.DataBind();
            }
        }
    }
}

Remember, you must make sure the user running the context has permission to access SharePoint web service. Or you can use

p.Credentials = <create new credential here> to call web service with specific credential in the code above.

GetPermissionCollection returns data in following format

[code:xml]
    <Permissions>
        <Permission
            MemberID="3"
            Mask="138612801"
            MemberIsUser="False"
            MemberGlobal="True"
            GroupName="Viewers"
        />
        <Permission
            MemberID="4"
            Mask="1011028719"
            MemberIsUser="False"
            MemberGlobal="True"
            GroupName="Sandbox Members"
        />
        <!--.....More permissions here.....-->
        <Permission
            MemberID="13"
            Mask="134287360"
            MemberIsUser="False"
            MemberGlobal="True"
            GroupName="Quick Deploy Users"
        />
    </Permissions>
</GetPermissionCollection>

[/code]

Two drop down lists will be displayed as following

James Tsai .Net SharePoint Blog - User Group Name Drop Down List

James Tsai .Net SharePoint Blog - Permission Drop Down List

Create permission check function

Permission DDL has its onClick event registered with DoPermissionCheck() JavaScript function. DoPermissionCheck() is where you perform bitwise operation to compare user/group mask with each permission.

DoPermissionCheck() does four things

1. Get selected values of both DDL

2. Convert selected decimal value from ddlUserGroup DDL to Hex base16.

3. Get high and low masks from user mask and permission mask (in Hex base 16)

4. Perform bitwise AND operation on two high masks and two low masks.

If you look closer, you will find it has similar implementation to HasRights(), EqualRights(), SetCurrentPermMaskFromString() functions from CORE.JS in SharePoint. SharePoint uses these three JavaScript functions to check user permissions (like what we doing here) to display correct list item context menu for user.

/*default.aspx*/

function DoPermissionCheck()
{
        /*get first dropdownlist selected value*/
        var uSelectedIndex = document.getElementById("<%Response.Write(ddlUserGroup.ClientID);%>").selectedIndex
        var uSelectedValue = document.getElementById("<%Response.Write(ddlUserGroup.ClientID);%>").options[uSelectedIndex].value
        /*get second dropdownlist selected value*/
        var pSelectedIndex  = document.getElementById("ddlPermissionSet").selectedIndex
        var pSelectedValue = document.getElementById("ddlPermissionSet").options[pSelectedIndex].value           
        /*convert user mask to hex base 16 (use toString(16))*/
        var maskInDecimal = parseInt(uSelectedValue);
        var userP = maskInDecimal.toString(16);       
        var requiredP = pSelectedValue;
        /*get masks length*/
        var requiredPL = requiredP.length;
        var userPL = userP.length;            
        /*get high and low permisison mask    */
        var requiredPermMaskH=parseInt(requiredP.substring(2, requiredPL - 8), 16);
        var requiredPermMaskL=parseInt(requiredP.substring(requiredPL - 8, requiredPL), 16);           
        /*get high and low user/group mask*/
        var userPermMaskH;
        var userPermMaskL;
        if(userP.length <=10 )
        {
            userPermMaskH=0;
                userPermMaskL=parseInt(userP,16);
        }
        else
        {
                userPermMaskH=parseInt(userP.substring(2, userPL - 8), 16);
                userPermMaskL=parseInt(userP.substring(userPL - 8, userPL), 16);
        }                                     
        /*do bitwise AND operation*/
        if(((requiredPermMaskL & userPermMaskL)==requiredPermMaskL)
                && ((requiredPermMaskH & userPermMaskH)==requiredPermMaskH))
        {
                document.getElementById("divPassed").style.display = "";
                document.getElementById("divFailed").style.display = "none";
        }
        else
        {
                document.getElementById("divPassed").style.display = "none";
                document.getElementById("divFailed").style.display = "";
        }
}

Now you can select different user/group and permission to see the image change.

James Tsai .Net SharePoint Blog - User Has No Permission example

And of course you can implement above method in C# to perform permission check on server side. You can also extend this control to check user permissions on list level. (If your list does not inherit permissions from parent web site)

Just change web service call to GetPermissionCollection(<list name>, "List");

Easy, isn't it?

Currently rated 5.0 by 3 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , , , ,

Programming | SharePoint

SPRegionalSettings.GlobalTimeZones - How to build world clock / get time zones information in SharePoint

by James 8/20/2008 5:44:00 PM

Ok, you probably do not want to create world clock web part from scratch. Many free ones over Internet. Not to mention the very popular world clock web part from "bamboo Solutions".

But if you really want to create one by yourself for whatever the reason is, or you just want show the local time for employees working in different cities around the world (in their profile page, maybe). It can be done easily without to use any external web services, or hack your way through windows registry to get time zones list.

Yes, thanks to SharePoint OM. You can get all the information you want from Microsoft.SharePoint.dll

SPRegionalSettings.GlobalTimeZones

It returns you a collection of SPTimeZones objects used in Windows SharePoint Services.

SPTimeZoneCollection timeZoneColl = SPRegionalSettings.GlobalTimeZones;
foreach (SPTimeZone tz in timeZoneColl)
{
    DateTime currentLocalDateTime = DateTime.Now;
    DateTime currentDestDateTime = tz.UTCToLocalTime(currentLocalDateTime.ToUniversalTime());
        Console.WriteLine("ID: {0}, DateTime: {1}, Description: {2}", tz.ID, currentDestDateTime.ToString(), tz.Description);
}

In above code sample, it loop through each SPTimeZone inside SPTimeZoneCollection. Writes ID, current date time (in local server date time format) and description in console.

This is what you will get

James Tsai .Net SharePoint VSTO C# ASP.NET Blog - SPRegionalSettings GlobalTimeZones Code Sample

As you can see, my local time in Sydney, Australia is Thursday, August 21, 2008 4:01pm. And time in Tokyo, Japan is 08/21/2008 3:01pm. In Auckland, New Zealand is 08/21/2008 6:01pm. Which are correct!

//This line of code converts SPTimeZone object from UTC to your local time
DateTime currentDestDateTime = tz.UTCToLocalTime(currentLocalDateTime.ToUniversalTime());

Basically that's all you need to create world clock web part, or date time conversion control in SharePoint site. You can either have GlobalTimeZones collection as drop down list for user to select the time zone for displaying date time. Or you can create a SharePoint list mapping between cities name with time zone ID in GolbalTimeZones collection.

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , , , ,

Programming | SharePoint

How to query cross-site lists in DataFormWebPart - Part 3. Filtering on column headers problem

by James 8/12/2008 12:36:00 AM

Here are the links to previous two parts of the series

Part 1. Build your own data source for Data Form Web Part

Part 2. Use XSLT generated from SharePoint Designer to display data 

If you build a Data Form Web Part with customized data source (i.e. data source that does cross-sites querying), you've probably noticed the filtering on column headers does not work. In this part of customize Data Form Web Part series, you will see how this problem can be fixed by extend your custom Data Form Web Part code a bit more.

Before click on "Form Name" column header, there are two items displayed under it.

James Tsai .Net Blog - SharePoint C# ASP.NET VSTO -  Data Form Web Part with cross-sites query data source displayed

After click on "Form Name" column header, no available filter data returned.

James Tsai .Net Blog - SharePoint C# ASP.NET VSTO -  Cross-sites query data source with filtering on column headers problem

The Problem

Before we going into how to fix it. You must first understand what is causing this problem. And the answer simple. Because with the customized data source you just created, the original Data From Web Part methods that used to handle filtering are no longer to be able to perform filtering and render filter value correctly.

You will see how it works behind the scene from the following three methods. These methods are called after you clicked on any filterable column header.


/*Microsoft.SharePoint.WebPartPages.DataFormWebPart*/
protected virtual void RaiseCallBackEvent(string eventArgument){};
protected virtual string GetCallbackResult(){};
private string DoCallBackFilters(string filterStr){};

 

RaiseCallBackEvent

This method assigns event argument to the web part (Data Form Web Part in this example) after callback event has been raised.

Event argument is a string variable represent in following format: (i.e. clicked on "Title" column header)

"__filter={Form Name @FormName x:string;1033 g_548009f0_beeb_4e3c_a4a6_71fc338cc8cb}"

As you can see, it contains column name and web part ClientID - g_548009f0_beeb_4e3c_a4a6_71fc338cc8cb.

 

GetCallbackResult

After RaiseCallBackEvent, this method gets called. It first check if raised callback event is for filtering event on column headers. If so, it strips out the filtering string

"Form Name @FormName x:string;1033 g_548009f0_beeb_4e3c_a4a6_71fc338cc8cb"

And pass it to DoCallBackFilters as an input argument.

 

DoCallBackFilters

DoCallBackFIlters prepares callback result that will be used for rendering filtering drop down list on column header. The correct returned string should look like this:

<SELECT>
<OPTION href="__doPostBack('g_548009f0_beeb_4e3c_a4a6_71fc338cc8cb','NotUTF8;__filter={@FormName=##dvt_all##}')"></OPTION>
<OPTION href="__doPostBack('g_548009f0_beeb_4e3c_a4a6_71fc338cc8cb','NotUTF8;__filter={@FormName=Temp page}')">Temp page</OPTION>
<OPTION href="__doPostBack('g_548009f0_beeb_4e3c_a4a6_71fc338cc8cb','NotUTF8;__filter={@FormName=Test Page}')">Test Page</OPTION>
</SELECT>

Note! This is where you get empty result when you clicked on column header. Because this method cannot process your customized cross-sites query data source, it returned empty string instead to GetCallbackResult.
James Tsai .Net Blog - SharePoint C# ASP.NET VSTO -  GetCallbackResult method failed on return correct data

 

Solution

DoCallBackFilters is a private method and there is no way to extend it to make it work the way you wanted. The only option here is to override GetCallbackResult method and make it to call new DoCallbackFilters replacement method. (CustomizedDoCallBackFIlters in this example)

/*Original implementation of GetCallbackResult*/

public virtual string GetCallbackResult()
{
    if (string.Compare(this.eventArgument, 0, "__filter", 0, 8, false, CultureInfo.InvariantCulture) != 0)
    {
        return string.Empty;
    }
    string filterStr = this.eventArgument.Substring(10, this.eventArgument.Length - 11);
    return this.DoCallBackFilters(filterStr);
}

Override above method with following implementation.


/*Override implementation of GetCallbackResult*/
public virtual string GetCallbackResult()
{
    //This is where it make sure event raised from filtering on column headers
    if (string.Compare(this.eventArgument, 0, "__filter", 0, 8, false, CultureInfo.InvariantCulture) != 0)
    {
        return string.Empty;
    }
    string filterStr = this.eventArgument.Substring(10, this.eventArgument.Length - 11);
    return this.CustomizedDoCallBackFilters(filterStr);
}
private string CustomizedDoCallBackFilters(string filterStr)
{
    string yourDataAsString;
    //Build your customized filtering data here. To return data in format described above
    return yourDataAsString;
}

Inside CustomizedDoCallBackFilters method you can build result data with any logic you like. As long as it is in correct format. I suggest you to follow the same logic of how you've built your data source. To make sure data displayed in column headers always consistent with actual displayed items in Data From Web Part.

James Tsai .Net Blog - SharePoint C# ASP.NET VSTO -  Correct data returned from GetCallbackResult method 

More Details

I will explain what should your final result data look like. As you can see in above, it follows this format:

[code:xml]

<SELECT>
<OPTION href="__doPostBack('g_548009f0_beeb_4e3c_a4a6_71fc338cc8cb','NotUTF8;__filter={@FormName=##dvt_all##}')"></OPTION>
<OPTION href="__doPostBack('g_548009f0_beeb_4e3c_a4a6_71fc338cc8cb','NotUTF8;__filter={@FormName=Temp page}')">Temp page</OPTION>
<OPTION href="__doPostBack('g_548009f0_beeb_4e3c_a4a6_71fc338cc8cb','NotUTF8;__filter={@FormName=Test Page}')">Test Page</OPTION>
</SELECT>

[/code]

Each <OPTION/> element inside <SELECT/> has only one attribute "herf". Except first <OPTION/> element served special purpose and I will explain it later.

But first let's see the break down to the "herf" attribute value

James Tsai .Net Blog - SharePoint C# ASP.NET VSTO - Correct result format explained

Only difference in each line are the values used to display on the UI drop down list and used for filtering PostBack result.

The first <OPTION/> is used to indicate whether filtering has been done on current column. To toggle the flag you just need to assign 1 to this <OPTION/> element

[code:xml]

<OPTION href="__doPostBack('g_548009f0_beeb_4e3c_a4a6_71fc338cc8cb','NotUTF8;__filter={@FormName=##dvt_all##}')">1</OPTION>

[/code]

James Tsai .Net Blog - SharePoint C# ASP.NET VSTO - How to use first element in returned result to toggle filtering on column header flag

 

 

That's all. It is pretty much how you fix filtering on column header problem. Just remember to return your final result data in GetCallbackResult().

In summary.

1. Get Callback argument with all the necessary data (field name and control client id).

2. Use above input argument to build result data using any code logic you like.

3. return data in GetCallbackResult() method.

Hope this post helps you to get what you want.

 

James

Currently rated 5.0 by 3 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , , ,

Programming | SharePoint

(CodePlex) MOSS Faceted Search - HTML encode/decode problem fixed!

by James 6/23/2008 6:38:00 PM

MOSS Faceted Search is a great SharePoint search extension project on CodePlex. It provides following features (copy from its CodePlex site)

  • Grouping search results by facet
  • Displaying a total number of hits per facet value
  • Refining search results by facet value
  • Update of the facet menu based on refined search criteria
  • Displaying of the search criteria in a Bread Crumbs
  • Ability to exclude the chosen facet from the search criteria
  • Flexibility of the Faceted search configuration and its consistency with MOSS administration

However, there are still some known issues exist due to this project does not update and release regularly.

HTML Encoding

An issue I experienced was HTML string encoding/decoding problem. After I added faceted search web part to search result page and searched for something, it returns result like following.

 

James Tsai .Net VSTO SharePoint Blog MOSS Faceted Search problem 

 

As you can see first facet value in both "Refine by Title" and "Refine by role" sections are empty. Only number of hits displayed. And it is because Faceted web part does not perform HTML encode/decode properly. It can be easily spotted by closer inspection of page's HTML source.

[code:html]

<!-- rendered html of facet web part -->


<td nowrap="nowrap" width="100%" style="vertical-align:middle"><a class="ms-navitem" href="javascript:__doPostBack(...)" style="vertical-align:middle;"><No Title></a><span> (2)</span></td>


<td nowrap="nowrap" width="100%" style="vertical-align:middle"><a title="&amp;lt;enter position title>" class="ms-navitem" href="javascript:__doPostBack(...)",style="vertical-align:middle;"><No Position defi...&lt;/a><span> (1)</span></td>

[/code]

First empty facet value is causing by "<No Title>" rendered as html tag, which is not recognized by browser. And the second empty facet value is with half-open "<No Position defi..." also treated as malformed html tag.

To fix this problem, you have to modify Common\Templates.cs source code file. perform HttpUtility.HtmlEncode  for facet.DisplayName before assign it as link.Text

 

//Templates.cs source code

void link_DataBinding(object sender, EventArgs e)
{
            /******irrelevant code omitted*******/     
           

            if (facet.DisplayName.Length > _cropMax)
            {
                link.ToolTip = facet.Name;

                //HtmlEncode facet display name
                link.Text = HttpUtility.HtmlEncode(facet.DisplayName.Remove(_cropMax)) + "...";
            }
            else link.Text = HttpUtility.HtmlEncode(facet.DisplayName);  //HtmlEncode here too

            /******irrelevant code omitted*******/   
}

After you re-deploy fixed version of the feature and web part, the search result should now look like

 

James Tsai .Net VSTO SharePoint Blog MOSS Faceted Search fix encoding problem

 

Everything look fine now? If you now click on the link you just fixed as followed above steps. You will see this in returned refine search result page.

 

James Tsai .Net VSTO SharePoint Blog MOSS Faceted Search decoding problem

 

HTML Decoding

 

Yes, the problem is obvious. Whatever we encoded, we must decode it. Otherwise Faceted search web part will pass encoded query value to do refine search. And it will not return correct result (or no result at all, since encoded string will not look pretty).

To fix this problem you will need to modify Common\Utility.cs source code file. Do HttpUtility.HtmlDecode for query.Properties[property] before add it to keywords ArrayList.

 

public static string BuildQueryString(SearchQuery query)
{
             /******irrelevant code omitted*******/  

            foreach (string property in query.Properties)
            {
                // consider empty facets and don't add them to qs
                if (query.Properties[property] == Common.Constants.UNNAMED_FACET_VALUE) continue;
                ka.Add(string.Format("{0}:\"{1}\"",property, HttpUtility.HtmlDecode(query.Properties[property])));
            }

             /******irrelevant code omitted*******/  

            return qs;
}

 

If you followed all above steps you should now see correct refined search keyword and results.

 

James Tsai .Net VSTO SharePoint Blog MOSS Faceted Search decoding problem fix

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

Programming | SharePoint

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen Adopted by James Tsai

About the author

Name of author James Tsai
.NET / SharePoint Consultant
Columbus, OH

E-mail me Send mail

Calendar

<<  September 2010  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910
View posts in large calendar

Certifications

MCPD
MCTS

Recent comments

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010

Sign in