Pre-populating Standard Page Layouts

detour sign

Warning: This uses undocumented methods. As with any code that relies on anything undocumented it’s not supported and it’s prone to breaking without warning so don’t rely on it working tomorrow!

It’s a known hidden feature of Salesforce that you can pre-populate fields on a standard page layout if you know the name of the fields. There are plenty of blogs on it. For example you can pre-populate the Account Name with the URL:

https://na43.salesforce.com/001/e?acc2=Peace+Without+Walls+Inc&acc5=012345

screenshot-2017-02-28-21-51-44

This gets a bit trickier still on custom page layouts:

https://na43.salesforce.com/a0Q/e?Name=Red%20Widget&00N0G00000DaxKQ=RED

screenshot-2017-02-28-21-53-45

Yeuck! Where did 00N0G00000DaxKQ come from? You have to dig it out using your browser developer tools.

Another blog post on the matter …

So to make this a bit easier, we’re going to write a little VF page and Controller to fathom it all out for us. We’ll only need to remember nice things like Developer Names for stuff.

To do this we need to query two system objects, the EntityDefinition and the FieldDefinition.

The Entity Definition will give us the URL and its Id as contained in the DurableId field.

Select DeveloperName, DurableId, EditUrl, NewUrl From EntityDefinition Where DeveloperName = 'Widget'

DeveloperName: Widget
DurableId: 01I0G000002e6SD
EditUrl: /{ID}/e
NewUrl: /a0Q/e

Using the DurableId from the first Query we can get the Field Definitions:

Select DeveloperName, DurableId, EntityDefinitionId, RunningUserFieldAccessId From FieldDefinition Where EntityDefinitionId = '01I0G000002e6SD' and DeveloperName = 'Colour'

DeveloperName: Colour
DurableId: 01I0G000002e6SD.00N0G00000DaxKQ
EntityDefinitionId: 01I0G000002e6SD

By stripping away everything after and including the . on the DurableId we are left with the Field Id we need to use in our URL.

So lets build something a bit more usable …

Controller:

public without sharing class PreloadPageLayoutController
{
	public String RedirectURL {get; set;}

	private static final String OBJECT_PARAMETER_KEY = 'o';

	/**
	 * Constructor - Builds URL
	 */
	public PreloadPageLayoutController()
	{
		// Page Parameter Map
		Map paramMap = ApexPages.currentPage().getParameters();

		// Get the Object Parameter
		String objectName = paramMap.get(OBJECT_PARAMETER_KEY);

		// Map the Key Pairs
		Map fieldMappings = new Map();
		fieldMappings.putAll(paramMap);
		fieldMappings.remove(OBJECT_PARAMETER_KEY);

		try
		{
			// Query the Entity Definition for the Object
			EntityDefinition ed = [Select DeveloperName, DurableId, EditUrl, NewUrl
									From EntityDefinition
									Where DeveloperName = :objectName];

			Map fieldNameToIdMap = new Map();

			Set fieldNames = fieldMappings.keySet();

			// Query the Field Definition for the Fields and Map the Developer Names to the Field Ids
			for (FieldDefinition fd : [Select DeveloperName, DurableId, EntityDefinitionId
										From FieldDefinition
										Where EntityDefinitionId = :ed.DurableId and
											DeveloperName in :fieldNames])
			{
				String[] idParts = fd.DurableId.split('\\.');

				fieldNameToIdMap.put(fd.DeveloperName, idParts[1]);
			}

			// Build the URL
			RedirectURL = ed.NewUrl;

			String prefix = '?';

			// Loop through the parameters
			for (String key : fieldMappings.keySet())
			{
				String value = fieldMappings.get(key);
				String fieldId = fieldNameToIdMap.get(key);

				String paramTemplate = '{0}{1}={2}';
				String param = String.format(paramTemplate, new String[] { prefix, fieldId == null ? key : fieldId, value });
				RedirectURL += param;

				prefix = '&';
			}

			ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, 'Redirecting to: ' + RedirectURL));
		}
		catch(Exception e)
		{
			ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, e.getMessage() + ' ' + e.getStackTraceString()));
			RedirectURL = null;
		}
	}

	/**
	 * Action Method to Redirect
	 */
	public PageReference redirect()
	{
		PageReference ref;

		if (RedirectURL != null)
		{
			ref = new PageReference(RedirectURL);
			ref.setRedirect(true);
		}

		return ref;
	}
}

Quick Unit Test Class:

@isTest
private class PreloadPageLayoutControllerTest {

	@isTest static void testParams()
	{
		Map paramMap = ApexPages.currentPage().getParameters();
 		paramMap.put('o', 'Account');
 		paramMap.put('Name', 'Test');
 		paramMap.put('acc2', 'Test2');

		PreloadPageLayoutController controller = new PreloadPageLayoutController();
		PageReference ref = controller.redirect();
		Map newParamMap = ref.getParameters();
		System.assert(newParamMap.containsKey('Name'));
		System.assertEquals('Test', newParamMap.get('Name'));
		System.assert(newParamMap.containsKey('acc2'));
		System.assertEquals('Test2', newParamMap.get('acc2'));
	}
}

Visualforce Page: PreloadPageLayout:


		If your page does not redirect automatically click here

We can now pass the parameters to our custom page instead but instead of having to inspect the page and work out the yeuky ids we can use the Developer Names instead:

https://na43.salesforce.com/apex/PreloadPageLayout?o=Widget&Name=Red+Widget&Colour=RED

It also works on Standard Objects for Custom fields only. For Standard fields you still need to use acc1, acc2, acc3 etc:

https://na43.salesforce.com/apex/PreloadPageLayout?o=Account&acc2=Peace+Without+Walls+Inc&acc5=012345&My_Rating=Good

You can also use it from your own Apex Code to do a redirect and pre-load the page:

public PageReference redirect()
{
	PageReference ref = Page.PreloadPageLayout;
	ref.getParameters().put('o', 'Widget');
	ref.getParameters().put('Name', 'Red Widget');
	ref.getParameters().put('Colour', 'RED');

	return ref;
}

Another use is a Custom Button to Add Red Widgets:

screenshot-2017-03-01-19-18-29

which you can then add onto the Search Layout.

Advertisements
Tagged with: , , ,
Posted in apex, code, force.com, salesforce

The Trouble with Dates and Times

