I use a lot of open source software for day-to-day jobs and in the same spirit as the software, I'd like to share some of my knowledge with the community.
So far I'll be covering drupal, sugarCRM and civiCRM among others. I seem to write hundreds of modules a year. Some for a specific purpose which would probably not help much but others extend the standard software in some pretty weird and wonderful ways.
If there's something specific that anyone needs, just shout and I'll have a look in the module library (see: collection of messy folders in hundreds of project files).
Various aspects of integrating drupal with CiviCRM on a real life project. I'm getting more and more involved with the development of Civi so I may need to post a few links to pages on civicrm.org. Please bear with me as things develop.
There is an official page on civicrm's wiki site to document many of the possible changes for civicrm v3.0. As a result, this page is simple a brainstorm I'm working on and not an indication of any comfirmed features for civicrm.
Disclaimer out of the way, time for some ideas. I have been working closest with the events system which will probably skew the focus of this page. I'll start with the list I've slowly been adding to.
I will add to this list as and when I can and please add your 2 pence in the comments.
After finishing a chunk of changes for a client to make changing registration names after submitting your form, I then had to tweak things further.
As It stands I haven't yet solved the problems that arose. I posted my original article on civicrm's website but I have now included it below. After the article I will provide my current problems, questions and solutions.
I recently finished writing an addition to the events system that will allow pa's etc to book places for people to events and then return at a later date to confirm the names. As far as I know, this is in progress for v2.3 but basically I need it now. Apart from some testing the system should work without too much trouble.
There is only one issue that will perhaps need addressing and that is anonymous signups. As far as I know, CiviCRM will create a new contact record for the person booking if need be and the existing system is preserved so things should work fine. There is an extra button which will make this system work on additional participant pages. Over the next few days I'll test this with v2.2 and see if it is worth getting into the core.
How did I do it I hear you say. Well make yourself comfy and I'll tell.
At the heart of it is an extra button on the additional participant pages. It says something like 'Just reserve place' and I check for this in postProcess() for the form. I also make sure the amount field IS required rather than ignoring it like you do for a skipped one. Then in AdditionalParticipant->postProcess() I set the email of the skipped participant to the same as the booking contact.
I made a small change to CRM_Event_Form_Registration->addParticipant so that the booker's contact_id can be added multiple times to an event. After that it was a case of providing a new link on the EventInfo page to the register update page I created which allowed you to only change the email addresses of your additional participants. Any new emails have a contact created for them and the record in the civicrm_participant table is updated to reflect the new id.
That about sums it up really. I think there may be a little checking I need to do with the change I made in addParticipant to make sure a user can't force extra participants in by revisiting the same page. Other than that, it seems to work.
I have created a patch so this can be tested. The patch should work against the 2.1 branch from the subversion repository but there's a good chance it will work with the tarball too. Obviously this will change files so when patching, please make sure you BACKUP FIRST. You can see the files altered from a quick scan of the file.
I made the patch against the core files so if you have tailored them to suit in a custom directory you might want to apply the changes manually.
note
This patch will NOT make the menu item it needs. You'll have to do that manually I'm afraid, in the database. The path in use is: civicrm/event/updateregister with the usual query string e.g. id=2&reset=1&action=preview will get you event 2 in test mode.
File civi_2_1_delayed_participants.patch
I need to check the form when submitted. Rather than adding the new email into the system as a contact if it doesn't exist, I need to ask for a name in these cases. I thought it wouldn't be that hard but it is proving rather tricky.
I've tried working with the elements in a group and failed miserably after several hours of trying. I now have the items as individual elements. I can set errors on these using a formRule. I would have thought that I could get the error by just using element->getElementError() but this draws a blank and so is the _errors array for the form.
I now have it that an error is set using setElementError() which may not be needed soon. I also store the names of the failed elements in a session variable. Then when rebuilding the form, I check whether it has been submitted or not and if so I can check for the error each time I put an element into the form.
Wish it was easier.
The situation as it stands now is; it's working. It's not pretty but it does the job. I don't know why it took me so long to work out what I was doing wrong but to clarify I'll go through the form lifecycle for a sec.
When a form is built methods get called in order:
preProcess -> buildQuickform -> setDefaultValues -> addRules -> display()
The display method is that of the QuickForm class.
This works for any form and when a form is submitted the form is rebuilt, just the same as it is in drupal. Here's the sequence:
preProcess -> buildQuickForm -> setDefaultValues -> addRules -> validate -> postProcess
With this in mind, if a form does not validate things go like this:
preProcess -> buildQuickForm -> setDefaultValues -> addRules -> validate -> display()
At the moment there is no easy way of doing something to the form AFTER it has failed validation because you build the form 3 steps earlier and errors are caught and highlighted later on.
My solution is ugly and I'm not proud of it but I pass in the form to the validation method and make changes there if I need to. Very hacky and if I can come up with a more elegant solution then I certainly will document it here or somewhere on this site.
I think perhaps a hook would be nice, especially if it was optional. Similar to the #after_build or #pre_render hooks from drupal.
In the meantime, there are other things I need to build/fix.
I'm currently working on a project that is heavily involved with the inner workings of drupal and CiviCRM. In the course of this I am increasingly needing to write my own hooks and calls to CiviCRM from within a drupal module. I agree that the api could get bloated and full of unnecessary functions but the fact remains, I need the data. So begins my quest to extend the tentacles of a drupal module into CiviCRM.
Features that are currently either in progress or at a point where they do the job they should and are waiting for refinement. None of this code is security tested although I do my best to keep things safe from the script kiddies.
Membership signup / Contribution block - displays a link to the contribution page.
Upcoming events block - displays a simple list of upcoming events
Events additions:
To come are:
I was hoping for a module or set of modules I could release but things are getting a little too bespoke at the moment. On the other hand, I can certainly provide the code for reference if anyone requests it. Just drop me an email or contact me from here and I'll send you a copy.
I'm afraid this is a blog about a blog. I have a bit of a task ahead of me and part of which is integrating CiviCRM with Sage. I have a few questions I'd like to put to the community so the best place for it is to ask directly on the CiviCRM site. You can have a good read here
UPDATE:
It seems I have more things to write about so here is another blog about possible future development ideas for CiviCRM here
While I'm not overly competent with CiviCRM I recently needed to work on the content of a page from within a drupal module. The hook system seems to be in active development but as of v2.1.2 it hasn't got to the point of exposing the template to the hook api. I added a few lines of code to enable this. I also files a feature request for version 2.2 so hopefully it'll be added for that.
During the meanwhilst here's my effort:
edit CRM/Core/Page.php
first add
<?php
require_once 'CRM/Utils/Hook.php';
?><?php
function run( )
{
if ( $this->_embedded ) {
return;
}
self::$_template->assign( 'mode' , $this->_mode );
self::$_template->assign( 'tplFile', $this->getTemplateFileName() );
if ( $this->_print ) {
if ( $this->_print == CRM_Core_Smarty::PRINT_SNIPPET ||
$this->_print == CRM_Core_Smarty::PRINT_PDF ) {
$content = self::$_template->fetch( 'CRM/common/snippet.tpl' );
} else {
$content = self::$_template->fetch( 'CRM/common/print.tpl' );
}
if ( $this->_print == CRM_Core_Smarty::PRINT_PDF ) {
require_once 'CRM/Utils/PDF/Utils.php';
CRM_Utils_PDF_Utils::domlib( $content, "{$this->_name}.pdf" );
} else {
echo $content;
}
exit( );
}
$config =& CRM_Core_Config::singleton();
// Adding hook to drupal
CRM_Utils_Hook::pageProcess(self::$_template);
$content = self::$_template->fetch( 'CRM/common/'. strtolower($config->userFramework) .'.tpl' );
echo CRM_Utils_System::theme( 'page', $content, true, $this->_print );
return;
}
?>Now we add a corresponding hook function in CRM/Utils/Hook.php
Add this before the end of the class.
<?php
static function pageProcess( &$page ) {
$config =& CRM_Core_Config::singleton( );
require_once( str_replace( '_', DIRECTORY_SEPARATOR, $config->userHookClass ) . '.php' );
$null =& CRM_Core_DAO::$_nullObject;
return
eval( 'return ' .
$config->userHookClass .
'::invoke( 1, $page, $null, $null, $null, $null, \'civicrm_page\' );' );
}
?>And now in your module you can write your hook.
<?php
function MODULE_NAME_civicrm_page(&$page) {
$someVar = "Hello World";
$page->assign("someVar", $someValue);
}
?>The only thing remaining is to add this to the template which will look something like {$someVar}
IMPORTANT:
At this point we are not checking what page we are using. I suggest looking at the smartyDebug=1 info to find out what makes your page unique and check for it OR just use the arg() from drupal to get your url. This method will add the var to all pages of your site although it will not be displayed unless it is added to you template.
Firstly, congratulations all the book sprinters. You did well. I'll be having a good read of it this weekend.
Before I delve into some code I'd like to give you my motivation for doing what I'm about to describe.
I need image uploading, ease of use and consistency. I also don't want to bother with maintaining 2 versions of fckeditor. I knew it shouldn't be too hard to get it working across to 2 systems and to be honest it wasn't. It took a little more to find out what the Image assist module needed though.
A nice feature of fckeditor is that we get a nice button in the editor toolbar instead of the link below the textarea as it would normally be. To get fckeditor and image assist working in drupal together you will need to copy img_assist_fckeditor.js from fckeditor module folder into img_assist folder. If you get a white empty popup screen then you have missed this step out.
On to some actual coding. Please make sure your drupal modules are all working before trying this for your own sanity's sake.
Rather than giving you chunks to piece together, here are the highlights and you can see the rest in the patch below.
I check for the Drupal userFramework and that it has fckeditor installed. We then alter the location of the fckeditor javascript files.
Later on I add the location to the drupal fckeditor's config file: fckeditor.config.js I altered this config file a bit to have some control over the buttons when used in a CiviCRM context.
Apart from setting a few more paths, that's all you need for fckeditor alone.
To add the Image Assist capability I had to trick it a bit. The standard thing to do for civicrm and fckeditor is to have a hidden input in the form but I needed a hidden (display:none) textarea as part of the javascript checking makes sure the element exists and is hidden by the sylesheet. The only other thing is that cunningly hidden <a> tag. I had to do this because the href is read by the javscript of fckeditor's toolbar button and it also points the image assist's output to the correct editor window.
Although I only did this today, I'm not sure why I added: $this->Config['TextareaID'] = $name; If someone finds out if it does anything in this situation then feel free to comment and I'll update if needed.
Now all you need is the patch and my additions to the drupal config file.
Index: fckeditor.php
===================================================================
--- fckeditor.php (revision 990)
+++ fckeditor.php (revision 1494)
@@ -187,33 +188,75 @@
if (!defined('HTML_QUICKFORM_FCKEDITOR_LOADED')) {
// load FCKeditor
$config = CRM_Core_Config::singleton( );
- $html = sprintf(
- '<script type="text/javascript" src="%s"></script>',
- $config->resourceBase . $this->BasePath . 'fckeditor.js'
- );
+ if($config->userFramework == 'Drupal' && module_exists('fckeditor')) {
+ $html = sprintf(
+ '<script type="text/javascript" src="%s"></script>',
+ drupal_get_path('module', 'fckeditor') . '/fckeditor/fckeditor.js');
+ } else {
+ $html = sprintf(
+ '<script type="text/javascript" src="%s"></script>',
+ $config->resourceBase . $this->BasePath . 'fckeditor.js'
+ );
+ }
define('HTML_QUICKFORM_FCKEDITOR_LOADED', true);
}
-
- $config =& CRM_Core_Config::singleton();
-
- // make link for iframe src
- $link = $config->resourceBase . $this->BasePath . 'editor/fckeditor.html?InstanceName=' . $name;
-
- if (strlen($this->ToolbarSet)) {
- $link .= '&Toolbar=' . $this->ToolbarSet;
+ $config =& CRM_Core_Config::singleton();
+
+ if($config->userFramework == 'Drupal' && module_exists('fckeditor')) {
+ global $base_url;
+ $fckPath = drupal_get_path('module', 'fckeditor');
+ // make link for iframe src
+ $link = '/' . $fckPath . '/fckeditor/editor/fckeditor.html?InstanceName=' . $name;
+
+ if(module_exists('img_assist')) {
+ drupal_add_js(drupal_get_path('module', 'img_assist') .'/img_assist.js');
+
+ drupal_add_js($fckPath .'/fckeditor/fckeditor.js');
+ drupal_add_js($fckPath .'/fckeditor.utils.js');
+ $html .= sprintf('<a href="%s" id="%s" style="display: none;"
+ />Add Image</a>',
+ htmlspecialchars($base_url . '/img_assist/load/fckeditor?textarea=' . $name),
+ 'img_assist-link-' . $name, $name);
+ $this->Config['TextareaID'] = $name;
+ }
+
+ $link .= '&Toolbar=' . 'DrupalCiviCRM';
+
+ // render the linked hidden field
+ $html .= sprintf('<input type="textarea" id="%s" name="%s" value="%s" style="display: none;"/>',
+ $name, $name, htmlspecialchars($this->getValue()));
+
+ $this->Config['CustomConfigurationsPath'] = '/' . $fckPath . '/fckeditor.config.js';
+ // render the config hidden field
+ $html .= sprintf('<input type="hidden" id="%s" name="%s" value="%s" />',
+ $cname, $cname, $this->_getConfigFieldString());
+ // render the editor iframe
+ $html .= sprintf(
+ '<iframe id="%s" src="%s" width="%s" height="%s" framborder="no" scrolling="no"></iframe>',
+ $name . '___Frame', $link, $this->Width, $this->Height
+ );
+
+ } else {
+
+ // make link for iframe src
+ $link = $config->resourceBase . $this->BasePath . 'editor/fckeditor.html?InstanceName=' . $name;
+
+ if (strlen($this->ToolbarSet)) {
+ $link .= '&Toolbar=' . $this->ToolbarSet;
+ }
+
+ // render the linked hidden field
+ $html .= sprintf('<input type="hidden" id="%s" name="%s" value="%s" />',
+ $name, $name, htmlspecialchars($this->getValue()));
+ // render the config hidden field
+ $html .= sprintf('<input type="hidden" id="%s" name="%s" value="%s" />',
+ $cname, $cname, $this->_getConfigFieldString());
+ // render the editor iframe
+ $html .= sprintf(
+ '<iframe id="%s" src="%s" width="%s" height="%s" framborder="no" scrolling="no"></iframe>',
+ $name . '___Frame', $link, $this->Width, $this->Height
+ );
}
-
- // render the linked hidden field
- $html .= sprintf('<input type="hidden" id="%s" name="%s" value="%s" />',
- $name, $name, htmlspecialchars($this->getValue()));
- // render the config hidden field
- $html .= sprintf('<input type="hidden" id="%s" name="%s" value="%s" />',
- $cname, $cname, $this->_getConfigFieldString());
- // render the editor iframe
- $html .= sprintf(
- '<iframe id="%s" src="%s" width="%s" height="%s" framborder="no" scrolling="no"></iframe>',
- $name . '___Frame', $link, $this->Width, $this->Height
- );
return $html;
}
}//This toolbar should work fine with "Filtered HTML" filter
FCKConfig.ToolbarSets["DrupalCiviCRM"] = [
['Source'],
['Cut','Copy','Paste','PasteText','PasteWord'],
['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
/*
* EXPERIMENTAL
* Uncomment the line below to enable linktonode and linktomenu buttons
* ATTENTION: Link to Content module must be installed first!
* Remember to load appropriate plugins with FCKConfig.Plugins.Add command a couple of lines above
*/
//['Link','Unlink','LinkToNode','LinkToMenu','Anchor'],
['Link','Unlink','Anchor'],
['Image','Flash','Table','Rule','Smiley','SpecialChar'],
'/',
['FontFormat'],
['Bold','Italic','Underline','StrikeThrough','-','Subscript','Superscript'],
['OrderedList','UnorderedList','-','Outdent','Indent'],
//as of FCKeditor 2.5 you can use also 'Blockquote' button
//['OrderedList','UnorderedList','-','Outdent','Indent','Blockquote'],
['JustifyLeft','JustifyCenter','JustifyRight'],
//uncomment this line to enable the page break button
//remember to load appropriate plugin with FCKConfig.Plugins.Add command a couple of lines above
//['JustifyLeft','JustifyCenter','JustifyRight','DrupalBreak','DrupalPageBreak'],
] ;And that's it. Please let me know what you think. The diff was run directly against the file in question and not from the civicrm installation root.
I think some of the work I am doing for CiviCRM is getting lost amongst the myriad pages in the civicrm.org's site. I'll try and define a few links here to important things I have been working on or are essential for Civi development.
http://forum.civicrm.org/index.php?topic=7239 A new way of getting and using user data
http://civicrm.org/node/503 Things I think the event system is missing
http://civicrm.org/node/511 Placeholder participant names - for PA's to use etc.
http://civicrm.org/node/524 Waiting Lists - this is making it into core.
I had to add some price sets to our recent project and I think I managed to get both the existing system and the price set system to work side by side. To give you an idea of why I had to do it, think of this; you have a specific membership open to organisations, the amount that the organisation needs to pay to join is based on its size. A company of 2-4 people will pay less than a 300+ employee company.
How did I do it? I added a system similar (and borrowed code) from the events system. I used the custom fields and not the price sets as defined in events. When I had the prices available I had to implement a complicated radio button system (complicated in code not usability) to get the price chosen. This means that if a price set is available, it will be shown and can be selected as a sub set of the main membership options.
I guess the best thing to illustrate this may be a picture. We haven't finished the theme yet so here is a VERY small glimpse of what we have set up.
Drupal is a fantastic CMS which I have been actively developing for, for some time now. Yesterday was the first time I had really ventured into the Ajax territory but I learnt a lot and hopefully this guide should help others.
Currently this example is Drupal 5.x only. I will update it for Drupal 6 in another post in due course.
I'm going to create a simple module to demonstrate the communication between client and server. There is already a module out there that does more or less exactly what this demo will do so I'd advise using that if you require the same functionality.
The demo module will be a simple username checker for new registrants. We will add a button to the registration form which will query the server for an existing user of that name. If it already exists we put up a warning otherwise we display a confirmation that they can safely use that name.
So, to begin, we will need the .info file which looks something like this:
; $Id$
name = Jquery Example
core = "5.x"
description = Quick example JQuery module. Adds a name checker to the register page.
version = "5.x-1.00"Okay so now we want to create the .module file. We need 3 things: we need to hook into the registration page, a menu callback for our jquery and a function for it to call.
We'll start with the menu system.
<?php
/**
* Implementation of hook_menu()
*/
function jquery_example_menu($may_cache) {
$items = array();
if($may_cache) {
$items[] = array(
'path' => 'usercheck',
'callback' => 'jquery_example_usercheck',
'access' => 1,
'type' => MENU_CALLBACK,
);
}
return $items;
}
?>This should be fairly straightforward if you're familiar with writing modules.
Next stop, the callback function we just defined.
<?php
function jquery_example_usercheck() {
$name = check_plain($_POST['theName']);
// Query the database.
$result = db_num_rows(db_query("SELECT uid FROM {users} WHERE name = '%s'", $name));
$reply = array();
// different replies based on result.
if($result > 0) {
// user exists.
$reply['exists'] = true;
$reply['replyString'] = t('Sorry, username %theName has already been taken', array('%theName' => $name));
} else {
$reply['exists'] = false;
$reply['replyString'] = t('Username %theName is available.', array('%theName' => $name));
}
print drupal_to_js($reply);
exit();
}
?>So what have we done here? We grab the username from the POST array and make sure it's safe to use with our db query by using check_plain(). Then we do a very simple query to see if the name exists and return the number of rows of that name. We should never get more than one but a simple check to see if it is greater than zero will suffice.
The last call before the exit() is the one we are really interested in. This handy method will convert the array we sent it into a JSON formatted string. This is very important as we will need to undo this action on the client's machine to reconstitute the data. For more information on JSON and why we use it see here
If you're happy (and you know it) that you understand everything so far, the next step will be to hook into the registration form.
<?php
/**
* Implementation of hook_form_alter()
*/
function jquery_example_form_alter($form_id, &$form) {
drupal_add_js(drupal_get_path('module', 'jquery_example'). '/jquery_example.js');
drupal_add_js(array(
'username_check' => array(
'ajaxUrl' => url('usercheck', null, null, true),
'msgWait' => t('Checking username availability ...')
),
), 'setting');
if($form_id == 'user_register') {
// we want to alter the form to add button and a placeholder for our reply
$form['checkUser'] = array(
'#type' => 'button',
'#value' => t('Check availability'),
);
$form['replyMessage'] = array(
'#type' => 'markup',
'#prefix' => '<div id="reply-message">',
'#value' => t("Feel free to check your username before proceeding"),
'#suffix' => '</div>'
);
}
}
?>At the top if this function we are adding our as-yet non-existent javascript file plus a couple of settings. These settings have been lifted almost directly from the username_check module
There's not much else to this other than a quick mention that we are passing $form by reference so any changes we make to $form will be passed back to the originator.
We can now move on to the exciting bit; the javascript. JQuery is built in to the core of Drupal but I can recommend getting hold of the jquery_update module to make sure you have a more recent version. In Drupal 6 it is up to date at the time of writing.
Create a new file called jquery_example.js and add this code:
if (Drupal.jsEnabled) {
$(document).ready(function() {
$("#edit-checkUser").bind("click", function() {
var username = $("#edit-name").val();
$.post(Drupal.settings.jquery_example.ajaxUrl,
{theName: username},
function (data) {
var result = Drupal.parseJson(data);
var message = $("#replyMessage");
message.html(result['replyString']);
message.show();
});
return false;
});
$("#replyMessage").hide();
});
}Lets go through this. Firstly we find out if javascript is enabled for the site with the line:
if (Drupal.jsEnabled) {
Then we ask JQuery to perform our function when the document is loaded and ready.
$(document).ready(function() {
$("#edit-checkUser").bind("click", function() {The second line of this will tell JQuery to find the item with ID of 'edit-checkUser' (remember that Drupal adds 'edit-' to form elements) and then bind the click event to a function declared straight after.
var username = $("#edit-name").val();
$.post(Drupal.settings.username_check.ajaxUrl,
{theName: username},
function (data) {
var result = Drupal.parseJson(data);
var message = $("#replyMessage");
message.html(result['replyString']);
message.show();
});
return false;So what are we doing? We grab the name that has been typed into the username textfield 'edit-name' by using the JQuery val() function.
We now do a call to the server with $.post(). On the JQuery website www.jquery.com the signature of this function is:
jQuery.post( url, [data], [callback], [type] )
Items in [] brackets are optional. If you look at this and apply it to our call you will see that we are reading the url from the Drupal.settings object. After this we send some data as an object {theName: username} as key-value pairs and then our callback will be the next function declaration.
function (data) {
var result = Drupal.parseJson(data);
var message = $("#replyMessage");
message.html(result['replyString']);
message.show();
}Very simply this will parse the reply using Drupal's JSON parser helper and we can do what we want once we have the information back. In this case we find the message div we set up in the form and was previously hidden with $("#replyMessage").hide(); declared further down the page just inside the $(document).ready() function. Once we have the message box we set the value and tell it to unhide itself with .show();
So there you have it. A simple ajax call to the server. Perhaps as an exercise you might like to see the effects of this javascript code:
if (Drupal.jsEnabled) {
$(document).ready(function() {
$("#edit-checkUser").bind("click", function() {
var username = $("#edit-name").val();
$.post(Drupal.settings.username_check.ajaxUrl,
{theName: username},
function (data) {
var result = Drupal.parseJson(data);
var message = $("#replyMessage");
message.html(result['replyString']);
message.removeClass('username-message-progress');
if(result.exists === true){
message.removeClass('username-accepted');
message.addClass('username-rejected');
}
else{
message.removeClass('username-rejected');
message.addClass('username-accepted');
}
message.show();
});
return false;
});
$("#replyMessage").ajaxStart(function(){
$(this).html(Drupal.settings.jquery_example.msgWait);
$(this).removeClass('username-accepted');
$(this).removeClass('username-rejected');
$(this).addClass('username-message-progress');
$(this).show();
});
$("#replyMessage").hide();
});
}Note the new classes being added. You'll need to add these to your css file for your theme. Also the .ajaxStart() function is useful for doing things once a call to the server is being made. Disabling buttons and altering messages is a good way to inform the user that something is going on.
That's all for now. Any questions please leave a comment or contact me from the contact page.
I've got a few clumps of code that is slowly making a nice library of useful classes for actionscript on the go. I'll post it and anything of a flash based nature here.
If you have anything you wish to add to the code here, please feel free to get in touch and I'll update the associated article.
I've been messing with a remote object class that I should really split into a reusable part and project specific code (the do say encapsulate that which changes). Essentially I'm using the features provided by the mxml RemoteObject class to create multiple asynchronous calls to different services using the one main object.
By doing this I'm hoping that by simply switching the service source I can use the services module from drupal to connect and use all sorts of drupal functionality over amfphp.
It's working well so far but as I said above, I just need to split things up a bit. Once that's done, the code will be here for perusal.
A while ago we were charged with the task of using drupal with SugarCRM. Unlike CiviCRM, these 2 systems have never been designed to work together nicely. Rather than hack into core code to share a user table we decided to take advantage of the existing SOAP functionality that SugarCRM affords us.
Unfortunately we had to alter about 2 lines of SugarCRM's core code to get this to work. Also I don't think the code is as secure as we would like it. There is a point at which the password is sent (although encrypted) via a GET request. This would not be our preferred choice.
As a result of these imperfections I am not posting the full module here. If you would like a copy or would like to help the continuing development of this project, please get in touch and I'll see what I can do.
Where to begin? There's a lot of fundamental knowledge which helps the Drupal learning curve but I've found some rules that I stick as close as possible to, to help me through the day.
There's a good reason to not alter the core files. Its called maintenance. Drupal allows a developer to keep custom code in only one or two locations which makes replacing core files that much easier.
If you have to alter the core files, DOCUMENT IT. A small text file for your own purposes in the root of the site could really help. Call it something obvious like README.txt or MODIFICATIONS.txt or whatever you choose, then have a clear readable structure to the doc to help you out. Here's an example:
Notes on modifications to core and standard modules
=============
CONTENTS
=============
1) Taxonomy.module (core)
2) Profile.module (core)
3) Video.module
Appendix
A) Sites Affected
B) Misc Notes
=====================
1) Taxonomy.module
We changed this module so that the taxonomy ID shows up in the list terms page for categories table.
2) Profile.module
Changed this module to automatically filter out the neurotic ravings of teenage schoolgirls.
3) Video.module
Code has been altered to prevent random deletion of the converted video files. See line 890.
=============
APPENDIX
=============
A) Sites affected
www.example.com
www.notonyournelly.com
B) Misc Notes
Some sites have had ECT to prevent them working on IE 6 it seems. Must sort this out and get the themes some counselling.There are so many script kiddies around now that we constantly have to keep our guard up. In the past we had several Joomla sites compromised because the clients did not keep up maintenance or want to have any kind of support package in place so we would do it. By documenting any core module changes you can update to the latest, most secure version at a moment's notice.
When writing your code. Never output submitted text directly to the screen and never use it without checking it first in an SQL query. Let me show you a non-drupal specific example.
You are creating a login box of your own for some reason. The user has the usual username and password fields. The values of these fields go straight into a db query.
Normal user
username: bob
password: halibutWhen we get to the query:
SELECT uid FROM users WHERE name = 'bob' AND password = 'halibut'Now for the malicious user
username: admin
password: ' OR 'a' = 'aIt may look odd but see what happens
SELECT uid FROM users WHERE name = 'admin' AND password = '' OR 'a' = 'a'And there you have it. An instant admin login.
That is, things a drupal developer might benefit knowing or doing.
I started writing this post a few weeks ago and when clicking submit, my internet connection dropped off and I subsequently lost it all. I'll try and capture the essence of my first post but with added readability.
As a rule I try and comment every function as I go along. It is of course possible to reach the point of overkill with comments. If I am implementing a hook I say so with the simple line Implementing hook_something()
Another thing I like to do is section my files up. Like this:
//===================================================
// DRUPAL HOOKS
//===================================================
//===================================================
// DRUPAL CALLBACKS
//===================================================
//===================================================
// PROCESSING FUNCTIONS
//===================================================
//===================================================
// THEMING
//===================================================
//===================================================
// HELPER FUNCTIONS
//===================================================I know that sometimes I slip up but generally I like to keep a simple structure to the function names and variables. Firstly I tend to keep my helper functions prefixed with an underscore: e.g. _my_helper_function()
Next, camel-case or underscores? I seem to have ended up using underscores but the choice is yours. If you pick one and stick to it, at least for that whole module, you should be able to easily remember your function names.
I always keep my constants defined right at the top of my document and in upper case. This is something that I am very strict on. It's not hard to do but can make things so much easier.
I use my constants to define the items in the hook_perm array. This means I can assign a simple name to the constant and then a human readable string for the value shown on the permissions page.
Even if you are a bloke
Make sure you have an easy to reach bookmark of the api pages (http://api.drupal.com) You'll find them invaluable. There are changes you might not be expecting between versions 5 and 6 and I think radical changes when we get to version 7. You'll need to use the api to find these differences. There is also a section in the main drupal developer handbook online that highlights some of the key changes (see: http://drupal.org/node/114774)
I use subversion (see: http://subversion.tigris.org) to keep all my sites in check. There are often multiple developers on the same project and trying to keep the code base in sync was a pain before I started using it. Basically I never lose anything now. I use the subversion server as the master copy, the working copies that people have are used to develop on and then we have our live/test servers. Using this method, even if someone decides they want to work on the same file at the same time you can easily combine changes and prevent losing one dev's changes. I recommend it.
If you are beginning drupal I recommend getting a good book on the subject. I have a few but I keep referring to the "Pro Drupal Development" book by John K. VanDyk published by apress. It covers a lot and will help you to get your head around the concepts of what happens under the skin of drupal.
the other thing I would suggest is to get on to the IRC channel (see: http://en.wikipedia.org/wiki/Irc) which is based on irc.freenode.net. Join the #drupal channel and ask away. Please bear in mind that there is an unwritten etiquette in the channel. Try not to annoy people and be patient, most users are holding down their day job and might not have time right away. If someone knows, they will answer.