Monday, April 25, 2011
[Salesforce.com] Cloud Force Paris 2011
Cloud Force Paris 2011 is coming soon, featuring Marc Benioff! Join us on April the 5th. More details on Salesforce.com web site.
I will make the Heroku presentation. Heroku is an amazing Ruby on Rails multitenant cloud computing platform. I really like it, it was love at first sight :-)
Stay tune for new Heroku blog posts at this place or on the Force.com blog.
[Salesforce.com] The Code Review Tool Kit
Today I want to share a set of shell scripts that I am using when working on my code reviews.
Code review, the big picture
It consists in performing a validation of the work done by a partner, checking:
- technical design,
- code quality, respect of the best practices,
- governor limits,
- etc.
Context
Too often, I do not have an Internet access on the customer site. I am retrieving the Salesforce organization meta data using eclipse and my 3G key; then I am working off line.
What do the scripts?
On the first hand, the scripts provide a high level overview of the code structure: how many classes, pages, components, triggers, where are the classes used, etc. On the other hand two additional tools: find every usage in the org of a field and get all the relationships between the objects (lookup and master/detail).
All the scripts generate a CSV output for an easy integration in Excel, copy/paste from the shell to Excel! (I like this motto)
This is really save my time and let me focus on the review analysis.
Script sources
The code is available under the GPL license 3.0
SOLUTION DISCUSSION
The requirements
My scripts could be replaced by a program that communicates with the API and uses the DescribeObject method. But just keep in mind my two requirements:
- an off line usage
- Keep It Simple
Why a shell script?
The scripts might be optimized, or would be nicer written in a language such as Perl or Ruby rather than a shell script. I experimented several Perl XML parsers but they were slow and required Perl and Unix skills to be compiled, too complex to be shared. The choice of shell scripts appears as the best solution, with an easy deployment regardless the platform:
- Unix or gnu/Linux shell
- Mac OSX shell
- Windows using Cygwin
Awk is also required (provided with these environments).
My working place is gnu/Linux, Ubuntu flavor.
Fill the spaces!
Nature abhors a vacuum. I recommend not using blank spaces in your Eclipse project names. Always prefer an underscore. This is really making sense when it comes to shell scripts. In a shell, all space characters contained in file or directory names should be protected with a backslash:
$ cd Force.com\ IDE
Same issue in a for loop: e.g. the string “Force.com IDE” is processed as two separate files/directories:
$ for i in `echo "Force.com IDE"`; do echo $i; done
Force.com
IDE
The solution is creating symbolic links:
olivier@Ubuntu1:~/Workspaces$ ln -s Force.com\ IDE Force.com_IDE
olivier@Ubuntu1:~/Workspaces$ ls -l
total 4
drwxr-xr-x 21 olivier olivier 4096 2011-03-05 09:55 Force.com IDE
lrwxrwxrwx 1 olivier olivier 13 2011-01-26 10:43 Force.com_IDE -> Force.com IDE
Remark 1: my Eclipse workspaces path is ~/Workspaces
All my projects are located under “~/Workspaces/Force.com_IDE”
Remark 2: the provided scripts will work even if your files/directories names contains spaces because I protected the paths with double quotes.
Preparing the environment
Unzip the archive scripts.tgz and copy the scripts to your “Force.com_IDE” directory, that is where are located your projects directories. This is convenient, the scripts will be able to reach any of your eclipse projects.
To run the scripts, you have to download at least the following meta data:
- classes
- pages
- triggers
- components
- objects
Remark: all the meta data are required for the script field_usage.sh.
To add more meta data to your existing Eclipse project: open the project properties window:
click on the “Add/Remove” button:
choose your meta data:
Code naming convention
I am working with this naming convention:
Visual force pages: VF + [number] + [name].
Triggers: [object name] + [event] + [name]. The name is optional
Apex classes (trigger): AP + [number] + name
Apex classes (controller): VF + [number] + name + _Ctrl. The class name should match with the Visual force page name
Apex classes (others): name
Components: CO + [number] + [name].
Remark: the number are defined in your technical design document (assuming you have one).
Scripts “limitation”
The best approach would had been to work with a code parser, a XML parser and multi lines regular expressions. I choose a “quick & dirty” way, using simple shell commands line:
Pro's
- quicker development
- easy to understand, maintain
- very fast script execution
con's
- hell, no con's, that doing the job pretty well!
THE SCRIPTS
./apex.sh
Run the command:
apex.sh [Directory]
Result:
The script prints to the standard output a CSV text result. Each row represents a class and provides the list of the pages, components and triggers where the class is used:
When the class is used in several vf pages, the names are separated by a pipe sign.
Here is a command line sample, where Dummy_Org is the name of my Salesforce organization.
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./apex.sh Dummy_Org
class,page,component,trigger
AP01_OpportunityStatus,,,OpportunityAfterUpdate
AP02_AccountHierarchy,,,AccountBeforeCreateUpdate
AP03_OpportunityLost,,,OpportunityAfterUpdate
CO01_mashup_Ctrl,,CO01_mashup,
VF01_AccountNew_Ctrl,VF01_AccountNew,,
VF02_ContractWizard_Ctrl,VF02_ContractWizard,,
VF03_AccountList,VF03_AccountList,,
You might wish to copy/paste the result to Excel and get a fancy layout:
vf.sh
Run the command:
./vf.sh [Directory]
Result:
The script prints to the standard output a CSV text result. Each row represents a visual Force page and provides the list of the Object controller, Apex controller, Apex extension used by the page.
Here is a command line sample:
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./vf.sh Dummy_Org
page,stdcontroller,controller,extensions
VF01_AccountNew,,VF01_AccountNew_Ctrl,
VF02_ContractWizard,,VF02_ContractWizard_Ctrl,
VF03_AccountList,Account,,VF03_AccountList
trigger.sh
Run the command:
./trigger.sh [Directory]
Result:
The script prints to the standard output a CSV text result. Each row represents a trigger and provides the list of the Apex classes used by the trigger:
Here is a command line sample:
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./trigger.sh Dummy_Org
trigger,classes
AccountBeforeCreateUpdate,AP02_AccountHierarchy
OpportunityAfterUpdate,AP01_OpportunityStatus|AP03_OpportunityLost
In this example, the trigger OpportunityAfterUpdate is calling two classes: AP01_OpportunityStatus and AP03_OpportunityLost
components.sh
Run the command:
./components.sh [Directory]
Result:
The script prints to the standard output a CSV text result. Each row represents a component and provides the list of the Apex controller, Apex extension used by the component and the visual force pages where the component is used.
Here is a command line sample:
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./components.sh Dummy_Org
component,controller,extension,page
CO01_mashup,CO01_mashup_Ctrl,,
field_usage.sh
Run the command:
./field_usage.sh [Directory] [field name]
Result:
The script prints to the standard output a CSV text result. Each row represents a meta object where the search field is present.
Columns: object type, object name, number of matches.
Here is a command line sample:
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./field_usage.sh Dummy_Org Segmentation__c
metaObject,name,occurence
applications,Dummy_Org_App,1
classes,VF01_AccountNew_Ctrl,10
classes,VF02_ContractWizard_Ctrl,8
layouts,Account-Customer Layout,1
layouts,Account-Prospect Layout,1
objects,Account,3
objects,Site__c,7
profiles,Account Manager,11
profiles,Admin,12
profiles,ContractManager,11
profiles,MarketingProfile,11
profiles,ReadOnly,11
profiles,Standard,11
triggers,AccountBeforeCreateUpdate,1
Remark 1: as the search relies only on the name, the script will return every occurrence of the file name. E.g. if you have created one custom field “segmentation__c” on the account object and another one on the opportunity object, all the occurrences will be returned, regardless their parent object.
Remark 2: I do not remove the comments in the Apex and Visual force (this will come in a future release). So when the field name appears in a comment it is considered as a valuable match.
objects_dep.sh
Run the command:
./objects_dep.sh [Directory]
Result:
The script prints to the standard output a CSV text result. Each row represents a relationship.
Columns: object 1, type of relationship, object 2.
object 1 as a relationship pointing to object 2.
the type of relationship might be “lookup” for a lookup, and “md” for a master-detail.
Here is a command line sample:
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./objects_dep.sh Dummy_Org
parent,relationship,child
Account,lookup,AccountExecutive__c
Account,lookup,CountryId__c
Contact,lookup,CountryId__c
Town__c,md,CountryId__c
Conclusion
I hope you will find these scripts useful. I am using them almost every day, so I will bring improvements and will post updated versions to this blog. Feel free to give your feedback by dropping a note in the blog comments.
Code review, the big picture
It consists in performing a validation of the work done by a partner, checking:
- technical design,
- code quality, respect of the best practices,
- governor limits,
- etc.
Context
Too often, I do not have an Internet access on the customer site. I am retrieving the Salesforce organization meta data using eclipse and my 3G key; then I am working off line.
What do the scripts?
On the first hand, the scripts provide a high level overview of the code structure: how many classes, pages, components, triggers, where are the classes used, etc. On the other hand two additional tools: find every usage in the org of a field and get all the relationships between the objects (lookup and master/detail).
All the scripts generate a CSV output for an easy integration in Excel, copy/paste from the shell to Excel! (I like this motto)
This is really save my time and let me focus on the review analysis.
Script sources
The code is available under the GPL license 3.0
SOLUTION DISCUSSION
The requirements
My scripts could be replaced by a program that communicates with the API and uses the DescribeObject method. But just keep in mind my two requirements:
- an off line usage
- Keep It Simple
Why a shell script?
The scripts might be optimized, or would be nicer written in a language such as Perl or Ruby rather than a shell script. I experimented several Perl XML parsers but they were slow and required Perl and Unix skills to be compiled, too complex to be shared. The choice of shell scripts appears as the best solution, with an easy deployment regardless the platform:
- Unix or gnu/Linux shell
- Mac OSX shell
- Windows using Cygwin
Awk is also required (provided with these environments).
My working place is gnu/Linux, Ubuntu flavor.
Fill the spaces!
Nature abhors a vacuum. I recommend not using blank spaces in your Eclipse project names. Always prefer an underscore. This is really making sense when it comes to shell scripts. In a shell, all space characters contained in file or directory names should be protected with a backslash:
$ cd Force.com\ IDE
Same issue in a for loop: e.g. the string “Force.com IDE” is processed as two separate files/directories:
$ for i in `echo "Force.com IDE"`; do echo $i; done
Force.com
IDE
The solution is creating symbolic links:
olivier@Ubuntu1:~/Workspaces$ ln -s Force.com\ IDE Force.com_IDE
olivier@Ubuntu1:~/Workspaces$ ls -l
total 4
drwxr-xr-x 21 olivier olivier 4096 2011-03-05 09:55 Force.com IDE
lrwxrwxrwx 1 olivier olivier 13 2011-01-26 10:43 Force.com_IDE -> Force.com IDE
Remark 1: my Eclipse workspaces path is ~/Workspaces
All my projects are located under “~/Workspaces/Force.com_IDE”
Remark 2: the provided scripts will work even if your files/directories names contains spaces because I protected the paths with double quotes.
Preparing the environment
Unzip the archive scripts.tgz and copy the scripts to your “Force.com_IDE” directory, that is where are located your projects directories. This is convenient, the scripts will be able to reach any of your eclipse projects.
To run the scripts, you have to download at least the following meta data:
- classes
- pages
- triggers
- components
- objects
Remark: all the meta data are required for the script field_usage.sh.
To add more meta data to your existing Eclipse project: open the project properties window:
click on the “Add/Remove” button:
choose your meta data:
Code naming convention
I am working with this naming convention:
Visual force pages: VF + [number] + [name].
Triggers: [object name] + [event] + [name]. The name is optional
Apex classes (trigger): AP + [number] + name
Apex classes (controller): VF + [number] + name + _Ctrl. The class name should match with the Visual force page name
Apex classes (others): name
Components: CO + [number] + [name].
Remark: the number are defined in your technical design document (assuming you have one).
Scripts “limitation”
The best approach would had been to work with a code parser, a XML parser and multi lines regular expressions. I choose a “quick & dirty” way, using simple shell commands line:
Pro's
- quicker development
- easy to understand, maintain
- very fast script execution
con's
- hell, no con's, that doing the job pretty well!
THE SCRIPTS
./apex.sh
Run the command:
apex.sh [Directory]
Result:
The script prints to the standard output a CSV text result. Each row represents a class and provides the list of the pages, components and triggers where the class is used:
When the class is used in several vf pages, the names are separated by a pipe sign.
Here is a command line sample, where Dummy_Org is the name of my Salesforce organization.
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./apex.sh Dummy_Org
class,page,component,trigger
AP01_OpportunityStatus,,,OpportunityAfterUpdate
AP02_AccountHierarchy,,,AccountBeforeCreateUpdate
AP03_OpportunityLost,,,OpportunityAfterUpdate
CO01_mashup_Ctrl,,CO01_mashup,
VF01_AccountNew_Ctrl,VF01_AccountNew,,
VF02_ContractWizard_Ctrl,VF02_ContractWizard,,
VF03_AccountList,VF03_AccountList,,
You might wish to copy/paste the result to Excel and get a fancy layout:
vf.sh
Run the command:
./vf.sh [Directory]
Result:
The script prints to the standard output a CSV text result. Each row represents a visual Force page and provides the list of the Object controller, Apex controller, Apex extension used by the page.
Here is a command line sample:
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./vf.sh Dummy_Org
page,stdcontroller,controller,extensions
VF01_AccountNew,,VF01_AccountNew_Ctrl,
VF02_ContractWizard,,VF02_ContractWizard_Ctrl,
VF03_AccountList,Account,,VF03_AccountList
trigger.sh
Run the command:
./trigger.sh [Directory]
Result:
The script prints to the standard output a CSV text result. Each row represents a trigger and provides the list of the Apex classes used by the trigger:
Here is a command line sample:
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./trigger.sh Dummy_Org
trigger,classes
AccountBeforeCreateUpdate,AP02_AccountHierarchy
OpportunityAfterUpdate,AP01_OpportunityStatus|AP03_OpportunityLost
In this example, the trigger OpportunityAfterUpdate is calling two classes: AP01_OpportunityStatus and AP03_OpportunityLost
components.sh
Run the command:
./components.sh [Directory]
Result:
The script prints to the standard output a CSV text result. Each row represents a component and provides the list of the Apex controller, Apex extension used by the component and the visual force pages where the component is used.
Here is a command line sample:
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./components.sh Dummy_Org
component,controller,extension,page
CO01_mashup,CO01_mashup_Ctrl,,
field_usage.sh
Run the command:
./field_usage.sh [Directory] [field name]
Result:
The script prints to the standard output a CSV text result. Each row represents a meta object where the search field is present.
Columns: object type, object name, number of matches.
Here is a command line sample:
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./field_usage.sh Dummy_Org Segmentation__c
metaObject,name,occurence
applications,Dummy_Org_App,1
classes,VF01_AccountNew_Ctrl,10
classes,VF02_ContractWizard_Ctrl,8
layouts,Account-Customer Layout,1
layouts,Account-Prospect Layout,1
objects,Account,3
objects,Site__c,7
profiles,Account Manager,11
profiles,Admin,12
profiles,ContractManager,11
profiles,MarketingProfile,11
profiles,ReadOnly,11
profiles,Standard,11
triggers,AccountBeforeCreateUpdate,1
Remark 1: as the search relies only on the name, the script will return every occurrence of the file name. E.g. if you have created one custom field “segmentation__c” on the account object and another one on the opportunity object, all the occurrences will be returned, regardless their parent object.
Remark 2: I do not remove the comments in the Apex and Visual force (this will come in a future release). So when the field name appears in a comment it is considered as a valuable match.
objects_dep.sh
Run the command:
./objects_dep.sh [Directory]
Result:
The script prints to the standard output a CSV text result. Each row represents a relationship.
Columns: object 1, type of relationship, object 2.
object 1 as a relationship pointing to object 2.
the type of relationship might be “lookup” for a lookup, and “md” for a master-detail.
Here is a command line sample:
olivier@Ubuntu1:~/Workspaces/Force.com_IDE$ ./objects_dep.sh Dummy_Org
parent,relationship,child
Account,lookup,AccountExecutive__c
Account,lookup,CountryId__c
Contact,lookup,CountryId__c
Town__c,md,CountryId__c
Conclusion
I hope you will find these scripts useful. I am using them almost every day, so I will bring improvements and will post updated versions to this blog. Feel free to give your feedback by dropping a note in the blog comments.
Labels:
apex,
awk,
bash,
code review,
salesforce,
script,
shell,
trigger,
visual force
[Salesforce.com] Facebook Apps integration
Introduction
Why social?
Create your Salesforce organization If you are new to Salesforce, sign up for a developer account. It is free, not time limited, and let you test all the features.
Limitation:
- limited storage
- you are not allowed to drive a commercial business on a developer edition
Salesforce online documentation: http://developer.force.com/
Understanding the “Request for permission” process
At the first time the user is getting access to the application he is asked for grants. The set of privileges is pretty wide, and with no granularity for the smallest set: Name, Profile picture, gender, user ID, list of friends, and all your public information (depending your privacy settings). This set may be enhanced. The largest one includes things like “giving access to your friends data”. A large set makes sense when used by a Facebook rich client (e.g. a BlackBerry FB client). But when it came to be used by FB games, you can easily guess that your precious data will fall directly into a commercial marketing database... And if you grant for “giving access to your data at any time” the application owner will be able to update his database at will, even when you are not using his app!
This is why you should be reasonable when asking for grant. Too many grants why make your customers fly away (unless if your target is teenagers; they will always grant for a “cool” app)
Permissions list doc: http://developers.facebook.com/docs/authentication/permissions/
User permission:
user_about_me, user_activities, user_birthday, user_education_history, user_events, user_groups, user_hometown, user_interests, user_likes, user_location, user_notes, user_online_presence, user_photo_video_tags, user_photos, user_relationships, user_relationship_details, user_religion_politics, user_status, user_videos, user_website, user_work_history, email, read_friendlists, read_insights, read_mailbox, read_requests, read_stream, xmpp_login, ads_management, user_checkins, user_address, user_mobile_phone
Friends permission:
friends_about_me, friends_activities, friends_birthday, friends_education_history, friends_events, friends_groups, friends_hometown, friends_interests, friends_likes, friends_location, friends_notes, friends_online_presence, friends_photo_video_tags, friends_photos, friends_relationships, friends_relationship_details, friends_religion_politics, friends_status, friends_videos, friends_website, friends_work_history, manage_friendlists, friends_checkins
Facebook Developer documentation
The official documentation is very weak and scattered, the examples are poor. Check this url:
http://developers.facebook.com/docs/
The most important part is understanding the graph API. This is the way to retrieve the user information. When the user is connecting to your app you get an Oauth token that will be used when calling the Facebook API. All the calls are REST and might be handled with a simple cUrl command line.
The graph API is explained on this page:
http://developers.facebook.com/docs/reference/api
The token is automatically handled by the SDKs. Choose your flavour on http://developers.facebook.com/docs/
For PHP: https://github.com/facebook/php-sdk/
Graph API usage:
Let's work with my own Facebook user Id.
Get a Facebook user Name: https://graph.facebook.com/1697007432
This is a REST call. FB returns a JSON structure:
Get a Facebook user profile picture: http://graph.facebook.com/1697007432/picture
Now let's have a look to the code
Facebook directory: the PHP Facebook SDK
Salesforce directory: the PHP Salesforce SDK
All PHP pages include common.php and config.php
config.php contains the credentials for:
- being recognized by Facebook
- access to the Facebook API
- access to your org through the Salesforce.com API
common.php is making the connections to Facebook and Salesforce.
Facebook
Facebook: if first connection to the app
- ask for the permissions
The permissions list is set in the parameter 'req_perms' (permissions name comma separated)
Then redirect to the application landing page.
Facebook: if no session
- retrieve the user informations required and saved them into sessions variable
Access to the user first name:
Facebook: if a session exists
do nothing
Salesforce
Salesforce: if no session
We are using the partner wsdl.
Connect to the org:
Check if a matching contact is existing. Save the Salesforce ContactId into the PHP session:
Salesforce: if a session exists
connect to the org (I did not find out how to keep the connection to Salesforce in the PHP session)
Now our App
The coding is similar to any PHP web site. No surprise.
Log a Case
An HTML form let your user log a Case. The form is populated with the information we already have: first name, last name, email, and the Salesforce contactId if available.
I assume that you are familiar to this kind of code and will not detailled the process. Just be aware that I had a tricky token in the Salesforce style! This token prevent the web page from being directly called. When posting the form, you have to send the token.
After the user submitted the form, we create the Contact if he does not exist, and then the case. We retrieve the Case Number with a soql query and show the number to the user.
Here is the code that creates the Case:
See my Cases
This page provides the list of the user cases.
Header and footer
Every page includes an header and a footer. The header contains the CSS style that will make your users feel like if they were in Facebook and not a third party application.
Remark
This app is really simple, the goal was to keep the code easily readable and ajustable to meet your requirements.
Conclusion
This tutorial was a proof of concept to show that making the clouds communicate together was easy. Now it is time for you to make your own baby steps in the clouds. Feel free to re use the code and let your imagination be the only limit.
This post is a tutorial that explains how to write a Facebook Application connected to a Salesforce organization.
Basically this is a connection between three Clouds: Facebook, Salesforce and your application.
Audience: non technical & technical profile.
Source code
The code is written in PHP and is available under the GPL license 3.0
Requirements
Basically this is a connection between three Clouds: Facebook, Salesforce and your application.
Audience: non technical & technical profile.
Source code
The code is written in PHP and is available under the GPL license 3.0
Requirements
To understand the example you need:
- basic knowledge of Salesforce
- basic knowledge of designing a dynamic web page
- basic knowledge of HTML/CSS
- background in a programming language (PHP, Java)
To drive through the example, you will also require:
- A Salesforce.com organization: your own Org (Production or Sandbox), or a developer environment. Not a force.com environment, unless you replace the Contact by a Custom Object.
- Your own server, hosting an Apache/PHP Server (my favorite flavor is Linux), must be reachable from the internet
- A domain name (to get access to your App)
The command lines to compile Apache and PHP and the configuration files are detailed at the end of this document.
Every 20 years, the computer industry have major changes that dramatically modify the way the companies are working. In the 60's we had mainframes, in the 80's client/server architectures, in 2000 the applications moved to the clouds. This shift has now moved to platforms, companies can now build applications in the clouds.
This shift impacted as well the way we are communicating: from workgroups to intranet computing, the collaboration has moved to the clouds too: Google, Facebook, Twitter and now Salesforce Chatter.
Today the business value of driving a Facebook page that collected thousands of “likes” is low if it is not connected to your other clouds.
Why social?
Let's assume that you are a proud owner of a B2C business. Most of your customers do not report their remarks, suggestions or complains. Why? The web form on your corporate web site might be not appropriate, or maybe this is not the right channel to collect these requests. Your B2C customers would be more comfortable in a casual environment where they could feel “like home”. Social networks are the answer and Facebook is THE place.
Here is the big picture: your company has a page on Facebook. The wall is dedicated to mass communication, so your customers will not post their requests to the wall ; the reasons:
- a lack of privacy
- you do not want the issues to be posted on your wall and being world readable
How to improve the process?
In this blog post, we will see how to create a Facebook Apps that will collect your customers' queries and push them to your Salesforce Org.
Let's drive through a simple user case. The customer wants to report an issue occurred a product.
Connect to the App
Step 1:
He goes to the App tab in your company Facebook page
First time: he grants the application (this step will be detailed later)
Step 2:
he lands in the App home page, nested in the tab. He did not loose the context. He is still in Facebook and continues receiving notifications and chat messages.
The home page is querying Salesforce to check if the Facebook user is known. We match his Facebook Id with an external field on the contact object.
Home page customization: message “Welcome user.firstname” + show the user profile thumbnail picture
- Known user: message “You have been identified as a customer”
- otherwise: no message
The menu:
1. Log a New Case
2. Browse my Cases
He goes to the App tab in your company Facebook page
First time: he grants the application (this step will be detailed later)
Step 2:
he lands in the App home page, nested in the tab. He did not loose the context. He is still in Facebook and continues receiving notifications and chat messages.
The home page is querying Salesforce to check if the Facebook user is known. We match his Facebook Id with an external field on the contact object.
Home page customization: message “Welcome user.firstname” + show the user profile thumbnail picture
- Known user: message “You have been identified as a customer”
- otherwise: no message
The menu:
1. Log a New Case
2. Browse my Cases
Log a New Case
- a form populated with the Facebook fields: first& last names, email address. All the fields are editable (this let the user switches for his real identity if he is using a fake name in Facebook)
- Case fields: reason & description
- The user clicks on the submit button
Log a New Case, behind the scene
- if the user does not match with an existing contact, we create a new contact, providing his Facebook ID
- We create a new case attached to the Contact (lookup relationship)
- We retrieve the Case Number
- New page: we show the Case Number
The implementation would have been more smart using a Personal Account but I choose the Contact object to keep it simple.
Create your Salesforce organization If you are new to Salesforce, sign up for a developer account. It is free, not time limited, and let you test all the features.
Limitation:
- limited storage
- you are not allowed to drive a commercial business on a developer edition
Salesforce online documentation: http://developer.force.com/
This step should be easy if you do not live in France! First of all, go to the url:
Facebook will check that you are a human being by asking your cell phone number. A token will be sent by SMS in a few minutes. Facebook decided one year ago to stop sending SMS to France. But you are lucky if you are living in the Democratic Republic of the Congo, you will receive the message...
Hopefully, there is a workaround: provide your credit card number! Ha ha! If you are not confident with this part of the process I suggest you to ask to your bank a temporary card number. For 0.5 euro my bank is providing a one time usage card number with a fixed amount and a limited time life.
Then, create your App. FB will provide the tokens required when using the FB API.
Facebook will not host your App: it will be shown in an iframe. All your resources (pages, styles, pictures, etc.) will stay on your server. Your App will call FB with Soap requests using the HTTPS protocol and a client certificate. The certificate is included in the Facebook SDK.
Our architecture
The server: I set up a virtual server using OVH Cloud. They are selling virtual server on demand. The cost is 0.01 euros / hour, and you only pay for the running hours.
The bad side is that the IP & host names change at every boot (but kept if you perform a reboot).
Operating System: Ubuntu 10
Memory: 256Mo
Web server: Apache
Scripting language: PHP
Other architectures
You might as well use a Windows server with IIS and PHP as a module.
The only requirement is that your server must be reachable from the internet.
Connect to your OVH server
Log in your OVH Manager and generate a key to log in as root without a password.
Your key must not be world readable:
chmod 600 mykey.pem
Then you may log in using a shell or a putty:
ssh -i mykey.pem root@mc-111-222-333-444.ovh.net
Apache 2.2.17
./configure --enable-module=rewrite --enable-so
make
make install
PHP 5.3.3
You do not need all these libs. This is my default set but you may reduce it at your convenience. The mandatory lines are in red.
./configure \
--with-apxs2=/usr/local/apache2/bin/apxs \
--enable-ftp \
--enable-bcmath \
--enable-calendar \
--with-jpeg-dir \
--with-png-dir \
--with-gd \
--enable-gd-native-ttf \
--with-freetype-dir \
--with-gettext \
--with-mysql \
--with-zlib-dir \
--with-openssl \
--with-curl \
--enable-exif --enable-soap
make
make install
Back to Apache
Edit the httpd.conf:
Add:
AddType application/x-httpd-php .php .php3
Declare a directory:
<directory>
Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</directory>
Add a virtual host:
<virtualhost>
ServerAdmin webmaster@yourdomain-name.com
DocumentRoot /home/olivier/www/site1
ServerName yourdomain-name.com
DirectoryIndex index.php index.html index.htm
ErrorLog logs/yourdomain-name-error_log
CustomLog logs/yourdomain-name-access_log combined
</virtualhost>
Start/Stop Apache
/usr/local/apache2/bin/apachectl start
/usr/local/apache2/bin/apachectl stop
Understanding the “Request for permission” process
At the first time the user is getting access to the application he is asked for grants. The set of privileges is pretty wide, and with no granularity for the smallest set: Name, Profile picture, gender, user ID, list of friends, and all your public information (depending your privacy settings). This set may be enhanced. The largest one includes things like “giving access to your friends data”. A large set makes sense when used by a Facebook rich client (e.g. a BlackBerry FB client). But when it came to be used by FB games, you can easily guess that your precious data will fall directly into a commercial marketing database... And if you grant for “giving access to your data at any time” the application owner will be able to update his database at will, even when you are not using his app!
This is why you should be reasonable when asking for grant. Too many grants why make your customers fly away (unless if your target is teenagers; they will always grant for a “cool” app)
Permissions list doc: http://developers.facebook.com/docs/authentication/permissions/
User permission:
user_about_me, user_activities, user_birthday, user_education_history, user_events, user_groups, user_hometown, user_interests, user_likes, user_location, user_notes, user_online_presence, user_photo_video_tags, user_photos, user_relationships, user_relationship_details, user_religion_politics, user_status, user_videos, user_website, user_work_history, email, read_friendlists, read_insights, read_mailbox, read_requests, read_stream, xmpp_login, ads_management, user_checkins, user_address, user_mobile_phone
Friends permission:
friends_about_me, friends_activities, friends_birthday, friends_education_history, friends_events, friends_groups, friends_hometown, friends_interests, friends_likes, friends_location, friends_notes, friends_online_presence, friends_photo_video_tags, friends_photos, friends_relationships, friends_relationship_details, friends_religion_politics, friends_status, friends_videos, friends_website, friends_work_history, manage_friendlists, friends_checkins
Facebook Developer documentation
The official documentation is very weak and scattered, the examples are poor. Check this url:
http://developers.facebook.com/docs/
The most important part is understanding the graph API. This is the way to retrieve the user information. When the user is connecting to your app you get an Oauth token that will be used when calling the Facebook API. All the calls are REST and might be handled with a simple cUrl command line.
The graph API is explained on this page:
http://developers.facebook.com/docs/reference/api
The token is automatically handled by the SDKs. Choose your flavour on http://developers.facebook.com/docs/
For PHP: https://github.com/facebook/php-sdk/
Graph API usage:
Let's work with my own Facebook user Id.
Get a Facebook user Name: https://graph.facebook.com/1697007432
This is a REST call. FB returns a JSON structure:
{
"id": "1697007432",
"name": "Olivier Nepomiachty",
"first_name": "Olivier",
"last_name": "Nepomiachty",
"gender": "male",
"locale": "en_GB"
}
Get a Facebook user profile picture: http://graph.facebook.com/1697007432/picture
Now let's have a look to the code
Facebook directory: the PHP Facebook SDK
Salesforce directory: the PHP Salesforce SDK
All PHP pages include common.php and config.php
config.php contains the credentials for:
- being recognized by Facebook
- access to the Facebook API
- access to your org through the Salesforce.com API
<?php
// Facebook
define("FACEBOOK_APP_ID", '123456789012345');
define("FACEBOOK_API_KEY", '112233445566778899aabbccddeeff00');
define("FACEBOOK_SECRET_KEY", 'aabbccddeeff00112233445566778899');
define("FACEBOOK_CANVAS_URL", 'http://apps.facebook.com/ucontainers-support/');
define("DOMAIN", 'mc-111-222-333-444.ovh.net');
include_once './facebook/facebook.php';
// Salesforce
define("USERNAME", "onepomiachty@myorg.com");
define("PASSWORD", "H@ck3r^");
define("SECURITY_TOKEN", "aBcDeFgHiJkLmNoPqRsTuVwXy");
require_once ('./salesforce/SforcePartnerClient.php');
?>
common.php is making the connections to Facebook and Salesforce.
Facebook: if first connection to the app
- ask for the permissions
The permissions list is set in the parameter 'req_perms' (permissions name comma separated)
$url = $facebook->getLoginUrl(array(
'canvas' => 1,
'fbconnect' => 0,
'req_perms' => 'email',
));
Then redirect to the application landing page.
Facebook: if no session
- retrieve the user informations required and saved them into sessions variable
$_SESSION['uid'] = $facebook->getUser();
$_SESSION['me'] = $facebook->api('/me');
Access to the user first name:
$_SESSION['me']['first_name']
Facebook: if a session exists
do nothing
Salesforce
Salesforce: if no session
We are using the partner wsdl.
Connect to the org:
$_SESSION['Sforce'] = new SforcePartnerClient();
$mySforceConnection=&$_SESSION['Sforce'];
$mySforceConnection->createConnection("./salesforce/partner.wsdl.xml");
$mySforceConnection->login(USERNAME, PASSWORD.SECURITY_TOKEN);
Check if a matching contact is existing. Save the Salesforce ContactId into the PHP session:
$query = "SELECT Id, Facebookuid__c from Contact Where Facebookuid__c='".$_SESSION['me']['id']."'";
$response = $mySforceConnection->query($query);
if (count($response->records)==1) {
$record = $response->records[0];
$_SESSION['SforceContactId']=$record->Id;
} else $_SESSION['SforceContactId']='';
Salesforce: if a session exists
connect to the org (I did not find out how to keep the connection to Salesforce in the PHP session)
Now our App
The coding is similar to any PHP web site. No surprise.
Log a Case
An HTML form let your user log a Case. The form is populated with the information we already have: first name, last name, email, and the Salesforce contactId if available.
I assume that you are familiar to this kind of code and will not detailled the process. Just be aware that I had a tricky token in the Salesforce style! This token prevent the web page from being directly called. When posting the form, you have to send the token.
$_SESSION['token']=sha1(time()+$_SESSION['me']['id']);
After the user submitted the form, we create the Contact if he does not exist, and then the case. We retrieve the Case Number with a soql query and show the number to the user.
Here is the code that creates the Case:
// create the case
$fields = array (
'ContactId' => $_SESSION['SforceContactId'],
'Description' => $CaDescription,
'Reason' => $CaReason,
'Origin' => 'Facebook',
'Subject' => '[FB] '.$CaReason
);
$sObject = new SObject();
$sObject->fields = $fields;
$sObject->type = 'Case';
$createResponse = $mySforceConnection->create(array($sObject));
$caseId='';
foreach ($createResponse as $createResult) { $caseId=$createResult; break; } $caseNumber='';
// select the new Case to get the Case Number
$query = "SELECT CaseNumber from Case Where Id='$caseId'";
$response = $mySforceConnection->query($query);
if (count($response->records)==1) {
$record = $response->records[0];
$caseNumber=$record->fields->CaseNumber;
}
$_SESSION['token']='';
Header("Location: CaseLogged.php?CaNumber=$caseNumber");
exit;
See my Cases
This page provides the list of the user cases.
Header and footer
Every page includes an header and a footer. The header contains the CSS style that will make your users feel like if they were in Facebook and not a third party application.
Remark
This app is really simple, the goal was to keep the code easily readable and ajustable to meet your requirements.
Conclusion
This tutorial was a proof of concept to show that making the clouds communicate together was easy. Now it is time for you to make your own baby steps in the clouds. Feel free to re use the code and let your imagination be the only limit.
Presentation
Hi, my name is Olivier Nepomiachty, I am a Technical Architect working in the CRM industry - France. The purpose of this blog is sharing knowledge with the developer community, whoever you are: customer, partner, ISV, freelance developer or just someone who is looking for technical guidance.
This blog is all about CRMs.
My specialties are architecture, integration, coding, single sign-on and performance testing. I am gnu/Linux geek and spent most of my computer time on an Ubuntu system. I am spending the rest of my time with my family (I am a proud father of two tough boys) and practicing ultra running; I love running long distances, days & nights on dirt tracks.
My elder boy. Background: Notre Dame de Paris
Design with The Gimp.
Subscribe to:
Posts (Atom)