SundialDates and DateTimes don’t always play nicely when you’re trying to push them through from environment to environment. You often have to mess about with them a bit to shoehorn them in and out of Salesforce.

I’m going to look here at how you get them to/from Javascript in the case of Visualforce remoting and then to/from JSON Strings if you’re integrating to another service where you need to use REST. There will no doubt be other ways to accomplish this and other scenerios but these are common use cases so this is more a starter for 10.

Javascript to Salesforce via Visualforce Remoting

Let’s begin by looking at going from Javascript to Salesforce via Visualforce Remoting.

Date example:

Javascript in a VF Page:

var dt = new Date();

console.log('dt=' + dt.toUTCString());

Visualforce.remoting.Manager.invokeAction(
    '{!$RemoteAction.RemoteController.sendDate}',
    dt.toUTCString(),
    function(result, event) {
        if (event.status)
        {
            console.log('result=' + result);
        }
    }
);

Apex Controller Methods:

@RemoteAction public static String sendDate(Date dt) 
{
    String r = 'sendDate: ' + dt;
    return r;
}

Results:

dt=Wed, 21 Sep 2016 18:40:43 GMT
result=sendDate: 2016-09-21 18:40:43

Strangely, you’ll notice the resulting date still has the time element preserved when you emit it. It shouldn’t be an issue since in Salesforce you’ll be throwing an instance of Date around. Just be careful when sending Dates back that were constructed in this way. It would probably be safer to use a DateTime in the remoting method and the use date() to return the date portion if that’s what you want.

DateTime example:

Javascript in a VF Page:

var dt = new Date();

console.log('dt=' + dt.toUTCString());

Visualforce.remoting.Manager.invokeAction(
    '{!$RemoteAction.RemoteController.sendDateTime}',
    dt.toUTCString(),
    function(result, event) {
        if (event.status)
        {
            console.log('result=' + result);
        }
    }
);

Apex Controller Methods:

@RemoteAction public static String sendDateTime(DateTime dt) 
{
    String r = 'sendDateTime: ' + dt;
    return r;
}

Results:

dt=Wed, 21 Sep 2016 18:40:43 GMT
result=sendDateTime: 2016-09-21 18:40:43

Salesforce to Javascript via Visualforce Remoting

Date example:

Javascript in a VF Page:

Visualforce.remoting.Manager.invokeAction(
    '{!$RemoteAction.RemoteController.getDate}',
    function(result, event) {
        if (event.status)
        {
            var d = new Date(result);
            console.log('d=' + d);
        }
    }
);

Apex Controller Methods:

@RemoteAction public static Date getDate() 
{
    return System.today();
}

Results:

d=Wed Sep 21 2016 01:00:00 GMT+0100 (BST)

DateTime example:

Javascript in a VF Page:

Visualforce.remoting.Manager.invokeAction(
    '{!$RemoteAction.RemoteController.getDateTime}',
    function(result, event) {
        if (event.status)
        {
            var dt = new Date(result);
            console.log('dt=' + dt);
        }
    }
);

Apex Controller Methods:

@RemoteAction public static DateTime getDateTime() 
{
    return System.now();
}

Results:

dt=Wed Sep 21 2016 20:16:17 GMT+0100 (BST)

Salesforce to JSON String

Date Example:

Apex code:

Date d = System.today();
String ds = JSON.serialize(d);
System.debug('d=' + d);
System.debug('ds=' + ds);

Results:

d=2016-09-21 00:00:00
ds="2016-09-21"

DateTime Example:

Apex code:

DateTime dt = System.now();
String dts = JSON.serialize(dt);
System.debug('ds=' + dt);
System.debug('dts=' + dts);

Results:

ds=2016-09-21 18:51:25
dts="2016-09-21T18:51:25.234Z"

JSON String to Salesforce

Now this is where the fun begins! You’d think that if you serialize a date or datetime, you can simply deserialize it back again wouldn’t you? WRONG!

Date Example:

This won’t work:

Date d = System.today();
String ds = JSON.serialize(d);
Date d2 = (Date)JSON.deserializeUntyped(ds);
System.debug('d=' + d);
System.debug('ds=' + ds);
System.debug('d2=' + d2);

Nor will this:

Date d = System.today();
String ds = JSON.serialize(d);
Date d2 = Date.valueOf(ds);
System.debug('d=' + d);
System.debug('ds=' + ds);
System.debug('d2=' + d2);

You need to do this:

Date d = System.today();
String ds = JSON.serialize(d);
Date d2 = Date.valueOf(ds.replace('"', ''));
System.debug('d=' + d);
System.debug('ds=' + ds);
System.debug('d2=' + d2);

Results:

d=2016-09-21 00:00:00
ds="2016-09-21"
d2=2016-09-21 00:00:00

DateTime Example:

This won’t work:

DateTime dt = System.now();
String dts = JSON.serialize(dt);
DateTime dt2 = (DateTime)JSON.deserializeUntyped(dts);
System.debug('dt=' + dt);
System.debug('dts=' + dts);
System.debug('dt2=' + dt2);

Nor will this:

DateTime dt = System.now();
String dts = JSON.serialize(dt);
DateTime dt2 = DateTime.valueOf(dts);
System.debug('dt=' + dt);
System.debug('dts=' + dts);
System.debug('dt2=' + dt2);

You need to do this:

DateTime dt = System.now();
String dts = JSON.serialize(dt);
DateTime dt2 = DateTime.valueOf(dts.replace('"', ' ').replace('T', ' '));
System.debug('dt=' + dt);
System.debug('dts=' + dts);
System.debug('dt2=' + dt2);

Results:

dt=2016-09-21 19:01:27
dts="2016-09-21T19:01:27.217Z"
dt2=2016-09-21 18:01:27

As I said, there are more ways to crack a nut and there are no doubt more scenarios but hopefully this will help avoid some of the confusion.

Tagged with: , , , , ,
Posted in apex, code, force.com, salesforce

Mocking Multiple HTTP Requests to the Same Endpoint

mockMy Blog posts are like buses. You don’t see one for ages and then two come along at once.

I was musing, like you do, what to do when Unit Testing a service that makes successive call-outs to the same endpoint. If the endpoints were different then you could use the MultiStaticResourceCalloutMock class. But what if you need to make two calls to the same endpoint?

So take this bit of code:

