27
Jun

Error when provisioning a Project Workspace

Project Server splits project data across two areas: Project Server holds the plan data (Tasks, Milestones) & resourcing data (Resources, Timesheets), and then SharePoint (WSS or MOSS) holds the Risks, Issues, Documents and other custom data.

When a project is initially published in Project Server, PWA (Project Web Access) will attempt to provision a new SharePoint subweb. This means Project Server creates a Project Workspace in SharePoint.

The problem I’m witnessing? That even manually creating the workspace results in a single dialog box in which the only text is “Error”.

Continue reading ‘Error when provisioning a Project Workspace’

18
Jun

The Banking Code of Conduct and liability for internet banking security

It seems that in March 2008 the voluntary banking code that banks in the UK comply with was changed.

Part of this change includes this paragraph:

Online banking
12.9 Online banking is safe and convenient as long as you take a number of simple precautions. Please make sure you follow the advice given below.
* Keep your PC secure. Use up-to-date anti-virus and spyware software and a personal firewall.

And a little further down is this:

Liability for losses
12.11 If you act fraudulently, you will be responsible for all losses on your account. If you act without reasonable care, and this causes losses, you may be responsible for them. (This may apply, for example, if you do not follow section 12.5 or 12.9 or you do not keep to your account’s terms and conditions.)

And there it is in black and white, if you do not follow the advice from the banks then you have not taken ‘reasonable care’ and any losses that you suffer will not be protected by the bank.

Continue reading ‘The Banking Code of Conduct and liability for internet banking security’

17
Jun

Workflow in SharePoint Project Server templates

Apparently this isn’t as simple as it should be.

The environment:

Project Server 2007 with SharePoint v3. Each Project has a SharePoint subweb as a “Project Workspace”. The Project workspaces are provisioned automatically by Project Server. Project Server applies a template to the new workspaces. The template can be customised but must be derived from the one Project Server originally shipped.

The scenario:

Put workflow into the template so that each provisioned Project workspace starts with a number of pre-defined processes

Shouldn’t be hard right? Well, it was.

Continue reading ‘Workflow in SharePoint Project Server templates’

10
Jun

Visual Studio 2008 Extension for SharePoint v3 (WSS and MOSS)

Finally this has been released.

And it passed me by totally.

So much so, that I emailed Alex Malek over at Microsoft to badger him to let me have a copy of the unfinished product so that I could get something done for a rather important client, only to have him respond with the public download URL… doh!

So this blog post is really just to ping the search engines out there in the hope that those who are looking can find. Karma-whore… for sure.

Continue reading ‘Visual Studio 2008 Extension for SharePoint v3 (WSS and MOSS)’

17
Aug

mod_rewrite + mod_proxy + spaces in URI = boom!

So I have a piece of .htaccess magic on one of the sites that I run.

What this particular set of instructions do is allow me to run a second web server of a different version behind my Apache 1.3 installation, and still have it appear to the end user as if I only run one web server, basically it’s a proxy. I like this, this is good.

The .htaccess rule I setup is for Trac and specifically the tracd daemon, and my rules look like this:
RewriteEngine On
RewriteRule ^trac_common/(.*)$ http://127.0.0.1:8080/trac_common/$1 [P]
RewriteRule ^projects/?(.*)$ http://127.0.0.1:8080/$1 [P]
RewriteRule ^trac(.*)$ http://127.0.0.1:8080/trac$1 [P]

The [P] at the end of those mod_rewrite lines is telling mod_rewrite to use mod_proxy to handle the request and forward the request internally to the web server running on the high port.

Nice and simple, if a request comes in which has the url /projects/ go to the top Trac page.
If a request comes in for the project named trac with the url /trac/ go to that projects’ Trac site.

This works OK, except that the code browser within Trac is accessing the Subversion repository, and within SVN some of the directory paths and files have spaces in them.

What happens is that under the hood a request ends up looking like this:
GET /trac/browser/root/path with spaces in HTTP/1.1

That’s wrong as only two spaces can exist in a GET request, and those spaces delimit the request type, request URI and the protocol.

So somewhere within mod_rewrite and mod_proxy the spaces are not being escaped as they should, because the request should look like this:
GET /trac/browser/root/path%20with%20spaces%20in HTTP/1.1

So, how do you go about replacing spaces using mod_rewrite?

Much googling will tell you that mod_rewrite does not allow you to perform regexp or string replacements.

However, you don’t need to. mod_rewrite already has a RewriteMap feature and we can use that.

If we take our rewrite rules out of the .htaccess file and place them in our httpd.conf file we can use the built-in escape function:
RewriteMap escape int:escape
RewriteEngine On
RewriteRule ^/trac_common/(.*)$ http://127.0.0.1:8080/trac_common/${escape:$1} [P]
RewriteRule ^/projects/?(.*)$ http://127.0.0.1:8080/${escape:$1} [P]
RewriteRule ^/trac(.*)$ http://127.0.0.1:8080/trac${escape:$1} [P]

That now correctly escapes spaces in the matched URL’s and the source browser connected to SVN now works perfectly.

Hope this saves someone else some time.

