// ==UserScript==
// @name      Flickr Photo Stream In Situ Permissions and Licensing
// @namespace   http://mrjoro.org/flickr/
// @description   Allows in situ settings of permissions and licensing on the flickr photo stream page without needing to jump to a new page.
// @include     http://www.flickr.com/photos/*
// @include     http://flickr.com/photos/*
// ==/UserScript==

// Version 0.13
// November 8, 2005
// Joseph Rozier
// http://mrjoro.org

// TO CHANGE THE COMMENT/METADATA PERMISSIONS, CHANGE THE perm_comment AND perm_addmeta VARS BELOW

// History
// 0.13 (November 8, 2005); now set comments/meta permissions to recommended levels instead of "only you"
// 0.12 (October 31, 2005); fixed typo that kept permissions change from working
// 0.11 (October 29, 2005): fixed post URL so that it will work with www.flickr.com as well as flickr.com, reset boxes after selection
// 0.10 (October 29, 2005): Original

// This work is licensed under the Creative Commons
// Attribution-NonCommercial License. To view a copy
// of this license, visit 
// http://creativecommons.org/licenses/by-nc/2.5/
// or send a letter to 
// Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.

(function()
{
	var lastRequestCompleted = true;

	// a "class" to hold information about each photo on the page
	function PhotoDescription()
	{
		// the anchor ("a" element) for the permissions
		this.permAnchor = null;
		
		// the anchor ("a" element) for the license type; this may
		// be null if there is no license type (i.e. if the
		// permissions is set to be private)
		this.licAnchor = null;
		
		// the flickr id for the photo
		this.photoId = null;
		
		// an element that will be used to provide in-situ status
		this.insituStatus = null;
	};
	
	function setInsituStatusText(photoDesc,statusText)
	{
		photoDesc.insituStatus.innerHTML = statusText;
		photoDesc.insituStatus.style.display = "block";
	}


	function clearInsituStatusText(photoDesc)
	{
		photoDesc.insituStatus.style.display = "none";
		photoDesc.insituStatus.innerHTML = "";
	};
	
	
	// this will hold the descriptions for all of the photos on the page
	var photoDescriptions = new Array();
	
	// terrible, terrible... but this is a global variable to hold the
	// "current" photo under consideration (since we use callbacks)
	var currentPhoto;
	

	// ---------------- PERMISSIONS -----------------
	
	// this will display the permissions popup in the correct location;
	// the permissions anchors are modified to call this method
	// when the user clicks on them
	window.mrjoro_displayPermissionsSelection = function(index)
	{
		if(!lastRequestCompleted)
		{
			return;
		}
		
		lastRequestCompleted = false;
		
		currentPhoto = photoDescriptions[index];

		var left = getActualLeft(currentPhoto.permAnchor);
		var top = getActualTop(currentPhoto.permAnchor);

		permBox.setAttribute("style","position:absolute; border: 1px solid black; color:gray; top:" + top + "px;left:" + left + "px;");
		permBox.style.display = "block";
	};

	// this will just hide the permissions box; note the cancel
	// button in the permission popup will call this method
	window.mrjoro_cancelPermissionsSelection = function()
	{
		permBox.style.display = "none";
		mrjoro_resetPermissionSelection();
		lastRequestCompleted = true;
	};
	
	window.mrjoro_resetPermissionsSelection = function()
	{
		document.getElementById("permTypeSelect").value = -1;		
	}

	
	// this is called whenever the user has made a selection
	// in the permissions drop-down; note that currentPhoto
	// was set already
	window.mrjoro_requestToModifyPermissions = function()
	{
	

		// first, hide the box
		permBox.style.display = "none";
			
		// the permTypeSelect element was created in the
		// innerHTML above, and it will contain the
		// selection the user made in the drop-down box
		var permType = document.getElementById("permTypeSelect").value;

		mrjoro_resetPermissionsSelection();

		var is_public=0;
		var is_private=0;
		var is_friend=0;
		var is_family=0;
		
		if(permType == 0)
		{
			is_public = 1;
			is_private = 0;
			is_friend = 0;
			is_family = 0;
		}
		else
		{
			is_public = 0;
			is_private = 1;
			if(permType == 1)
			{
				is_friend = 1;
			}
			else if(permType == 2)
			{
				is_family = 1;
			}
			else if(permType == 3)
			{
				is_friend = 1;
				is_family = 1;
			}
		}
		

		setInsituStatusText(currentPhoto,"Please wait...");

		// make the request
		// we are making a post to the same page that the user would
		// have been on if they weren't using this script (photo_settings.gne),
		// so we have to pass in the parameters it is expecting
		window.mrjoro_xmlhttp = new XMLHttpRequest();

		// this provides the callback method

		window.mrjoro_xmlhttp.onreadystatechange=window.mrjoro_requestToModifyPermissionsResponseHandler;
		window.mrjoro_xmlhttp.open("POST","/photo_settings.gne",true);
		window.mrjoro_xmlhttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded');

		// for now, we will set the comments and notes permissions to
		// the recommended level (all flickr members can add comments, only
		// contacts can add notes and tags
		
		// comment permissions
		// only you (0)
		// your friends and/or family (1)
		// your contacts (2)
		// any flickr member (recommended) (3)
		var perm_comment = 3;
		
		// metadata permissions
		// only you (0)
		// your friends and/or family (1)
		// your contacts (recommended) (2)
		// any flickr member (3)
		var perm_addmeta = 2;

		// global_auth_hash is defined on the page by flickr; we pass it as the magic cookie
		var paramString = 
			"id="+currentPhoto.photoId+
			"&done=1&is_public=" + is_public +
			"&is_private=" + is_private + 
			"&is_friend=" + is_friend + 
			"&is_family=" + is_family + 
			"&perm_comment=" + perm_comment + 
			"&perm_addmeta=" + perm_addmeta + 
			"&magic_cookie=" + global_auth_hash + 
			"&Submit=Submit";
		window.mrjoro_xmlhttp.send(paramString);
	};		

	// called back by the system when making the XMLHttpRequest
	window.mrjoro_requestToModifyPermissionsResponseHandler = function()
	{
		// the XMLHttpRequest will callback to this method
		// several times; readyState 4 indicates it has
		// completed the request
		if(window.mrjoro_xmlhttp.readyState != 4)
		{
			return;
		}
		
		lastRequestCompleted = true;
		
		// if there was a problem making the request,
		// the HTTP status will not be 200
		if(window.mrjoro_xmlhttp.status != 200)
		{
			alert("Unable to complete request.");
			clearInsituStatusText(currentPhoto);
			return;
		}
		

		// this causes the page to refresh, which will
		// ensure that the user sees the accurate state
		// of the license types/permissions that flickr
		// sees
		location.reload();
		
		// we don't bother clearing the status text if we
		// are doing a reload, since it will be cleared
		// automatically
		
	};	


	// ---------------- LICENSE TYPE -----------------


	// this will display the license popup in the correct location;
	// the license anchors are modified to call this method
	// when the user clicks on them
	window.mrjoro_displayLicenseSelection = function(index)
	{
		if(!lastRequestCompleted)
		{
			return;
		}
		
		lastRequestCompleted = false;
		
		currentPhoto = photoDescriptions[index];

		var left = getActualLeft(currentPhoto.licAnchor);
		var top = getActualTop(currentPhoto.licAnchor);

		// position the license selection box near the original icon
		licenseBox.setAttribute("style","position:absolute; border: 1px solid black; color:gray; top:" + top + "px;left:" + left + "px;");
		
		// display the license selection box
		licenseBox.style.display = "block";
		
	};

	// this will just hide the license box; note the cancel
	// button in the license popup will call this method	
	window.mrjoro_cancelLicenseSelection = function()
	{
		licenseBox.style.display = "none";
		window.mrjoro_resetLicenseSelection();
		lastRequestCompleted = true;
	};
	
	window.mrjoro_resetLicenseSelection = function()
	{
		document.getElementById("licenseTypeSelect").value = -1;		
	}
	

	// this is called whenever the user has made a selection
	// in the license drop-down; note that currentPhoto
	// was set already	
	window.mrjoro_requestToModifyLicense = function()
	{
		licenseBox.style.display = "none";

		// licenseTypeSelect was created in the innerHTML for
		// the license popup; it will contain the value
		// the user selected for the license type (which were
		// chosen to match the license type values that flickr
		// understands
		var licenseType = document.getElementById("licenseTypeSelect").value;
		window.mrjoro_resetLicenseSelection();
		
		setInsituStatusText(currentPhoto,"Please wait...");

		// we make a post to the target used on the license selection
		// page, with the parameters the target is expecting
		window.mrjoro_xmlhttp = new XMLHttpRequest();
		window.mrjoro_xmlhttp.onreadystatechange=window.mrjoro_requestToModifyLicenseResponseHandler;
		
		window.mrjoro_xmlhttp.open("POST","/photo_license.gne",true);
		window.mrjoro_xmlhttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
		window.mrjoro_xmlhttp.send("id="+currentPhoto.photoId+"&done=1&license="+licenseType+"&Submit=Submit");
		
	};

	// called back by the system when making the XMLHttpRequest
	window.mrjoro_requestToModifyLicenseResponseHandler = function()
	{
		// the XMLHttpRequest will callback to this method
		// several times; readyState 4 indicates it has
		// completed the request	
		if(window.mrjoro_xmlhttp.readyState != 4)
		{
			return;
		}

		lastRequestCompleted = true;

		
		// if there was a problem making the request,
		// the HTTP status will not be 200
		if(window.mrjoro_xmlhttp.status != 200)
		{
			alert("Unable to complete request.");
			clearInsituStatusText(currentPhoto);
			return;
		}

		// this causes the page to refresh, which will
		// ensure that the user sees the accurate state
		// of the license types/permissions that flickr
		// sees
		location.reload();

		// we don't bother clearing the status text if we
		// are doing a reload, since it will be cleared
		// automatically

	};
	


	// --------------- POPUP SETUP -------------		
	// we setup HTML for the permissions and the license type popups; note
	// that the onchange attribute of these callout to the appropriate
	// method (and that there is also a cancel)
	// it does not matter where we add these in the document because
	// they are styled as popups
	var permBox = document.createElement("div");
	permBox.innerHTML = "<select id='permTypeSelect' onchange='window.mrjoro_requestToModifyPermissions();'><option value='-1'>- Choose A Permission Type -</option><option value='0'>Public</option><option value='1'>Private, Friends</option><option value='2'>Private, Family</option><option value='3'>Private, Friends and Family</option><option value='4'>Private</option></select><input class='DeleteButt' value='CANCEL' onclick='window.mrjoro_cancelPermissionsSelection();' type='button'/>";
	document.body.appendChild(permBox);
	permBox.style.display = "none";	

	var licenseBox = document.createElement("div");
	licenseBox.innerHTML = "<select id='licenseTypeSelect' onchange='window.mrjoro_requestToModifyLicense();'><option value='-1'>- Choose A License -</option><option value='0'>None (All rights reserved)</option><option value='4'>Attribution License</option><option value='6'>Attribution-NoDerivs License</option><option value='3'>Attribution-NonCommercial-NoDervis License</option><option value='2'>Attribution-NonCommercial License</option><option value='1'>Attribution-NonCommercial-ShareAlike License</option><option value='5'>Attribution-ShareAlike License</option></select><input class='DeleteButt' value='CANCEL' onclick='window.mrjoro_cancelLicenseSelection();' type='button'/>";
	document.body.appendChild(licenseBox);
	licenseBox.style.display = "none";		

	
	// -------------- PHOTO DISCOVERY AND ANCHOR MODIFICATION ------------------
		

	// we need to find the permissions links and the license type links; we
	// want to change them so that instead of going to the indicated page,
	// they will call our javascript methods to popup the appropriate
	// selection box
	
	// finding these elements is a bit hacky, necessarily requiring us
	// to look at flickr's HTML and working back from there (this is
	// a problem inherent in Greasemonkey); if flickr ever changes their
	// photo pages, the following will most likely need to change
	
	// for the permission type, we want to find the anchors ("a" elements)
	// that have "Set privacy permissions for this photo" as the title,
	// but only those that have an img child (the ones with text should
	// remain unchanged... those are the "set privacy" text links)
	
	// note that the license typ is only displayed if the permissions
	// type is public... flickr does not let you set a license otherwise
	// so we will have the search for the license anchor within the
	// for loop below

	var imgNodesXpath = "//a[@title = 'Set privacy permissions for this photo']/img";
	var imgNodes = 
		document.evaluate( imgNodesXpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );

	for(var i=0; i<imgNodes.snapshotLength; i++)
	{
		// first, grab the id from the href in the
		// parent node (remember, we grabbed the img
		// element above, so the parent is the a element)
		var img = imgNodes.snapshotItem(i);
		var a = img.parentNode;

		photoDescriptions[i] = new PhotoDescription();
		photoDescriptions[i].permAnchor = a;

		var href = a.getAttribute("href");

		// grab the photo id from the href; it is the id in
		// the href, so we'll do a search for id= and then stop at
		// the ampersand 

		var idIndex = href.indexOf('id=');
		var startIndex = idIndex+3;
		var ampIndex = href.indexOf('&',startIndex);

		var photoId = href.substring(startIndex,ampIndex);
		
		photoDescriptions[i].photoId = photoId;

		// now, set the href to use our javascript instead
		a.setAttribute("href","javascript:window.mrjoro_displayPermissionsSelection(" + i + ");");

		// now, create an in situ status p element, which we can use
		// to say "Please wait" after the user makes a selection
		photoDescriptions[i].insituStatus = document.createElement("p");
		
		// put it after the paragraph containing the icons
		a.parentNode.parentNode.insertBefore(photoDescriptions[i].insituStatus,a.parentNode.nextSibling);
		photoDescriptions[i].insituStatus.style.display="none";

		// we also want to make the permissions adjustable
		// flickr has the following rules:
		// (1) permissions may only be set on public photos
		// (2) only those photos that have a cc license have
		// an icon and link on the photo stream page

		// so, we need to make sure to only add the ability
		// to change permissions based on if a photos is
		// public, and then we need to add a link if the
		// photo is not cc-licensed

		if(img.getAttribute("alt") == 'This photo is public')
		{
			// since it is public, we can modify licensing

			// if there is an a sibling to the a that has
			// an alt "This photo is licensed", then we
			// will just use that a... otherwise, we will
			// need to create a new a and add it as an
			// immediate sibling

			// get any a tags under the parent that contain
			// an img tag whose alt is "This photo is licensed"

			var licImgNodesXpath = "a/img[@alt = 'This photo is licensed']";
			var licImgNodes = 
				document.evaluate( licImgNodesXpath, a.parentNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );

			var hrefjs = "javascript:window.mrjoro_displayLicenseSelection(" + i + ");";
			if(licImgNodes.snapshotLength > 0)
			{
				// there is a license icon, so grab the
				// parent link and set the href
				var licImg = licImgNodes.snapshotItem(0);
				var licA = licImg.parentNode;
				licA.setAttribute("href",hrefjs);

				photoDescriptions[i].licAnchor = licA;
			}
			else
			{
				// we will need to create a link...
				var cLink = document.createElement("a");
				cLink.setAttribute("href",hrefjs);
				cLink.setAttribute("style","text-decoration:none");
				cLink.appendChild(document.createTextNode(" ©"));
				a.parentNode.insertBefore(cLink,a.nextSibling);

				photoDescriptions[i].licAnchor = cLink;
			}
		}

	}

// ----------------- HELPER METHODS ------------

	// the following are convenience methods for finding the
	// actual location on the given element; since this
	// script is for Firefox, we just use Firefox's method
	// of determining this
	getActualLeft = function(e)
	{
		var cur = 0;

		while(e.offsetParent != null)
		{
			cur += e.offsetLeft;
			e = e.offsetParent;
		}

		return cur;
	};

	getActualTop = function(e)
	{
		var cur = 0;

		while(e.offsetParent != null)
		{
			cur += e.offsetTop;
			e = e.offsetParent;
		}

		return cur;
	};

		

})();	