public with sharing class MyCallout
{
	public static Boolean doCallouts()
	{
		Boolean isDog = isDog('Toto');
		Boolean isHuman = isHuman('Dorothy');

		return isDog && isHuman;
	}

	private static Boolean isDog(String name)
	{
		return getSpecies(name) == 'dog';
	}

	private static Boolean isHuman(String name)
	{
		return getSpecies(name) == 'human';
	}

	private static String getSpecies(String name)
	{
		HttpRequest req = new HttpRequest();

		req.setMethod('GET');
		req.setHeader('Content-type', 'application/json');
        req.setHeader('Accept', 'application/json');

		req.setEndpoint('https://somewhereovertherainbow.com/wherestoto');

		req.setBody('{"name" : "' + name + '"}');

		Http http = new Http();

		HttpResponse res = http.send(req);

		if (res.getStatusCode() != 200)
		{
			return null;
		}

		Map marshalledResponse = (Map)JSON.deserializeUntyped(res.getBody());

		String species = (String)marshalledResponse.get('species');

		return species;
	}
}

It makes two successive calls to the same endpoint to resolve the species of a given Wizard Of Oz character. If they both match it returns true. Simple enough, but what about testing it?

In our unit test we create our mock response class (implementing the HttpCalloutMock interface) and add a simple constructor that takes a list of responses.

The respond method then returns the responses in sequence each time it’s called. Easy peasy lemon squeezy.

@isTest
private class MyCalloutTest
{
	@isTest static void testDoCallouts_pass()
	{
		// Create the responses in the correct sequence
		String[] responses = new String[] {
			'{ "species" : "dog" }',
			'{ "species" : "human" }'
		};

        // Set mock callout class
        Test.setMock(HttpCalloutMock.class, new MockResponse(responses));

		Boolean result = MyCallout.doCallouts();

        System.assertEquals(true, result);
	}

	@isTest static void testDoCallouts_fail()
	{
		// Create the responses in the wrong sequence
		String[] responses = new String[] {
			'{ "species" : "human" }',
			'{ "species" : "dog" }'
		};

        // Set mock callout class
        Test.setMock(HttpCalloutMock.class, new MockResponse(responses));

		Boolean result = MyCallout.doCallouts();

        System.assertEquals(false, result);
	}

    public class MockResponse
        implements HttpCalloutMock
    {
    	private Integer m_responseIndex;
    	private String[] m_reponses;

    	public MockResponse(String[] reponses)
    	{
    		m_responseIndex = 0;
    		m_reponses = reponses;
    	}

        public HTTPResponse respond(HTTPRequest req)
        {
            HttpResponse res = new HttpResponse();
            res.setHeader('Content-Type', 'application/json');

            // Return the next response in the list
			String reponse = m_reponses[m_responseIndex];
			m_responseIndex ++;

            res.setBody(reponse);
            res.setStatusCode(200);

            return res;
        }
    }
}

You could adapt this pattern to provide different response codes in the same way.

Tagged with: , , ,
Posted in apex, code, force.com, salesforce, Uncategorized

Wrapping Custom Metadata to Support Unit Testing

Wrapping Paper

Wrapping PaperMy last post looked at using a wrapper class to make Custom Settings more convenient to access from apex code and to ease defaulting and unit testing.

If, like me, you’ve taken the leap to using Custom Metadata instead of List based Custom Settings you may have found yourself with a headache when writing your unit tests. Sure, you don’t need SeeAllData=true to see your metadata from your unit test but you may want to make sure the settings are in order for your given test.

Unlike Custom Settings, you can’t use DML to inject data for your unit tests. Attempting to instantiate a custom metadata object results in the rather friendly:

[OPERATION FAILED]: classes/MyClass.cls: Field is not writeable

To get round this I’ve adapted the pattern from my last post to support Custom Metadata.

Lets use the example Custom Metadata defined here:

Custom Metadata Definition

Nothing too exciting, a checkbox and a text field but it’ll do to demonstrate.

So we’ll start with the wrapper:

/**
 * Wrapper for My_Custom_Metadata_Type__mdt	to ease access and testing
 */
public with sharing class MyCustomMetadataTypes
{
	// Cached Map of metadata
	@TestVisible private static Map s_myCustomMetadataTypeMap = new Map();

	// Public getter
	public static Metadata getMyCustomMetadataType(String developerName)
	{
		// Check the cache
		Metadata myMetadata = s_myCustomMetadataTypeMap.get(developerName);

		// Not found, so query the metadata
		if (myMetadata == null)
		{
			My_Custom_Metadata_Type__mdt myCustomMetadataType;

			try
			{
				myCustomMetadataType = [Select DeveloperName, My_Checkbox__c, My_Text__c
				    					From My_Custom_Metadata_Type__mdt
				     					Where DeveloperName = :developerName];
			}
			catch(System.QueryException sqe)
			{
				throw new MyCustomMetadataTypeException('Could not find My Custom Metadata Type with DeveloperName: ' + developerName);
			}

			// Wrap the metadata using the inner class
			myMetadata = new Metadata(myCustomMetadataType);

			// Add it to the cache
     		s_myCustomMetadataTypeMap.put(myCustomMetadataType.DeveloperName, myMetadata);
		}

		// Return the wrapped metadata
		return myMetadata;
	}

	// Inner class to wrap the metadata
	public class Metadata
	{
		// Properties represent the Fields in the Metadata
		public Boolean My_Checkbox {get; set;}
		public String My_Text {get; set;}

		// Constructor used when creating test values
		public Metadata()
		{
		}

		// Construct from a concrete metadata instance
		private Metadata(My_Custom_Metadata_Type__mdt metadata)
		{
			this.My_Checkbox = metadata.My_Checkbox__c;
			this.My_Text = metadata.My_Text__c;
		}
	}

	public class MyCustomMetadataTypeException extends Exception {}
}

You’ll notice we have an inner class that reflects the fields that we’ve defined in the Custom Metadata.

It has two constructors, a public one we can use for mocking test data and a private one for marshalling an instance of the metadata in the real world. We’ll always use this inner class to get metadata valus and never reference the metadata directly.

A static Map of Developer Names to instances of the inner class provide the way to set values for Unit Tests. It’s set to TestVisible for this purpose. It’s also used to cache values whenever the getter is called. While it’s not strictly necessary to cache the data as Salesforce takes care of that, it does help support the pattern.