26
Feb

SharePoint SPListItem quirks

There are some funny things going on inside SharePoint, and this is one of the funniest…

We have a list, with two fields:

<Field
  Type="Text"
  DisplayName="Category"
  MaxLength="255"
  Name="Category0"
  ColName="nvarchar17"
/>
<Field
  Name="Category"
  FromBaseType="TRUE"
  Type="Choice"
  DisplayName="Category (deprecated)"
  Format="Dropdown"
  FillInChoice="FALSE"
  ColName="nvarchar2"
><!-- CHOICES --></Field>

Note that one has the DisplayName of ‘Category’ and the other has the Name of ‘Category’.

The one with the Name ‘Category’ is deprecated, and we actually want to populate the field with the DisplayName ‘Category’.

You’d expect this would be fine… so consider that we have an empty list and that we create a new item and try and populate the item:

SPListItem newListItem = spList.Items.Add();
newListItem["Title"] = “The title”;
newListItem["Category"] = “Category 1″;
newListItem.Update();

That should work right? I mean MSDN very plainly states that on a SPListItem that you access a field using the DisplayName of the SPField that you want to set the value against.

It tries to populate the wrong field.

Why? Because it seems that if a SPField exists that has an InternalName the same as a DisplayName, then that SPField takes priority. As the Name property of a SPField *is* the InternalName, we’ve actually just attempted to assign the string “Category 1″ to the Choice Field that is “Category (deprecated)”.

A very subtle but pain in the arse thing that.

So how to get around it? Well, instead of passing in the DisplayName you can use the overloaded version of spListItem[] to pass in the int32 index of the SPField in the spList.Fields collection.

So if you loop the spList.Fields collection, work out where the real “Category” field is that we want to update, and grab the index… you’d expect to be able to update the list item right?

Right, but… not if the item you are creating is the first item to ever go into the list. It will work for every subsequent item, but never the first item.

For some reason the index based accessors only work after an item exists, so if we go back to that original piece of code and update it with the Field index:

SPListItem newListItem = spList.Items.Add();
newListItem["Title"] = “The title”;
newListItem[2] = “Category 1″;
newListItem.Update();

That won’t work.

Instead you have to save the item, and then grab a fresh instance of the item and save that:

SPListItem newListItem = spList.Items.Add();
newListItem["Title"] = “The title”;
newListItem.Update();

SPListItem existingListItem = spList.GetItemByID(newListItem.ID);
existingListItem[2] = “Category 1″;
existingListItem.Update();

And that piece of code works.

The rules here:

  • You can’t set a value to a field using the DisplayName if another field exists with a matching Name / InternalName.
  • You can’t use the index based accessors until an item exists within the SharePoint list.

This probably won’t matter to the majority of SharePoint developers, but to those who encounter either of the above, it’s really worth noting them down… close to three days we lost on this.

Credit to Louise B for discovering through much pain the first part of this two-part bug.

26
Dec

Why hasn’t on-line banking experienced a Web 2.0 overhaul?

I was recently contacted by American Express as part of a market research thing.

The market research involved several members of the market research company interviewing me with a film crew present, and some other people who I guess were employees of either AMEX or the market research company witnessing proceedings.

Continue reading ‘Why hasn’t on-line banking experienced a Web 2.0 overhaul?’

26
Dec

Is there such a thing as a good online bicycle shop?

And what do I even mean by that?

Well what I’ve been longing for is an on-line store that I can use for the majority of my purchases. The problem I seem to have here is that my purchases are so varied. I also suffer because I am fussy about which sites to use as they have to be designed well and have high usability in mind.

So, what on earth am I looking for that I feel it doesn’t exist?

Continue reading ‘Is there such a thing as a good online bicycle shop?’

14
Dec

I’m in love with the Flickr API now they’ve added JSON

The Flickr web services were great, but just a little annoying in that to make them work in a web page I would always have to create wrappers on the server to perform the web service calls. I had to do this as if you’re in JavaScript you have no permission to perform a cross-domain XmlHttp call, and because if you’re not doing it in JavaScript you were on the server anyway.

The two biggest reasons I disliked this are that it added load to my server, and it added a dependency on the responsiveness of my server on the responsiveness of the Flickr servers.

That is now a thing of the past.

JSON is JavaScript Object Notation. It is a way of encapsulating within JavaScript a hierarchical object model and of populating that with properties, values and content.

It is documented here: http://www.json.org/ and is simply a joy to use.

All you need do is add a HTML script tag to your page with the SRC of that being the JSON web service that you wish to call, once the web service returns you have a JavaScript object that you can act on.

Because you can pull in JavaScript from across domains, this works across domains.

So I played around with it for half an hour last night and knocked out a demo. What the demo does is takes a username of a Flickr user (I’m “the boy on the bike”), looks up their Flickr Id, and then queries the most recent public photographs for that user and renders the thumbnails of them.

Here is a demonstration (right click to download the source).

Here is the code that I used, remember to change the flickApiKey to use it on your site:

<html>
<head>
</head>
<body>
<script type="text/javascript">
var totalThumbs = 16;
var thumbsPerRow = 4;
var flickrApiKey = '73cd571dac7cb8390a8e848ed7ab5c09';
var flickrUsername = '';
function jsonFlickrApi(rsp) {
	if (rsp.stat != "ok") {
		document.getElementById('flickrContainer').appendChild(
			document.createTextNode('Flickr Error: ' + rsp.message)
		);
		return;
	} else if (rsp.user) {
		var flickrPhotoScript = document.createElement('script');
		flickrPhotoScript.type = 'text/javascript';
		flickrPhotoScript.src =
			'http://www.flickr.com/services/rest/?format=json&api_key=' + flickrApiKey +
				'&method=flickr.people.getPublicPhotos&per_page=' + totalThumbs +
				'&user_id=' + rsp.user.id;
		flickrUsername = rsp.user.username._content;
		document.getElementById('flickrContainer').appendChild(flickrPhotoScript);
	} else if (rsp.photos) {
		for (var ii = 0; ii < rsp.photos.photo.length; ii++) {
			var photo = rsp.photos.photo[ii];
			if (!ii) {
				var userAnchor = document.createElement(’a');
				userAnchor.href = ‘http://www.flickr.com/photos/’ + photo.owner + ‘/’;
				userAnchor.appendChild(document.createTextNode(flickrUsername));
				document.getElementById(’flickrContainer’).appendChild(
					document.createTextNode(totalThumbs + ‘ Most recent photos from ‘)
				);
				document.getElementById(’flickrContainer’).appendChild(userAnchor);
				document.getElementById(’flickrContainer’).appendChild(
					document.createTextNode(’.')
				);
				document.getElementById(’flickrContainer’).appendChild(
					document.createElement(’br’)
				);
			}
			var anchor = document.createElement(’a');
			anchor.href = ‘http://www.flickr.com/photos/’ + photo.owner + ‘/’ + photo.id + ‘/’;
			var img = document.createElement(’img’);
			img.height = 75;
			img.width = 75;
			img.border = 0;
			img.alt = photo.title;
			img.src = ‘http://static.flickr.com/’ + photo.server + ‘/’ + photo.id + ‘_’ + photo.secret + ‘_s.jpg’;
			anchor.appendChild(img);
			document.getElementById(’flickrContainer’).appendChild(anchor);
			if (ii % thumbsPerRow == thumbsPerRow - 1) {
				document.getElementById(’flickrContainer’).appendChild(
					document.createElement(’br’)
				);
			}
		}
	}
}
function flickrLoad() {
	var flickrUserScript = document.createElement(’script’);
	flickrUserScript.type = ‘text/javascript’;
	flickrUserScript.src = ‘http://www.flickr.com/services/rest/?format=json&api_key=’ + flickrApiKey +
		‘&method=flickr.people.findByUsername&username=’ + escape(’the boy on the bike’);
	document.getElementById(’flickrContainer’).appendChild(flickrUserScript);
}
if (document.attachEvent) {
	window.attachEvent(’onload’, flickrLoad);
} else if (document.addEventListener) {
	window.addEventListener(’load’, flickrLoad, false);
}
</script>
<div id=’flickrContainer’ class=’smallfont’></div>
</body>
</html>
21
Nov

Booleans in Googlemail Filters

Just a quick tip, if you’re a heavy user of Gmail then you probably have quite a few labels setup, and also a fair few filters.

A lot of my filters were very similar:

Matches: from:(friend1@example.com)
Do this: Apply label "Friends"

Matches: from:(friend2@example.com)
Do this: Apply label "Friends"

You can end up with a confusing number of filters this way. But there is help, Google have a little known about power-user feature in that the filters can use Boolean search syntax to combine multiple clauses within one pattern.

Matches: from:(friend1@example.com OR friend2@example.com)
Do this: Apply label "Friends"

Using Booleans you can reduce the number of filters that you need to create and maintain to the bare minimum. One filter to label all friends, rather than one filter per friend.

It’s possible to get pretty clever with combining several types of query in one:

Matches: from:(subscription1@example.com) OR to:(mailinglist1@example.com) OR subject:(Mailing List Name)
Do this: Apply label "Subscriptions"

The trickery here is how to enter that query into Gmail given that Gmail doesn’t give you an interface to enter advanced filters. The answer is to recognise that when you fill in a field in the filter form the filter automatically has the field name and brackets wrapped around it.

So… if you took the above example and entered the “from” field, anything you enter in that field will be surrounded by “from:(” and “)”. With that in mind, you would enter the following in the “from” field providing your filter started with a “from” criteria:

subscription1@example.com) OR to:(mailinglist1@example.com) OR subject:(Mailing List Name

Now when you click “Test Search”, Gmail wraps the above in “from:(” and “)” and completes the syntax to make the search:

from:(subscription1@example.com) OR to:(mailinglist1@example.com) OR subject:(Mailing List Name)

Which means you now have multi-field filters and boolean searches.

The full guide to advanced operators is available here on the Gmail help site:
http://mail.google.com/support/bin/answer.py?answer=7190

With the above tips you should be able to perform most queries you could imagine.

Give it a try :)