A great thing in CiviCRM v3.4 and v4.0 is that for Joomla! users there is a new helper file that makes it very easy to access the CiviCRM API. That combined with version 3 of the API makes it easier than ever to put CiviCRM data anywhere in your site. I'm going to show you how I made a module that shows the groups a logged in user is a member of. Because it uses the API this can display on any page whether or not it is a CiviCRM page.
A few notes before I start. This is going to be a Joomla! 1.6 module. Also, I did have to make a slight change to administrator/components/com_civicrm/helpers/api.php, changing
$this->civiInit( );
to
self::civiInit( );
at line 31. Also I'm going to make this module Joomla! style, which means that it can have a layout override or alternative layout.
The helper file has two functions, CiviInit to get all of the CiviCRM settings and configuration information and civiImport which loads specific APIs.
My module is going to be called mod_mycivigroups. As with most Joomla! modules it has some standard files:
- mod_mycivigroups.php
- mod_mycivigroups.xml
- helper.php
- tmpl/default.php
- language/en-GB/en-GB.mod_mycivigroups.ini
- language/en-GB/en-GB.mod_mycivigroups.sys.ini
And of course language files and index.html files in each folder.
The helper is the best place to start:
// no direct access defined('_JEXEC') or die;
class modMyCivigroupsHelper { // This will get the list of groups the logged in user is a member of. function myCiviGroupsGetGroupContacts() { CivicrmHelperApi::civiimport('api'); CivicrmHelperApi::civiimport('GroupContact'); CivicrmHelperApi::civiimport('UFMatch');
$user =& JFactory::getUser();
$results = null; if (!$user->guest){ $userarray = array ('uf_id' => (string) $user->id, 'version' => '3'); $contact_id = civicrm_api('UFMatch','get',$userarray);
// We can only get groups for people who have a value for contact_id. if ($contact_id['is_error'] == 0){ $params = array('contact_id' => $contact_id['id'] , 'version' => '3'); $results = civicrm_api('GroupContact','get', $params); }
} return $results; } }
Remembering that Joomla is strongly convention driven, the class takes its name from the module. The function starts by using the functions from the component helper to import the base API, the GroupContact API (which gets a list of groups for a given contact ID), and the UFMatch API (which gets the contact ID for a given userid from the CMS).
We can only get the list of groups for users who are logged in so we limit this to people who are not guests.
Reading the API we then make a parameters array of the data the API needs, in this case the user ID and the version of the API. The API itself always takes three arguments: the API name (entity), the action, and an array of parameters. Then we use the API wrapper
$contact_id = civicrm_api('UFMatch','get',$userarray);
to get the contact_id. However, we don't only get the contact_id because API v3 always returns an array with the same structure. In this case $contact_id['id'] is the id itself, $contact_id['is_error'] is 0 on success and 1 on failure, and $contact-Id['count'], the number of results, which in this case should be 1 if we find a maching contact.
Next we check to see if there was actually a result using $contact_id['is_error'] and if so, we use the GroupContact API to get the list of groups the contact is a member of.
In this case the $params are just the id and the API version.
Now taking a step back, the mod_mycivigroups.php files consists of:
//don't allow other scripts to grab and execute our file defined('_JEXEC') or die('Direct Access to this location is not allowed.'); /** * *This assumes contacts and users have been synched so all Joomla! users should have contact_id values. */
//Get the helper and include the files needed for group contact and UF Match
require_once JPATH_ROOT . '/administrator/components/com_civicrm/helpers/api.php';
require_once dirname(__FILE__).'/helper.php';
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx'));
$results = modMyCiviGroupsHelper::myCiviGroupsGetGroupContacts();
require JModuleHelper::getLayoutPath('mod_mycivigroups', $params->get('layout', 'default'));
Which is a standard Joomla! module file with the addition of
require_once JPATH_ROOT . '/administrator/components/com_civicrm/helpers/api.php';
and
$results = modMyCiviGroupsHelper::myCiviGroupsGetGroupContacts();
which are similar to what you would see in many modules.
Finally, we take the results and use them in our layout file, default.php (in the tmpl folder).
// no direct access defined('_JEXEC') or die('Direct Access to this location is not allowed.'); ?> <?php $user =& JFactory::getUser();
// You have to be logged in to show your groups. if (!$user->guest): // Not in a group so nothing to list. if ($results['count'] == 0): echo JText::_('No_Groups'); else: $values = array_slice($results['values'], 0, $params->get('number', 5)); ?> <?php endif; // Really a webmaster probably would use the viewing access level to // make sure this isn't needed, but you never know. else : echo JText::_('Not_Logged_In'); endif;
For people who are logged in, we check the results and if the count was 0, post a message. If the count is more than 0 we make an unordered list of groups up to some maximum number (which is set in the module parameters). You want a maximum since listing say 100 groups is probably going to mess up your page. Finally in case there is not a logged in user we gracefully say something, just in case the webmaster is showing the module to them.
So now you can show a user's groups on any page. And great for Joomla! users you can do either a template override or (in 1.6) an alternative layout for the module. Which means you can make the HTML whatever you want and do your own CSS easily.
You can download the complete module from joomlacode.org.
What would be great is to get a whole bunch of ready made modules for common things we would like to display on all pages (such as upcoming events, memberships, campaigns etc.).
Comments
Just a quick note.
if ($contact_id['is_error'] == 0){
$params = array('contact_id' => $contact_id['id'] , 'version' => '3');
$results = civicrm_api('GroupContact','get', $params);
}
'is_error' = 0
indicates no error - there may still be no matches.
$contact_id['count'] should tell you how many matches are found.
If you are using an API that returns 'is_error' => 1 just because there are no matches you should treat it as a bug
In the layout you see
if ($results['count'] == 0):
echo JText::_('No_Groups');
else:
Which deals with the non error result that there is a matched contact who has no groups. The code you quote prevents attempting to use the GroupContact API when here is no valid contact_id which obviously can't work.
So this code doesn't do what you say, all it does is gracefully fail when the user isn't linked to a contact rather than show an error message. Civi is kind of known in the joomla world for unnecessarily showing disaster messages when it just needs to say "no linked contact" or "not logged in" and this code addresses that by instead return calm messages that tell the user what is going on.
I was looking at
$userarray = array ('uf_id' => (string) $user->id, 'version' => '3');
$contact_id = civicrm_api('UFMatch','get',$userarray);
// We can only get groups for people who have a value for contact_id.
if ($contact_id['is_error'] == 0){
I guess it's hard to see how this could give no error & no contact (unless for some reason $user->id was empty in which case it *might* ).
So if UFMatch finds no match that actually does return an error, so you might consider that a bug, i don't know. That's the current behavior but if it changes this code would be be
The point is that if there is not a valid contact_id then you shouldn't go searching for the groups for that non existant contact. This is saying if there is no error--- that there is a contact-- go ahead and get the groups.
So if UFMatch finds no match that actually does return an error, so you might consider that a bug
Yes, that was my point - the standard behaviour is that if no match is found then count should be 0 but so should is_error. If an api doesn't do this then you should possibly file an issue but certainly assume in your code that at some point it may be fixed. (NB I took a quick look at the code & can't see why it would do this. I also removed the mandatory field check as there is no reason for the api not to search on other fields in the table.
-
There are a couple of crucial missing lines in the post. In the layout,
Should be:
Wow more raw html than I have written in a long time.
I just can't find this file to edit, and the module isn't finding it either? I have 4.0.1 installed?
administrator/components/com_civicrm/helpers/api.php,
It's there in 4.0.1 but it's really different.
Any suggestions on where to adjust this. I continually get an error Warning: require_once(api/v3/utils.php) [function.require-once]: failed to open stream: No such file or directory in /home/civi/public_html/administrator/components/com_civicrm/civicrm/api/v3/Contact.php on line 43
Fatal error: require_once() [function.require]: Failed opening required 'api/v3/utils.php' (include_path='.:/usr/lib/php:/usr/local/lib/php') in /home/civi/public_html/administrator/components/com_civicrm/civicrm/api/v3/Contact.php on line 43
I am guessing it has something to do with not initializing? I would really love to figure this out so I can help write some modules that would be of great use.
Elin,
I noticed that in the helper/api.php file you have:
define('CIVICRM_SETTINGS_PATH', JPATH_BASE . DS . 'administrator' . DS . 'components' . DS . 'com_civicrm' . DS . 'civicrm.settings.php');
But in case of backend application JPATH_BASE will already contain the 'administrator' part.
This would make this define wrong and would cause failure in the
require_once CIVICRM_SETTINGS_PATH;
Is this the case, or am I reading things wrong?
Nick