The public getter simply takes an argument of a developer name and returns an instance of the inner class populated with the metadata. So in you’re production code you can do things like:

String myString = MyCustomMetadataTypes.getMyCustomMetadataType('indierock').My_Text;

If the developer name doesn’t exist it will throw an exception which you can catch.

I’ll now show you some unit test examples and we’re done:

@isTest
private class MyCustomMetadataTypesTest
{
	@isTest static void thisWontWork()
	{
		// You can't do this as the metadata fields are not writeable!
		// Result: [OPERATION FAILED]: classes/MyCustomMetadataTypesTest.cls: Field is not writeable: My_Custom_Metadata_Type__mdt.DeveloperName

		/*
		My_Custom_Metadata_Type__mdt metadata = new My_Custom_Metadata_Type__mdt(
			DeveloperName = 'test',
			My_Checkbox__c = true,
			My_Text__c = 'some text'
		);

		insert metadata;
		*/
	}

	@isTest static void thisWorks()
	{
		// Create our test metadata
		{
			MyCustomMetadataTypes.Metadata metadata = new MyCustomMetadataTypes.Metadata();
			metadata.My_Checkbox = true;
			metadata.My_Text = 'some text';

			MyCustomMetadataTypes.s_myCustomMetadataTypeMap.put('test', metadata);
		}
		{
			MyCustomMetadataTypes.Metadata metadata = new MyCustomMetadataTypes.Metadata();
			metadata.My_Checkbox = false;
			metadata.My_Text = 'some more text';

			MyCustomMetadataTypes.s_myCustomMetadataTypeMap.put('test2', metadata);
		}

		// Use the wrapper to access the metadata
		MyCustomMetadataTypes.Metadata metadata = MyCustomMetadataTypes.getMyCustomMetadataType('test');

		// Assert the results
		System.assertEquals(true, metadata.My_Checkbox);
		System.assertEquals('some text', metadata.My_Text);

		// Use the wrapper to access the metadata
		MyCustomMetadataTypes.Metadata metadata2 = MyCustomMetadataTypes.getMyCustomMetadataType('test2');

		// Assert the results
		System.assertEquals(false, metadata2.My_Checkbox);
		System.assertEquals('some more text', metadata2.My_Text);

		// This demonstrates the exception thrown when the metadata developer name can't be found
		try
		{
			MyCustomMetadataTypes.Metadata metadata3 = MyCustomMetadataTypes.getMyCustomMetadataType('test3');
			System.assert(false, 'Expected an Exception.');
		}
		catch(Exception e)
		{
			System.assert(e instanceof MyCustomMetadataTypes.MyCustomMetadataTypeException);
		}
	}

	@isTest static void thisAlsoWorks()
	{
		// Using some Custom Metadata already defined in my org. Look, no SeeAllData=true!

		// Use the wrapper to access the metadata
		MyCustomMetadataTypes.Metadata metadata = MyCustomMetadataTypes.getMyCustomMetadataType('indierock');

		// Assert the results
		System.assertEquals(true, metadata.My_Checkbox);
		System.assertEquals('The Cribs', metadata.My_Text);
	}
}

 

 

Tagged with: , , ,
Posted in apex, code, force.com, salesforce

Wrapping Custom Settings to Provide Defaults and Ease Unit Testing

Marconi Valve TunerI have a little pattern I use for wrapping custom settings. It has two uses, first to provide sensible defaults if the settings have not been configured and secondly to make writing unit tests easier with predictable values.

If you’ve ever deployed a new bit of code that runs fine in your sandbox because you have all your custom settings configured but then fails in production as they’ve not been set up then this pattern is for you.

Hierarchy Settings

We’ll start with hierarchy settings since this is the simplest wrapper. Take the custom settings below:

Hierarchy Custom Settings

We have 2 properties, a boolean and a string.

Here’s the wrapper class:

public with sharing class MyHierarchySettings
{
	@TestVisible private static MyHierarchySettings__c s_settings;

	// Cache the settings
	private static MyHierarchySettings__c getSettings()
	{
		if (s_settings ==  null)
		{
			s_settings = MyHierarchySettings__c.getInstance();
		}

		return s_settings;
	}

	public static Boolean getMyBooleanValue()
	{
		return getSettings().My_Boolean_Value__c;
	}

	public static String getMyTextValue()
	{
		MyHierarchySettings__c settings = getSettings();

		// Provide a default if it's not initialised
		if (settings.My_Text_Value__c == null)
		{
			return 'henrietta';
		}
		
		return settings.My_Text_Value__c;
	}
}

You’ll see that we have a static variable to cache the settings and a getSettings() method which caches the custom settings. It’s also cunningly visible to unit tests. We then have a getter for each property and in the case of the Text setting, returns a default. We’ve no need to return a default for the boolean since it will happily return false and that’s a good enough default for me in this scenario. It’s worth noting that the getInstance() method always returns an object for a Hierarchy setting even if it’s not been defined yet. You’ll see later this is not true of List settings.

So to demonstrate we have a unit test:

@isTest
private class MyHierarchySettingsTest 
{	
	@isTest static void test_getMyBooleanValueDefault() 
	{
		System.assertEquals(false, MyHierarchySettings.getMyBooleanValue());
	}
	
	@isTest static void test_getMyTextValueDefault() 
	{
		System.assertEquals('henrietta', MyHierarchySettings.getMyTextValue());
	}
	
	@isTest static void test_setMyBooleanValue() 
	{
		MyHierarchySettings.s_settings = new MyHierarchySettings__c(My_Boolean_Value__c = true);
		System.assertEquals(true, MyHierarchySettings.getMyBooleanValue());
	}
	
	@isTest static void test_setMyTextValue() 
	{
		MyHierarchySettings.s_settings = new MyHierarchySettings__c(My_Text_Value__c = 'scarlet');
		System.assertEquals('scarlet', MyHierarchySettings.getMyTextValue());
	}
}

You’ll see in the unit tests how we can make use of the static variable to initialise the custom setting with our own values. This allows us to write a unit test with predictable data and is much tidier than inserting custom settings data.

List Custom Settings

List custom settings are a little more complicated. Let’s look at the sample settings below:

List Custom Settings

Again I have just a Boolean and a String property. So let’s look at the wrapper class for this:

public with sharing class MyListSettings
{
	@TestVisible private static Map<String, MyListSettings__c> s_settingsMap;

	// Cache the settings
	private static MyListSettings__c getSettings(String key)
	{
		if (s_settingsMap == null)
		{
			s_settingsMap = new Map<String, MyListSettings__c>();
		}

		MyListSettings__c settings = s_settingsMap.get(key);

		if (settings ==  null)
		{
			settings = MyListSettings__c.getInstance(key);
			s_settingsMap.put(key, settings);
		}

		return settings;
	}

	public static Boolean getMyBooleanValue(String key)
	{
		MyListSettings__c settings = getSettings(key);

		// Provide a default if it's not initialised
		if (settings == null)
		{
			return false;
		}

		return settings.My_Boolean_Value__c;
	}

	public static String getMyTextValue(String key)
	{
		MyListSettings__c settings = getSettings(key);

		// Provide a default if it's not initialised
		if (settings == null || settings.My_Text_Value__c == null)
		{
			return 'henrietta';
		}
		
		return settings.My_Text_Value__c;
	}
}

You’ll notice this time the static variable is a map keyed on the setting name. The getSettings() method still caches the data but stores it in the map. The getters for the properties take an argument of the key to the List. You’ll also notice that we have to explicitly supply a default for the Boolean. This is because the getInstance(key) method will return null if there is no List setting with the specified key.

On to the unit test to demonstrate:

@isTest
private class MyListSettingsTest 
{	
	@isTest static void test_getMyBooleanValueDefault() 
	{
		System.assertEquals(false, MyListSettings.getMyBooleanValue('geoff'));
	}
	
	@isTest static void test_getMyTextValueDefault() 
	{
		System.assertEquals('henrietta', MyListSettings.getMyTextValue('geoff'));
	}
	
	@isTest static void test_setMyBooleanValue() 
	{
		MyListSettings.s_settingsMap = new Map<String, MyListSettings__c> {'geoff' => new MyListSettings__c(My_Boolean_Value__c = true)};
		System.assertEquals(true, MyListSettings.getMyBooleanValue('geoff'));
	}
	
	@isTest static void test_setMyTextValue() 
	{
		MyListSettings.s_settingsMap = new Map<String, MyListSettings__c> {'geoff' => new MyListSettings__c(My_Text_Value__c = 'scarlet')};
		System.assertEquals('scarlet', MyListSettings.getMyTextValue('geoff'));
	}
}

In the same way as the hierarchy test we can use the static map variable to initialise it with predictable data.

Tagged with: , , ,
Posted in apex, code, force.com, salesforce

Connecting to Salesforce using OAuth 2.0 from PHP

Dancers

Introduction

Salesforce, with its rich set of APIs allows endless integration with 3rd party systems.

The key to accessing Salesforce from another system, be it a Web App, Mobile device or even a command line script, is logging on and obtaining a Session Token.

There a number of ways to do that. You could, for example, prompt for a Username, Password and Security token and use Salesforce’s Username-Password flow. However that would mean passing user credentials around and is not ideal. If you wanted to avoid the user having to repeatedly log on to Salesforce you would have to store the user credentials somewhere which would be even worse.

A better way of doing this is to use Salesforce’s Web Server Authentication Flow. This lets Salesforce handle the login process and sends back an Authentication Token from which you can request a Session Token. Better still, you can use this method to get a Refresh Token. The Refresh Token can then be stored or used later when the Session Token has expired in order to get a fresh Session Token without having to repeat the login process.

Warning: It’s very important that the Refresh Token (and to a certain extent the Session Token) is treated as if it were a password. Take steps to store it securely as anyone gaining access to this token could log on to your Salesforce org. The code in this example is for illustrative purposes, I don’t recommend spitting your tokens out to the screen.

The OAuth Dance

Sometimes referred to as the OAuth dance, this is the exchange of information between the consumer (in this case a PHP web server) and a provider (in this case Salesforce).

The following diagram outlines the steps involved in getting the all important Session Token:

OAuth Flow

Once you have you a Refresh Token you can hang on to it and use it for logging in again once your Session Token has expired. The process is then much simpler:

OAuth Refresh Token flow

PHP Example

I’ve chosen PHP, it’s fairly ubiquitous and I have it to hand but the steps involved apply to whatever language and framework you choose.

Secure Your Server!

It’s important that your PHP script is running on a secure server. Salesforce will only accept a call back to a URL over HTTPS. If you don’t have a secure server and simply want to try it out in an insecure and non-sensitive environment, I’ve described a Dropbox hack at the end. Doing so will transmit sensitive login information over an insecure connection. It is therefore not recommended, so use it at your own peril!

Create a Connected Application

Firstly you’ll need to login in to Salesforce and navigate to Setup->Create->Apps

Under Connected Apps, click New.

Fill in the mandatory fields.

Check the Enable OAuth Settings box.

In the Callback URL field, enter the URL of your PHP Script:

Eg: https://apps.myphpserver.com/TestOAUTH/sfdc_oauth_web.php

Under Available OAuth Scopes select:

Perform requests on your behalf at any time (refresh_token, offline_access)

Click Save and make a note of the generated Consumer Key and Consumer Secret.

Consumer App

The consumer app is a simple PHP web page that displays the connection state and provides buttons to step through the OAuth process:

OAuth Reset

The Reset button re-initialises the state to what you see above. passthroughState1 and 2 are simply there to demonstrate how to pass state through the authentication request and back. codeVerifier is 128 bytes of random data used to secure the authentication step.

Clicking Authenticate will redirect the user to the salesforce login page and prompt the user to Authenticate the connection before issuing a call back to the PHP script:

OAuth Authorise

OAuth Callback

You’ll see we now have a code passed back from Salesforce. This is the Authentication Code. Both passthroughState1 and 2 have also been sent back in a state parameter. You’ll see how this works in the code later.

Clicking Login via Authentication Code will take that code and attempt to login to Salesforce to get the Session Token and Refresh Token:

OAuth Code Login

You’ll see now we have a Session Token and Refresh Token. Using the Session Token we can now perform a request to get some data. Clicking the Get User button will use the REST API to get some user information:

Get User

Once the Session Token has expired we can use the Refresh Token to obtain a new Session Token without having to authenticate again. In a real world example you may cache this Refresh Token for later use. Clicking Login via Refresh Token with do this:

OAuth Refresh Login

PHP Script

Here’s the PHP Script in full. I’ll describe some of the salient points after. You will need to modify the getClientId() and getClientSecret() methods to to use the Consumer Key and Consumer Secret that belong to the Connected App you created earlier.

<?php
	// Report all errors (ignore Notices)
	error_reporting(E_ALL & ~E_NOTICE);
	ini_set('display_errors', 1);
	session_start();

	// Define our State class
	class State 
	{
		public $passthroughState1;	// Arbitary state we want to pass to the Authentication request
		public $passthroughState2;	// Arbitary state we want to pass to the Authentication request

		public $code;				// Authentication code received from Salesforce
		public $token;				// Session token
		public $refreshToken;		// Refresh token
		public $instanceURL;		// Salesforce Instance URL
		public $userId;				// Current User Id
		
		public $codeVerifier;		// 128 bytes of random data used to secure the request

		public $error;				// Error code
		public $errorDescription;	// Error description

		/**
		 * Constructor - Takes 2 pieces of optional state we want to preserve through the request
		 */
	    function __construct($state1 = "", $state2 = "")
	    {
	    	// Initialise arbitary state
	    	$this->passthroughState1 = $state1;
	    	$this->passthroughState2 = $state2;

	    	// Initialise remaining state
			$this->code = "";
			$this->token = "";
			$this->refreshToken = "";
			$this->instanceURL = "";
			$this->userId = "";
			
			$this->error = "";
			$this->errorDescription = "";

			// Generate 128 bytes of random data
			$this->codeVerifier = bin2hex(openssl_random_pseudo_bytes(128));
	    }

	    /**
	     * Helper function to populate state following a call back from Salesforce
	     */
	    function loadStateFromRequest()
	    {
	    	$stateString = "";

	    	// If we've arrived via a GET request, we can assume it's a callback from Salesforce OAUTH
	    	// so attempt to load the state from the parameters in the request
			if ($_SERVER["REQUEST_METHOD"] == "GET") 
			{
				$this->code = $this->sanitizeInput($_GET["code"]);
				$this->error = $this->sanitizeInput($_GET["error"]);
				$this->errorDescription = $this->sanitizeInput($_GET["error_description"]);
				$stateString = $this->sanitizeInput($_GET["state"]);

				// If we have a state string, then deserialize this into state as it's been passed
				// to the salesforce request and back
				if ($stateString)
				{
					$this->deserializeStateString($stateString);
				}
			}
	    }

	    /**
	     * Helper function to sanitize any input and prevent injection attacks
	     */
	    function sanitizeInput($data) 
	    {
  			$data = trim($data);
  			$data = stripslashes($data);
  			$data = htmlspecialchars($data);
  			return $data;
		}

		/**
		 * Helper function to serialize our arbitary state we want to send accross the request
		 */
		function serializeStateString()
		{
			$stateArray = array("passthroughState1" => $this->passthroughState1, 
								"passthroughState2" => $this->passthroughState2
								);

			return rawurlencode(base64_encode(serialize($stateArray)));
		}

		/**
		 * Helper function to deserialize our arbitary state passed back in the callback
		 */
		function deserializeStateString($stateString)
		{
			$stateArray = unserialize(base64_decode(rawurldecode($stateString)));

			$this->passthroughState1 = $stateArray["passthroughState1"];
			$this->passthroughState2 = $stateArray["passthroughState2"];
		}

		/**
		 * Helper function to generate the code challenge for the code verifier
		 */
		function generateCodeChallenge()
		{
			$hash = pack('H*', hash("SHA256", $this->generateCodeVerifier()));

			return $this->base64url_encode($hash);
		}

		/**
		 * Helper function to generate the code verifier
		 */
		function generateCodeVerifier()
		{
			return $this->base64url_encode(pack('H*', $this->codeVerifier));
		}

		/**
		 * Helper function to Base64URL encode as per https://tools.ietf.org/html/rfc4648#section-5
		 */
		function base64url_encode($string)
		{
			return strtr(rtrim(base64_encode($string), '='), '+/', '-_');
		}

		/**
		 * Helper function to display the current state values
		 */
		function debugState($message = NULL)
		{
			if ($message != NULL)
			{
				echo "<pre>$message</pre>";
			}

			echo "<pre>passthroughState1 = $this->passthroughState1</pre>";
			echo "<pre>passthroughState2 = $this->passthroughState2</pre>";
			echo "<pre>code = $this->code</pre>";
			echo "<pre>token = $this->token</pre>";
			echo "<pre>refreshToken = $this->refreshToken</pre>";
			echo "<pre>instanceURL = $this->instanceURL</pre>";
			echo "<pre>userId = $this->userId</pre>";
			echo "<pre>error = $this->error</pre>";
			echo "<pre>errorDescription = $this->errorDescription</pre>";
			echo "<pre>codeVerifier = $this->codeVerifier</pre>";
		}
	}

	// If we have not yet initialised state, are resetting or are Authenticating then Initialise State
	// and store in a session variable.
	if ($_SESSION['state'] == NULL || $_POST["reset"] || $_POST["authenticate"])
	{
	 	$_SESSION['state'] = new State('ippy', 'dippy');
	}

	$state = $_SESSION['state'];

	// Attempt to load the state from the page request
	$state->loadStateFromRequest();

	// if an error is present, render the error
	if ($state->error != NULL)
	{
		renderError();		
	}

	// Determine the form action
	if ($_POST["authenticate"])	// Authenticate button clicked
	{
		doOAUTH();	
	}
	else if ($_POST["login_via_code"])	// Login via Authentication Code button clicked
	{
		if (!loginViaAuthenticationCode())
		{
			renderError();
			return;
		}

		renderPage();
	}
	else if ($_POST["login_via_refresh_token"])	// Login via Refresh Token button clicked
	{
		if (!loginViaRefreshToken())
		{
			renderError();
			return;
		}

		renderPage();
	}
	else if ($_POST["get_user"])	// Get User button clicked
	{
		// Get the user data from Salesforce
		$userDataHTML = getUserData();

		// Render the page passing in the user data
		renderPage($userDataHTML);
	}
	else	// Otherwise render the page
	{
		renderPage();
	}

	// Render the Page
	function renderPage($userDataHTML = NULL)
	{
		$state = $_SESSION['state'];

		echo "<!DOCTYPE html>";
?>
		<html>
			<head>
			  	<title>SFDC - OAuth 2.0 Web Server Authentication Flow</title>
			  	<meta charset="UTF-8">
			</head>

			<body>
				<h1>SFDC - OAuth 2.0 Web Server Authentication Flow</h1>
<?php
				// Show the current state values
				$state->debugState();

				// If we have some user data to display then do so
				if ($userDataHTML)
				{
					echo $userDataHTML;
				}
?>
				<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
					<input type="submit" name="reset" value="Reset" />
					<input type="submit" name="authenticate" value="Authenticate" />
					<input type="submit" name="login_via_code" value="Login via Authentication Code" />
					<input type="submit" name="login_via_refresh_token" value="Login via Refresh Token" />
					<input type="submit" name="get_user" value="Get User" />
				</form>

			</body>
		</html>
<?php
	}

	/**
	 * Redirect page to Salesforce to authenticate
	 */
	function doOAUTH()
	{
		$state = $_SESSION['state'];

		// Set the Authentication URL
		// Note we pass in the code challenge
		$href = "https://login.salesforce.com/services/oauth2/authorize?response_type=code" . 
				"&client_id=" . getClientId() . 
				"&redirect_uri=" . getCallBackURL() . 
				"&scope=api refresh_token" . 
				"&prompt=consent" . 
				"&code_challenge=" . $state->generateCodeChallenge() .
				"&state=" . $state->serializeStateString();

		// Wipe out arbitary state values to demonstrate passing additional state to salesforce and back
		$state->passthroughState1 = NULL;
		$state->passthroughState2 = NULL;

		// Perform the redirect
		header("location: $href");
	}

	/**
	 * Login via an Authentication Code
	 */
	function loginViaAuthenticationCode()
	{
		$state = $_SESSION['state'];

		// Create the Field array to pass to the post request
		// Note we pass in the code verifier and the authentication code
		$fields = array('grant_type' => 'authorization_code', 
						'client_id' => getClientId(),
						'client_secret' => getClientSecret(),
						'redirect_uri' => getCallBackURL(),
						'code_verifier' => $state->generateCodeVerifier(),
						'code' => $state->code,
						);
		
		// perform the login to Salesforce
		return doLogin($fields, false);
	}

	/**
	 * Login via a Refresh Token
	 */
	function loginViaRefreshToken()
	{
		$state = $_SESSION['state'];

		// Create the Field array to pass to the post request
		// Note we pass in the refresh token
		$fields = array('grant_type' => 'refresh_token', 
						'client_id' => getClientId(),
						'client_secret' => getClientSecret(),
						'redirect_uri' => getCallBackURL(),
						'refresh_token' => $state->refreshToken,
						);

		// perform the login to Salesforce
		return doLogin($fields, true);
	}

	/**
	 * Login to Salesforce to get a Session Token using CURL
	 */
	function doLogin($fields, $isViaRefreshToken)
	{
		$state = $_SESSION['state'];

		// Set the POST url to call
		$postURL = 'https://login.salesforce.com/services/oauth2/token';

		// Header options
		$headerOpts = array('Content-type: application/x-www-form-urlencoded');

		// Create the params for the POST request from the supplied fields	
		$params = "";
		
		foreach($fields as $key=>$value) 
		{ 
			$params .= $key . '=' . $value . '&';
		}

		$params = rtrim($params, '&');

		// Open the connection
		$ch = curl_init();

		// Set the url, number of POST vars, POST data etc
		curl_setopt($ch, CURLOPT_URL, $postURL);
		curl_setopt($ch, CURLOPT_POST, count($fields));
		curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headerOpts);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

		// Execute POST
		$result = curl_exec($ch);

		// Close the connection
		curl_close($ch);

		//record the results into state
		$typeString = gettype($result);
		$resultArray = json_decode($result, true);

		$state->error = $resultArray["error"];
		$state->errorDescription = $resultArray["error_description"];

		// If there are any errors return false
		if ($state->error != null)
		{
			return false;
		}

		$state->instanceURL = $resultArray["instance_url"];
		$state->token = $resultArray["access_token"];

		// If we are logging in via an Authentication Code, we want to store the 
		// resulting Refresh Token
		if (!$isViaRefreshToken)
		{
			$state->refreshToken = $resultArray["refresh_token"];
		}

		// Extract the user Id
		if ($resultArray["id"] != null)
		{
			$trailingSlashPos = strrpos($resultArray["id"], '/');

			$state->userId = substr($resultArray["id"], $trailingSlashPos + 1);
		}

		// verify the signature
		$baseString = $resultArray["id"] . $resultArray["issued_at"];
		$signature = base64_encode(hash_hmac('SHA256', $baseString, getClientSecret(), true));

		if ($signature != $resultArray["signature"])
		{
			$state->error = 'Invalid Signature';
			$state->errorDescription = 'Failed to verify OAUTH signature.';

			return false;
		}

		// Debug that we've logged in via the appropriate method
		echo "<pre>Logged in " . ($isViaRefreshToken ? "via refresh token" : "via authorisation code") . "</pre>";

		return true;
	}

	/**
	 * Get User Data from Salesforce using CURL
	 */
	function getUserData()
	{
		$state = $_SESSION['state'];

		// Set our GET request URL
		$getURL = $state->instanceURL . '/services/data/v20.0/sobjects/User/' . $state->userId . '?fields=Name';

		// Header options
		$headerOpts = array('Authorization: Bearer ' . $state->token);

		// Open connection
		$ch = curl_init();

		// Set the url and header options
		curl_setopt($ch, CURLOPT_URL, $getURL);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headerOpts);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

		// Execute GET
		$result = curl_exec($ch);

		// Close connection
		curl_close($ch);

		// Get the results
		$typeString = gettype($result);
		$resultArray = json_decode($result, true);

		// Return them as an html String
		$rtnString = '<hr><h2>User Data</h2>';

		foreach($resultArray as $key=>$value) 
		{ 
			$rtnString .= "<pre>$key=$value</pre>";
		}

		return $rtnString;
	}

	/**
	 * Helper function to render an Error
	 */
	function renderError()
	{
		$state = $_SESSION['state'];

		echo '<div class="error"><span class="error_msg">' . $state->error . '</span> <span class="error_desc">' . $state->errorDescription . '</span></div>';
	}

	/**
	 * Get the hard coded Client Id for the Conected Application
	 */
	function getClientId()
	{
		return "xxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzzzzzzzzzzzz";
	}

	/**
	 * Get the hard coded Client Secret for the Conected Application
	 */
	function getClientSecret()
	{
		return "1234567890123456789";
	}

	/**
	 * Get the Call back URL (the current php script)
	 */
	function getCallBackURL()
	{
		$callbackURL = ($_SERVER['HTTPS'] == NULL || $_SERVER['HTTPS'] == false ? "http://" : "https://") .
			$_SERVER['SERVER_NAME'] . "/" . $_SERVER['PHP_SELF'];

		return $callbackURL;
	}
