Kniganapolke Tech Blog

Hello guys

I’m building a web site with Orchard 1.9.2. There is Localization module available that enables you to add Localization Part to any content type to make it localizable.

As for user locale selection Orchard renders a link to available translations for each applicable content item (that is localizable and has translations). But if you want a global locale selector that would change all translated content including menus this locale selector doesn’t do the job.

So, I managed to create a widget that renders links to available translations of the whole page. The code takes URL of the page and tries to retrieve content item that is bound to it. And then builds links to this content item’s translations.

The nice part is that menu items that point to localizable content items (pages, projection etc.) are localized automatically. If such a menu item does not have the current translation it is excluded from menu.

Let me admit that my web site is quite simple and I’ve assembled this solution just today. So I’m sure it isn’t perfect for some specific scenarios.

Here are the steps I’ve accomplished:

1. Create a new content type (creatable, listable) with just Common and Widget content parts. Let it be LocaleSelector, for example.
2. Add Widget stereotype to it.
3. Add a template for this content type to your theme (for ex, Widget-LocaleSelector.cshtml).
4. Add references to Orchard.Alias and Orchard.Localization projects to your theme’s project.
5. Add the following code to the template:

@using System.Globalization 
@using Orchard.Alias 
@using Orchard.ContentManagement 
@using Orchard.Localization.Services 
@using Orchard.Localization.Models

@{ 
 var localizationService = WorkContext.Resolve<ILocalizationService>(); 
 var aliasService = WorkContext.Resolve<IAliasService>(); 
 var contentManager = WorkContext.Resolve<IContentManager>(); 
 var cultureManager = WorkContext.Resolve<ICultureManager>();

 string path = Request.Url.PathAndQuery; 
 IContent content = GetByPath(path, aliasService, contentManager); 
}

@if (content != null)
 {
  IEnumerable<LocalizationPart> localizations = localizationService.GetLocalizations(content);

  
@foreach (var l in localizations) { var localized = content.As(); string culture = l.Culture.Culture; if(localized.MasterContentItem != null) { content = localized.MasterContentItem.ContentItem; } var localizedContentItem = localizationService.GetLocalizedContentItem(content, culture).ContentItem; var cultureInfo = CultureInfo.GetCultureInfo(l.Culture.Culture);   }
} @functions{ public IContent GetByPath(string path, IAliasService aliasService, IContentManager contentManager) { if (path == null || aliasService == null || contentManager == null) { return null; } string aliasPath = HttpUtility.UrlDecode(path); aliasPath = aliasPath.TrimStart(new char[] { '/' }); var contentRouting = aliasService.Get(aliasPath); if (contentRouting != null) { object id; if (contentRouting.TryGetValue("id", out id)) { int contentId; if (int.TryParse(id as string, out contentId)) { return contentManager.Get(contentId); } } } return null; } }

After that you’ll be able to add the widget easily through Orchard admin interface in any zone (the widget will be available in the list). Also you can modify the HTML and add styles to make the locale selector look as you need.

In my previous posts I showed how to authorize to a Sharepoint server and call it’s web services.
In my particular case I had to pull some lists from Sharepoint to show them on a web form. Authorization call + 3 WS calls took about 7 seconds to complete. So I decided to cache the lists to save 4 calls and to provide a separate form to update the lists.
The following code snippet serializes / deserializes a php array to / from a file.


// Call Sharepoint WS to het a list
/* array */ $result = $client->call("GetListItems", 
array("listName" => $list_guid, "viewName" => $view_guid,));
if($client->fault || !result){
	// Handle error
	return false;
}
// You may want to process the result for example to pick out only necessary fields
$data_array = processResult( /* array */ $result );
// Serialize data, put it to a file
file_put_contents($fileName, serialize($data_array), FILE_USE_INCLUDE_PATH);
	
// Deserialize data from a file into php array
$srlzd = file_get_contents($filePath, FILE_USE_INCLUDE_PATH);
$data_array = ($srlzd) ? unserialize($srlzd) : null;

I use Nusoap to call the web service.

In the previous post I showed how to authorize through a web form. We get a cookie among the response headers in case of successfull authorization. This post shows how to use the authorization cookie or use the NTLM (Windows) authentication to make calls to Sharepoint web services.

soap_defencoding = "UTF-8"; 
$err = $client->getError(); 
if ($err) { 
 // Handle error return false; 
} 
// To authorize to a Sharepoint server that uses NTLM (Windows) authentication scheme from the same LAN 
//$client->setCredentials($user, $pass, "ntlm");

// $cookie - received after authorization through a web form
$client->setCurlOption(CURLOPT_COOKIE, $cookie);
$client->setCurlOption(CURLOPT_SSL_VERIFYPEER, FALSE);
$client->setCurlOption(CURLOPT_SSL_VERIFYHOST, FALSE);

