Development Policies
Submitting Patches to Upstream
Overview of submitting code
- Code patches for review can be submitted in the sourceforge tracker HERE. We usually can test and give feedback to your patches within 1-2 days. Please derive patches from the most current OpenEMR development version from either of below repositories:
- The development tip from the CVS repository on sourceforge
- The 'master' branch from the git repository on github: http://github.com/openemr/openemr (quick howto documentation here)
- Note you can leverage github to maintain your own fork of code, so can then submit code by simply uploading your custom branches to your fork on github.
- The 'master' branch from the git repository on gitorious: http://gitorious.org/openemr/openemr
Creating A Patch
Creating patches with diff
Using diff is simple whether you are working with single files or entire source directories. To create a patch for a single file, use the form:
diff -u original.c new.c > original.patch
To create a patch for an entire source tree, make a copy of the tree:
cp -R original new
Make any changes required in the directory new/. Then create a patch with the following command:
diff -rupN original/ new/ > original.patch
That's all you need to get started with diff and patch. For more information use:
man diff man patch
Notifying the Development Team
Place the patch in the tracker's 'Code Review' section, with an explanation. Please also place an explanation of the patch in the developer forum so we know its in the tracker.
Carriage Returns / Line Feeds
All text files for the project should have Unix-style line endings (i.e. no carriage returns). Developers who use a Windows desktop should also use a suitable text editor that respects this (last checked, EditPad Lite and Notepad++ are free examples).
General Development Best Practices
Copyright and Licensing
Each file in the source tree should begin with a copywright declaration, and information about what license the file is released under.
PHP
Many of the practices at http://www.odi.ch/prog/design/php/guide.php appear to be good rules when working with the OpenEMR source.
Javascript
When need to encode variables, use the encodeURIComponent() function instead of using the escape() function (to ensure compatibility with utf-8 characters).
HTML
Each page in OpenEMR should be valid HTML. the validator at http://validator.w3.org/ is useful for ensuring compliance. XHTML 1.1 compliant documents are preferred.
CSS
it is preferred that CSS stylesheets contain all style related contents of our html forms. css stylesheets should also validate.
OpenEMR Specific Development Best Practices
MySQL connections
All of your MySQL calls need to go through openemr/library/sql.inc or you will break the encoding (utf8). See that file for details and the large amount of example throughout code. NEVER, NEVER, NEVER use the native mysql_* calls.
PHP Sessions and Browser Windows
You must include a JavaScript call to top.restoreSession() wherever you invoke a PHP script that requires current session data (which is most of them). How to do this is discussed in more detail in the architecture discussion wiki page.
Internationalization
- The main php function used for translation is xl(), basically all of labels and messages have to go through this function. To learn about this function definition, parameters, and general use, please read this wiki page, and ensure you understand it.
- These things are what I consider the tenets of the xl() function:
- For coding new xl functions:
- 1. No trailing or leading whitespace.
- Below is WRONG
xl('Demographics ');
- Below is CORRECT
xl('Demographics') . ' ';
- Below is WRONG
- 2. No variables.
- Below is WRONG
xl('please type $name here');
- Below is CORRECT
xl('please type') . ' ' . $name . ' ' . xl('here');
- Below is WRONG
- 1. No trailing or leading whitespace.
- For previously coded xl functions:
- To be safe, just leave them be (the above rules do not apply).
- For previously coded xl functions:
- Don't forget about javascript strings. As long as the javascript is in a .php or .inc file it will be translated.
- For example:
alert("Please type letters only");
- Should be:
alert("<?php xl('Please type letters only','e'); ?>");
- Do not use the text or values of your buttons or controls in your coding algorithm. For example, if you have a 'submit' button and use the 'submit' string(the 'value' of the button) in your algorithm, then it will not work if it's translated to another word.
For good examples, look through the code. If any questions don't hesitate to ask them on the sourceforge developer forums.
Input Collection
A relatively new set of functions (openemr/library/formdata.inc.php) have been created for generalized input validation/cleaning (deal with magic quotes) and preparing for database insertion (escaping data). The goal of this it to put in place a central mechanism to avoid sql-injection attacks and to get OpenEMR ready for PHP6.
This is an active project. Check out this link for progress and examples of it's use. If any questions don't hesitate to ask them on the sourceforge developer forums.
List and objectives of the openemr/library/formdata.inc.php functions:
- formData() - This function will remove escapes (if magic quotes is set), and then places database specific escapes to ensure safe database insertion of variable. Input accepts POST, GET, or REQUEST variables, and there is an option to trim the input.
- formDataCore() - This function will remove escapes (if magic quotes is set), and then places database specific escapes to ensure safe database insertion of variable. Input accepts any variable, and there is an option to trim the input.
- strip_escape_custom() - This function will remove escapes (if magic quotes is set). Input accepts any variable
- add_escape_custom() - This functions places database specific escapes to ensure safe database insertion of variable. Input accepts any variable
Access Control Objects
If you add a new Access Control Object to the OpenEMR codebase, then also add it to the following three sites:
- Header notes of the library/acl.inc file
- acl_setup.php file
- acl_upgrade.php file
We are in process of standardizing this. Please see here for details : http://www.openmedsoftware.org/wiki/Sending_Email
Upgrade SQL META Language
Comment Meta Language for sql upgrades (via the sql_upgrade.php script):
Each section within an upgrade sql file is enveloped with an #If*/#EndIf block. At first glance, these appear to be standard mysql comments meant to be cryptic hints to other developers about the sql goodness contained therein. However, were you to rely on such basic premises, you would find yourself grossly deceived. Indeed, without the knowledge that these comments are, in fact a sneakily embedded meta language derived for a purpose none-other than to aid in the protection of the database during upgrades, you would no doubt be subject to much ridicule and public beratement at the hands of the very developers who envisioned such a crafty use of comments. -jwallace
While these lines are as enigmatic as they are functional, there is a method to the madness. Let's take a moment to briefly go over proper comment meta language use.
-- The #If* sections have the behavior of functions and come complete with arguments supplied command-line style -- -- Your Comment meta language lines cannot contain any other comment styles such as the nefarious double dashes "--" lest your lines be skipped and the blocks automatcially executed with out regard to the existing database state. -- -- Comment Meta Language Constructs: -- -- #IfNotTable -- argument: table_name -- behavior: if the table_name does not exist, the block will be executed -- #IfTable -- argument: table_name -- behavior: if the table_name does exist, the block will be executed -- #IfMissingColumn -- arguments: table_name colname -- behavior: if the colname in the table_name table does not exist, the block will be executed -- #IfNotColumnType -- arguments: table_name colname value -- behavior: If the table table_name does not have a column colname with a data type equal to value, then the block will be executed -- #IfNotRow -- arguments: table_name colname value -- behavior: If the table table_name does not have a row where colname = value, the block will be executed. -- #IfNotRow2D -- arguments: table_name colname value colname2 value2 -- behavior: If the table table_name does not have a row where colname = value AND colname2 = value2, the block will be executed. -- #EndIf -- all blocks are terminated with and #EndIF statement.
Creating a list in list_options
Lists are a nice way to build customizable lists that support standardized output formatting and internationalization. See Administration->Lists for details and check out the list_options mysql table.
- Example of placement of new list in database.sql:
INSERT INTO list_options ( list_id, option_id, title, seq, is_default ) VALUES ('lists','proc_specimen','Procedure Specimen Types', 1,0); INSERT INTO list_options ( list_id, option_id, title, seq, is_default ) VALUES ('proc_specimen','blood' ,'Blood' ,10,0); INSERT INTO list_options ( list_id, option_id, title, seq, is_default ) VALUES ('proc_specimen','saliva','Saliva',20,0); INSERT INTO list_options ( list_id, option_id, title, seq, is_default ) VALUES ('proc_specimen','urine' ,'Urine' ,30,0); INSERT INTO list_options ( list_id, option_id, title, seq, is_default ) VALUES ('proc_specimen','oth' ,'Other' ,90,0);
- Example of placement of new list in the upgrade sql file:
#IfNotRow2D list_options list_id lists option_id proc_specimen INSERT INTO list_options ( list_id, option_id, title, seq, is_default ) VALUES ('lists','proc_specimen','Procedure Specimen Types', 1,0); INSERT INTO list_options ( list_id, option_id, title, seq, is_default ) VALUES ('proc_specimen','blood' ,'Blood' ,10,0); INSERT INTO list_options ( list_id, option_id, title, seq, is_default ) VALUES ('proc_specimen','saliva','Saliva',20,0); INSERT INTO list_options ( list_id, option_id, title, seq, is_default ) VALUES ('proc_specimen','urine' ,'Urine' ,30,0); INSERT INTO list_options ( list_id, option_id, title, seq, is_default ) VALUES ('proc_specimen','oth' ,'Other' ,90,0); #EndIf
Note as demonstrated above that the seq entries increment in 10's, and that the option_id entry should not contain any blanks or caps.
These lists can be used to configuration of layouts in Administration->layouts, and can also use the functions in options.inc.php within scripts.
- Example of generating a item selector using the list(note the input variable will be $POST['form_proc_specimen']):
generate_form_field(array('data_type'=>1,'field_id'=>'proc_specimen','list_id'=>'proc_specimen'), $defaultProcSpecimen);
- Example of generating the title of the list from the option_id:
generate_display_field(array('data_type'=>'1','list_id'=>'proc_specimen'),$chosen_proc_specimen);
Creating a global configuration setting
As of version 4.0, these can be added to library/globals.inc.php . Then the variable can be called in code as so : $GLOBALS['yourVariableName'] . It can also then be configured by the user in Adminstration->Globals screen.