?>

I’m using a class called State to represent the state and storing an instance of it in a SESSION variable.

You’ll notice some helper functions for popuating the state from a GET request following the Salesforce call back.

You’ll also notice some helper functions for serialising and deserialising the state we want to pass through with the authorisation request. While it’s not that useful in this example as we are using PHP’s SESSION variables to manage state it demonstrates how you can preserve data across the request.

In addition there are two helper functions that generate the Code Challenge and Code Verifier used across the authorisation request. While not strictly necessary this adds an additional layer of security. Salesforce will compare the two values when attempting to login using the authentication code. I had a bit of mare getting this to work so I’d like to give a quick nod to Ian Ratcliffe’s forum post that used the PHP bin2hex and pack functions to clinch this. You’ll see these codes used when constructing the authorisation and login requests in the doOAUTH() and loginViaAuthenticationCode() functions.

In the doLogin() function you’ll also notice another security check after the login has succeeded. This verifies the signature passed back in the login response with a hash of the User Id and Issued At values from the response using the Client Secret.

Dropbox Hack

As I mentioned earlier, it’s important that your web server is secured since Salesforce will only authenticate a callback over HTTPS.

I don’t recommend this at all and take no responsibility for using this hack … but if, like me, you’re just hacking around in a developer org, have nothing that you’d want falling into the wrong hands and are just trying stuff out you can use Dropbox to take care of the HTTPS callback.