$client->setCurlOption(CURLOPT_TIMEOUT, 25);	// To avoid total page timeout

// Sharepoint fields' names
$field1_name = "ABC 123";
$field2_name = "DEF 456";
// Fields values
$field1_value = "ABC 123";
$field2_value = "DEF 456";

// $list_guid - find out the GUID of the target Sparepoint list
// $view_guid - find out the GUID of the target Sparepoint view

$newField = '
<UpdateListItems xmlns="http://schemas.microsoft.com/sharepoint/soap/">
<listName>{'.$list_guid.'}</listName>
<updates>
<Batch OnError="Continue" ListVersion="1" ViewName="{'.$view_guid.'}">
<Method ID="1" Cmd="New">
<Field Name="'. $field1_name .'">'. $field1_value .'</Field>
<Field Name="'. $field2_name .'">'. $field2_value .'</Field>
</Method>
</Batch>
</updates>
</UpdateListItems>';

$result = $client->call("UpdateListItems", $newField);

if ($client->fault){
// Handle error
return false;
}
?>

I use Nusoap classes to call Sharepoint web services to add a new element to a list in this code snippet.

I had to make a php based site interchange some information with a MS Sharepoint based site, moreover the latter was secured by a proxy-server and used SSL (https).

The first step was to pass authorization on proxy-server, which was supposed to be done through a special authorization form.
Using Firebug add-on for Firefox (or Fiddler for FF and IE) I watched the POST request headers and fields sent when submitting authorization form manually.

So I needed to make the same POST request with credentials from php script.

Moreover, a cookie was set after authorization (either fault or successfull) which we need to store and use for following requests.
This can be done with curl (libcurl library for php).

$ch = curl_init();
// $authorization_url - URL what to post credentials to, for example "https://myhost/login"
curl_setopt($ch, CURLOPT_URL, $authorization_url);

curl_setopt ($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt ($ch, CURLOPT_REFERER, 'set your referer here');

// Do not follow "Location:" header
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);

// To use SSL protocol without verifying or providing any certificates
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Avoid "Expect: 100-continue" header
curl_setopt ($ch, CURLOPT_HTTPHEADER, array('Expect:'));

// Indicate it's a POST request
curl_setopt ($ch, CURLOPT_POST, 1);

// Prepare POST fields
$postfields  = 'Field1='.urlencode('field 1 value');
$postfields .= '&Field2='.urlencode('field 2 value');
$postfields .= '&Field3='.urlencode('field 3 value');
curl_setopt ($ch, CURLOPT_POSTFIELDS, $postfields);

// For debugging purposes
// return transfer data as a result
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// read headers
curl_setopt($ch, CURLOPT_HEADER, 1);
// show request headers, will display all the outgoing info, including fields
curl_setopt($ch, CURLINFO_HEADER_OUT, true);

// Make the request
$data = curl_exec($ch);
$errors = curl_error($ch)
// get info about the transfer, for debugging purposes
$details = curl_getinfo($ch); 

curl_close($ch);

// Displaying debugging info
//var_dump($data);
//var_dump($errors);
//var_dump($details);

// Parse data for cookie header, store cookie as it will be used for subsequent requests
$cookie = '';
$pattern = '/Set-Cookie:(.*?)\n/';
if (preg_match($pattern, $data, $result))
$cookie = $result[1];

Setting “CURLINFO_HEADER_OUT” option to true seems to be the only way to see the outgoing request headers and POST data.

Then we can make a GET request to ask for a resource (a page, for example). Send authorization cookie using “CURLOPT_COOKIE” option.

$ch = curl_init();
// Set URL of the target resource (for example, https://myserver/targetresource)
curl_setopt($ch, CURLOPT_URL, $resource_url);

curl_setopt ($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt ($ch, CURLOPT_REFERER, 'set your referer here');

// Do not follow "Location:" header
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

curl_setopt ($ch, CURLOPT_HTTPHEADER, array('Expect:'));

// Set authorization cookie that we got as a result of our first request
curl_setopt($ch, CURLOPT_COOKIE, $cookie);

// For debugging purposes
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);

$data = curl_exec($ch);
$errors = curl_error($ch)
// get info about the transfer, for debugging purposes
$details = curl_getinfo($ch);

// Displaying debugging info
//var_dump($data);
//var_dump($errors);
//var_dump($details);

curl_close($ch);

Links that helped:

I decided to start my own tech blog as I have almost succeeded with my task – sending data from php-based site to a sharepoint-based site by means of web services.  I came across some troubles with authorization and debugging, so I’ll post some usefull links and code snippets.


  • kniganapolke: Happy to hear that )))
  • Khuong: You save my life!!! I have been dealing with this for a whole week! When I was about to commit a suicide, you showed up and saved my life!!! Tha
  • kniganapolke: Thanks, your code seems to be more generic.

Categories