CDML Block Function

Filemaker Pro has decided to drop support for CDML in their products. You can still learn a bit from examining this code. In a general sense, this is what you'd have to do to absorb any other templating system into Smarty. But, use this plugin with FilemakerPro? only if you want to start coding down an evolutionary dead end. I am currently excising it from our smarty templates so we can be forward-compatible. sniff.

This is the initial version. There are still a bunch of print_r and echo statements left in to help with debugging. The path to the cdml_format_files directory is specified for Macintosh OS X. If you have another system, it should work to send the path as the cdmlpath parameter.

Also see SmartyFilemakerPro for another way if you are running your FMPro server on a machine that is not also the php server or you don't have write permission to the cdml_format_files directory.

Good luck and let me know how you fare!


 * Smarty plugin
 * -------------------------------------------------------------
 * File:     block.CDML.php
 * Type:     block
 * Name:     CDML
 * Purpose:  Allows FMPro content to be easily included in Smarty.
 *			 This puts database access into the designer's lap
 *			 because they must know CDML to write template files
 *			 but allows the php code to be very trim. 
 *			 Basically, I got tired of writing the database
 *			 requests and shoveling renamed field data over to smarty templates.
 * 			 The big insight here is to realize that CDML is a fully-formed
 *			 template language. This plugin puts CDML inside
 *			 Smarty. This gives you a template language that is already 
 *			 specially tuned to work with FMPro.
 *	     	 How it works... Takes a block of CDML text (FilemakerPro's native
 *           template language), write it to a file that FMPro
 *           can use in a query string, then issue the query through
 *           smarty fetch to get the data.
 *           Basically, allows you to use CDML's InlineActions in Smarty.
 * Usage:    Put this php code in the plugins dir in a file named block.cdml.php
 *			 '/Applications/FileMaker Pro 6 Folder/cdml_format_files/' is
 *			 the default path to the cdml format file that will be written
 *			 The php program must have permission to write to that folder
 * 			 On our system it is group permission www, nobody is also common
 *			 `chown validuser:www /Applications/FileMaker Pro 6 Folder/cdml_format_files/`
 *			 Then simply enclose your valid CDML in the block.
 *			 this block defaults to use FMPro db running on localhost:8080 (
 *			 Send server and port params to change this.
 *			 However, the cdml writing code only works on local filesystem
 *			 you will have to patch the code or write a php script on the
 *			 remote FMPro machine if you want to configure FMPro on a 
 *			 different server (not localhost)
 *			 All CDML templating is acheived using InlineActions
 *			 This example code shows how to use smarty 'get' variables
 * 			 in the CDML InlineAction find request. Any smarty variable
 *			 encountered in the CDML block will be replaced before the 
 *			 CDML block is written to the formatfile.
 *			 assumes a url like: url/index.php?id=42 that displays
 *			 index.tpl:
 *			 {* Smarty Comment *}
 *			 {CDML db="anydbonthesystem.fp5" pass="validpasswordtothatdb"}
 *			 	[FMP-InlineAction: -db=anotherdb.fp5, -lay=layoutname, fieldname={$}, -find]
 *					{* now we are in anotherdb context *}
 *					[FMP-field: somefieldname]		
 *				[/FMP-InlineAction]
 *			 {/CDML}
 *			 Note: InlineAction has intratag syntax that sometimes uses curlybrackets.
 *			 You will get a smarty undefined tag error if you do not escape them.
 *			 To use curlybrackets either replace them with {ldelim} and {rdelim}
 *			 or surround the InlineAction with {literal}{/literal} tags
 *			 The literal tags will mess up any smarty tags in your InlineAction params
 *			 so it is best to use the {ldelim} and {rdelim} solution
 * @author daniel cummings d a n aht d b m s c a n  daht calm
 * @link
 * @param array
 * @param Smarty
 * @return string
 * @uses smarty_function_fetch()
 * MODIFIED: 02-11-04 DEC
 * added a bunch of optional arguments to CDML block to help debugging: 
 * utility="fieldlist" lets you see all the fieldnames for the layout specified by 
 * lay="ClientView" argument.
 * If lay is not specified, filedlisting shows all fields in the db.
 * showquery="true" lets you echo the query that was sent to the -view action
 * MODIFIED: 03-09-04 DEC
 * made $cdmlpath variable static 
 * added uniqid() fn to default $cdmlpath to avoid tempfile conflicts
 * added unlink() fn to remove tempfile when finished
 * -------------------------------------------------------------
function smarty_block_CDML($params, $content, &$smarty){
	static $cdmlpath = "";
	// if content is set, this is the closing tag calling the function
    if (isset($content)) {
    	// utilize the fetch plugin in this CDML plugin
    	// we can now use the fetch function later on in the code
    	// calling it by name and passing it params it needs
    	// e.g., smarty_function_fetch($params, &$smarty);
    	//may not need to do this, could we use fopen() to get a url?
		require_once $smarty->_get_plugin_filepath('function','fetch');
    	// put the params into named variables to help constructing
    	// the GET query to go to the FMPro database
		//print_r ($params);	
    	// the data about the CDML format file
		if (empty($params['db'])) {
				if (empty($db)) {
					$smarty->_trigger_fatal_error("[plugin] parameter 'db' cannot be empty");
			} else {
				$db = $params['db'];
			//$cdmlpath = $params['cdmlpath'];
		if (empty($params['cdmlpath'])) {
			//use the default temp filename with a uniq id
			$cdmlpath= "/Applications/FileMaker Pro 6 Folder/cdml_format_files/" . $uniqid . "smarty.html";
		 } else {
			// filename passed in must be unique from call to 
			// call (uses static variable to implement)
			// warn plugin user
				$smarty->_trigger_fatal_error("[plugin] parameter 'cdmlpath' cannot be the same as the previous call. Use uniqid() function to generate a unique filename");
			$cdmlpath = $params['cdmlpath'];
		$cdmlfile = '&-format=' . basename($cdmlpath);
			// the generic vars, should have defaults
			//$server = $params['server'];
			//$port = $params['port'];
		if (empty($params['server'])){
		 } else {
			$server = $params['server'];
		if (empty($params['port'])){
		} else { 
			$port = $params['port'];	
		//$path = $params['path'];
		// hard code the path since it should always be the same
		if (empty($params['lay'])){
		} else { 
			$lay = "&-lay=" . $params['lay'];	
		// the database specific vars
		// db is first so it doesn't need an ampersand in the string
		$db = '-db=' . $db;
		// the user and pass specific to this database
		// we will put them as params in the fetch command
		if (empty($params['user'])){
		} else {
				$us = $params['user'];
		if (empty($params['pass'])){
		} else { 
			$pw = $params['pass'];
		// hard code the type of action that can be performed
		// to be view because we will use InlineActions
		$action = '&-view';
		//set the assign var
		if (empty($params['assign'])){
		} else {
				$assign = $params['assign'];
		//set the returntype var
		if (empty($params['returntype'])){
		} else {
				$returntype = $params['returntype'];
		//set the returntype's separator var for explode
		if (empty($params['separator'])){
		} else {
				$separator = $params['separator'];
		//check if there is a utility request
		if (empty($params['utility'])){
		} else {
				$utility = $params['utility'];
		//check if there is a showquery request
		if (empty($params['showquery'])){
		} else {
				$showquery = $params['showquery'];
				if ($showquery == "true") {
					// store the CDML query for possible debugging/display
					$CDMLquery = "CDML query:" . "http://$server:$port/$path$db$cdmlfile$lay$query$limit$action" . "<hr>";
		// write the block contents to the filepath
		// this is the key to the whole plugin!
		// it lets the CDML code exist inside your smarty templates
		// and is written dynamically to file so that FMPro can use it
		$v = write_CDML_block ($content, $cdmlpath, $utility, $CDMLquery);
		//echo $v;
		// now the ensuing fetch (or curl) function can
		// use the file to build the CDML GET query url of the format:
		// this is what the fetch function looks like in the template
		// it needs to be adjusted slightly to exist in this plugin
		// note: the -view command of the CDML request lets us connect
		// to the databases and then use InlineActions to do all the work
		// {*fetch file="http://$server:$port/$path$db$cdmlfile$lay$findreq$limit&-find" user=$us pass=$pw *}
		// could this use just the fopen() function to access the URL?
		//echo "CDML get query:" . "http://$server:$port/$path$db$cdmlfile$action" . "<hr>";
		$fetchparams = array('file' => "http://$server:$port/$path$db$lay$cdmlfile$action", 'user'=>$us, 'pass'=>$pw);
		$ret = smarty_function_fetch($fetchparams, &$smarty);
		//remove the tempfile
		// code that forces FMPro's HTML 
		// entities to behave. This overrides
		// the encoding settings in the CDML tags
		// A bit of brute force that compromises CDML slightly
		// May consider changing this to let CDML's default 
		// HTML encoding go through with its ugly entities
		$ret = unhtmlentities($ret);
		// modify the ret value with another filter
		// initial filter makes a leading subsummary array with
		// empty values in array from a CR separated list
		if( $returntype !=null ) {
			$ret_array = explode($separator, $ret);
			print_r ($ret_array);
			$ret = CDML_sub_summary_from_array($ret_array);
		//does the user want to assign the output?
		//if not, just return it
		if( $assign != null ) {
				$smarty->assign($assign, $ret);
		} else {
				return $ret;

// write a CDML file
// wrapper allows you to add or filter stuff
function write_CDML_block ($data, $filepath, $utility, $CDMLquery) {
	//check the utility param to see what kind of stuff is requested
	//echo $filepath;
	switch ($utility) {
	 case "fieldlist":
	//echo $utility;
		// prepend a field listing tool
		// will list the fields of the 
		// current layout for the database
		// you call the -view action on
	   $data = "Fieldnames:<br />[FMP-LayoutFields][FMP-FieldName]<br />[/FMP-LayoutFields]" . $data;
	case 1: //other tools
	if (!empty($CDMLquery)){
			$data = $CDMLquery . $data;
	$stat=write_a_file($data, $filepath);
	//should return something here or post error somewhere
	return $stat;

//write a file
function write_a_file ($data, $filepath) {
// do some checking to see if the text is already written
// and only write it again if it has changed
	//echo $filepath;
	if($fl = fopen($filepath, 'w+')){
	   if(flock($fl, LOCK_EX)){
		  fseek($fl, 0);
		  ftruncate($fl, 0);
		  $stat = fwrite($fl, $data);
		  flock($fl, LOCK_UN);
	//should return something here or post error somewhere
	return $stat;

// CREATED: DEC 2003-12-10
// MODIFIED: DEC 2003-12-10
// send in a sorted array with repeating values 
// function returns new array with empty values
// in place of repeated entries
// This is possible to do in smarty, see index.tpl for details
function CDML_sub_summary_from_array ($sorted_array, $trailing_bit=1)
   		if ($trailing_bit) {
				//create a "trailing" sub-summary array
				//data points are at the end
				//first we reverse the array
				$reversed_array = array_reverse($sorted_array);
				foreach ($reversed_array as $datapoint)
					{//putting the repeats inside the if avoids
					//extra if checks for the trailing_bit
					if ($dp==$datapoint)
						{//if datapoint doesn't change, store empty string
							array_push($ret_array, "");
						{//first time through or the datapoint changed, store it
							array_push($ret_array, $datapoint);
						//store the datapoint
						$dp = $datapoint;
				//bring the array back to previous
				$ret_array = array_reverse($ret_array);
		else {
				//create a "leading" sub-summary array
				//data points are at the head
				foreach ($sorted_array as $datapoint)
						if ($dp==$datapoint)
						{//if datapoint doesn't change, store empty string
							array_push($ret_array, "");
						{//first time through or the datapoint changed, store it
							array_push($ret_array, $datapoint);
						//store the datapoint
						$dp = $datapoint;
    return $ret_array;

//from user notes at: get_html_translation_table()
function unhtmlentities ($string)  {
   $trans_tbl = get_html_translation_table (HTML_ENTITIES);
   $trans_tbl = array_flip ($trans_tbl);
   $ret = strtr ($string, $trans_tbl);
   return preg_replace('/&#(\d+);/me',