To do this, in your Public Dropbox folder create a script called redirect.htm that looks like this (change the path to match the URL of your PHP script:

<script>
var href = window.location.href;
var queryParams = href.substring(href.indexOf('?'));
window.location = 'http://apps.myphpserver.com/TestOAUTH/sfdc_oauth_web.php' + queryParams;
</script>

Now copy the Dropbox public link and paste it into the Callback URL of your Connected App definition on Salesforce.

In the function getCallBackURL() of your PHP script assign the same link path to the $callbackURL variable.

Now Salesforce will use call back to the Dropbox script which will then strip the parameters and attach them to a redirect to your PHP Script (all very insecurely, of course!)

Tagged with: , , , , , ,
Posted in apex, code, force.com, PHP, salesforce

Lightning Lookup Component – <aura:if> workaround

For those of you having tried to implement the Lightning Lookup from my post: https://meltedwires.com/2015/10/31/salesforce-lightning-lookup-component-v2/

You may have experienced what appears to be a platform bug surrounding <aura:if> that results in the friendly, yet uninformative error: “Something has gone wrong. Cannot read property ‘childNodes’ of null. Please try again.”  Thanks to Vipul for finding this, it worked when I first published the article but clearly something has changed in the matrix.

After doing a bit of digging it seems to be related to the rerendering of <aura:if> in the AccountLookup.cmp. There’s a whole thread on it here. So until Salesforce apply a fix, I’ll post the workaround here.

Note: The bug doesn’t show up in the lookup component itself but in the example AccountLookup component.

The workaround is to use CSS to show/hide the <force:recordView> rather than an <aura:if>.

To do that we need add some style to the AccountLookup Component as follows by clicking the Style link in the Dev Console or adding the AccountLookupStyle.css to the bundle:

.THIS.hideme {
    display: none;
}

Next, we modify the AccountLookup.cmp to use a <div> instead of an <aura:if>:

<aura:component implements="flexipage:availableForAllPageTypes,force:appHostable">
    <!-- Attributes -->
    <aura:attribute name="recordId" type="Id" description="The current record Id to display" />
    
    <!-- Event handlers -->
    <aura:handler name="updateLookupIdEvent" event="c:UpdateLookupId" action="{!c.handleAccountIdUpdate}"/>
    <aura:handler name="clearLookupIdEvent" event="c:ClearLookupId" action="{!c.handleAccountIdClear}"/>
    
    <!-- Lookup component -->
    <c:LookupSObject label="Account" pluralLabel="Accounts" sObjectAPIName="Account" instanceId="MyAccount" listIconSVGPath="/resource/SLDS092/assets/icons/standard-sprite/svg/symbols.svg#account" listIconClass="slds-icon-standard-account" />

    <!-- Record view -->
    <div aura:id="accountview" class="hideme">
    	<force:recordView recordId="{!v.recordId}" />
    </div>
</aura:component>

Finally we add a Renderer to the AccountLookup and override the rerender method to show/hide the <force:recordView> depending on whether there is an Id present. We can do that by clicking the Renderer link in the Dev Console or adding the AccountLookupRenderer.js to the bundle:

({
	rerender: function(cmp, helper) {
		this.superRerender();

		// Get the Id and Record View
		var accountId = cmp.get('v.recordId');
 		var accountView = cmp.find('accountview');

		// Show or Hide the Account View depending on the Id value
        if (!accountId)
        {
            $A.util.addClass(accountView, 'hideme');
        }
        else
        {
            $A.util.removeClass(accountView, 'hideme');
        }
    },
})

It’s not quite ideal, as the <force:recordView> appears to be rerendered asynchronously and you get the message: “Invalid Record Id or Record not accessible.” briefly appearing.

It should, however, get you round the problem for now.

Posted in apex, code, force.com, lightning, salesforce, Uncategorized
About Me
Product Services Developer at:
FinancialForce.com
All views expressed here are my own. More about me and contact details here.

Enter your email address to follow this blog and receive notifications of new posts by email.

Copyright (there isn’t any, feel free to reuse!)

CC0
To the extent possible under law, Tony Scott has waived all copyright and related or neighboring rights to MeltedWires.com Examples and Code Samples. This work is published from: United Kingdom.

%d bloggers like this: