diff --git a/addressbook/inc/class.addressbook_merge.inc.php b/addressbook/inc/class.addressbook_merge.inc.php
index 250b970a23..daf7eddc14 100644
--- a/addressbook/inc/class.addressbook_merge.inc.php
+++ b/addressbook/inc/class.addressbook_merge.inc.php
@@ -13,7 +13,7 @@
/**
* Addressbook - document merge object
*/
-class addressbook_merge // extends bo_merge
+class addressbook_merge extends bo_merge
{
/**
* Functions that can be called via menuaction
@@ -21,12 +21,6 @@ class addressbook_merge // extends bo_merge
* @var array
*/
var $public_functions = array('show_replacements' => true);
- /**
- * Instance of the addressbook_bo class
- *
- * @var addressbook_bo
- */
- var $contacts;
/**
* Constructor
@@ -35,157 +29,25 @@ class addressbook_merge // extends bo_merge
*/
function __construct()
{
- $this->contacts = new addressbook_bo();
+ parent::__construct();
}
/**
- * Return if merge-print is implemented for given mime-type (and/or extension)
+ * Get addressbook replacements
*
- * @param string $mimetype eg. text/plain
- * @param string $extension only checked for applications/msword and .rtf
+ * @param int $id id of entry
+ * @param string &$content=null content to create some replacements only if they are use
+ * @return array|boolean
*/
- static function is_implemented($mimetype,$extension=null)
+ protected function get_replacements($id,&$content=null)
{
- static $zip_available;
- if (is_null($zip_available))
+ if (!($replacements = $this->contact_replacements($id)))
{
- $zip_available = check_load_extension('zip') &&
- class_exists('ZipArchive'); // some PHP has zip extension, but no ZipArchive (eg. RHEL5!)
+ return false;
}
- switch ($mimetype)
+ if (strpos($content,'$$calendar/') !== null)
{
- case 'application/msword':
- if (strtolower($extension) != '.rtf') break;
- case 'application/rtf':
- case 'text/rtf':
- return true; // rtf files
- case 'application/vnd.oasis.opendocument.text':
- if (!$zip_available) break;
- return true; // open office write xml files
- case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
- case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars
- if (!$zip_available) break;
- return true; // ms word xml format
- default:
- if (substr($mimetype,0,5) == 'text/')
- {
- return true; // text files
- }
- break;
- }
- return false;
-
- // As browsers not always return correct mime types, one could use a negative list instead
- //return !($mimetype == egw_vfs::DIR_MIME_TYPE || substr($mimetype,0,6) == 'image/');
- }
-
- /**
- * Return replacements for a contact
- *
- * @param int/string/array $contact contact-array or id
- * @param string $prefix='' prefix like eg. 'user'
- * @return array
- */
- function contact_replacements($contact,$prefix='')
- {
- if (!is_array($contact))
- {
- $contact = $this->contacts->read($contact);
- }
- if (!is_array($contact)) return array();
-
- $replacements = array();
- foreach(array_keys($this->contacts->contact_fields) as $name)
- {
- $value = $contact[$name];
- switch($name)
- {
- case 'created': case 'modified':
- $value = date($GLOBALS['egw_info']['user']['preferences']['common']['dateformat'].' '.
- ($GLOBALS['egw_info']['user']['preferences']['common']['timeformat']==12?'h:i a':'H:i'),$value);
- break;
- case 'bday':
- if ($value)
- {
- list($y,$m,$d) = explode('-',$value);
- $value = $GLOBALS['egw']->common->dateformatorder($y,$m,$d,true);
- }
- break;
- case 'owner': case 'creator': case 'modifier':
- $value = $GLOBALS['egw']->common->grab_owner_name($value);
- break;
- case 'cat_id':
- if ($value)
- {
- // if cat-tree is displayed, we return a full category path not just the name of the cat
- $use = $GLOBALS['egw_info']['server']['cat_tab'] == 'Tree' ? 'path' : 'name';
- $cats = array();
- foreach(is_array($value) ? $value : explode(',',$value) as $cat_id)
- {
- $cats[] = $GLOBALS['egw']->categories->id2name($cat_id,$use);
- }
- $value = implode(', ',$cats);
- }
- break;
- case 'jpegphoto': // returning a link might make more sense then the binary photo
- if ($contact['photo'])
- {
- $value = ($GLOBALS['egw_info']['server']['webserver_url']{0} == '/' ?
- ($_SERVER['HTTPS'] ? 'https://' : 'http://').$_SERVER['HTTP_HOST'] : '').
- $GLOBALS['egw']->link('/index.php',$contact['photo']);
- }
- break;
- case 'tel_prefer':
- if ($value && $contact[$value])
- {
- $value = $contact[$value];
- }
- break;
- case 'account_id':
- if ($value)
- {
- $replacements['$$'.($prefix ? $prefix.'/':'').'account_lid$$'] = $GLOBALS['egw']->accounts->id2name($value);
- }
- break;
- }
- if ($name != 'photo') $replacements['$$'.($prefix ? $prefix.'/':'').$name.'$$'] = $value;
- }
- // set custom fields
- foreach($this->contacts->customfields as $name => $field)
- {
- $name = '#'.$name;
- $value = (string)$contact[$name];
- switch($field['type'])
- {
- case 'select-account':
- if ($value) $value = common::grab_owner_name($value);
- break;
-
- case 'select':
- if (count($field['values']) == 1 && isset($field['values']['@']))
- {
- $field['values'] = customfields_widget::_get_options_from_file($field['values']['@']);
- }
- $values = array();
- foreach($field['rows'] > 1 ? explode(',',$value) : (array) $value as $value)
- {
- $values[] = $field['values'][$value];
- }
- $value = implode(', ',$values);
- break;
-
- case 'date':
- case 'date-time':
- if ($value)
- {
- $format = $field['len'] ? $field['len'] : ($field['type'] == 'date' ? 'Y-m-d' : 'Y-m-d H:i:s');
- $date = array_combine(preg_split('/[\\/. :-]/',$format),preg_split('/[\\/. :-]/',$value));
- $value = common::dateformatorder($date['Y'],$date['m'],$date['d'],true);
- if (isset($date['H'])) $value .= ' '.common::formattime($date['H'],$date['i']);
- }
- break;
- }
- $replacements['$$'.($prefix ? $prefix.'/':'').$name.'$$'] = $value;
+ $replacements += $this->calendar_replacements($id,strpos($content,'$$calendar/-1/') !== null);
}
return $replacements;
}
@@ -197,9 +59,8 @@ class addressbook_merge // extends bo_merge
* @param boolean $last_event_too=false also include information about the last event
* @return array
*/
- function calendar_replacements($id,$last_event_too=false)
+ protected function calendar_replacements($id,$last_event_too=false)
{
- require_once(EGW_INCLUDE_ROOT.'/calendar/inc/class.calendar_boupdate.inc.php');
$calendar = new calendar_boupdate();
// next events
@@ -258,232 +119,15 @@ class addressbook_merge // extends bo_merge
return $replacements;
}
- /**
- * Merges a given document with contact data
- *
- * @param string $document path/url of document
- * @param array $ids array with contact id(s)
- * @param string &$err error-message on error
- * @param string $mimetype mimetype of complete document, eg. text/*, application/vnd.oasis.opendocument.text, application/rtf
- * @param array $fix=null regular expression => replacement pairs eg. to fix garbled placeholders
- * @return string/boolean merged document or false on error
- */
- function &merge($document,$ids,&$err,$mimetype,array $fix=null)
- {
- if (!($content = file_get_contents($document)))
- {
- $err = lang("Document '%1' does not exist or is not readable for you!",$document);
- return false;
- }
- // fix garbled placeholders
- if ($fix && is_array($fix))
- {
- $content = preg_replace(array_keys($fix),array_values($fix),$content);
- //die("
".htmlspecialchars($content)."
\n");
- }
- list($contentstart,$contentrepeat,$contentend) = preg_split('/\$\$pagerepeat\$\$/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat
- if ($mimetype == 'application/vnd.oasis.opendocument.text' && count($ids) > 1)
- {
- //for odt files we have to slpit the content and add a style for page break to the style area
- list($contentstart,$contentrepeat,$contentend) = preg_split('/office:body>/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat
- $contentstart = substr($contentstart,0,strlen($contentstart)-1); //remove "<"
- $contentrepeat = substr($contentrepeat,0,strlen($contentrepeat)-2); //remove "";
- // need to add page-break style to the style list
- list($stylestart,$stylerepeat,$styleend) = preg_split('/<\/office:automatic-styles>/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document style sheets
- $contentstart = $stylestart.'';
- $contentstart .= '';
- $contentend = '';
- }
- list($Labelstart,$Labelrepeat,$Labeltend) = preg_split('/\$\$label\$\$/',$contentrepeat,-1, PREG_SPLIT_NO_EMPTY); //get the Lable content
- preg_match_all('/\$\$labelplacement\$\$/',$contentrepeat,$countlables, PREG_SPLIT_NO_EMPTY);
- $countlables = count($countlables[0])+1;
- preg_replace('/\$\$labelplacement\$\$/','',$Labelrepeat,1);
- if ($countlables > 1) $lableprint = true;
- if (count($ids) > 1 && !$contentrepeat)
- {
- $err = lang('for more then one contact in a document use the tag pagerepeat!');
- return false;
- }
- foreach ($ids as $id)
- {
- if ($contentrepeat) $content = $contentrepeat; //content to repeat
- // generate replacements
- if ($lableprint) $content = $Labelrepeat;
- if (!($replacements = $this->contact_replacements($id)))
- {
- $err = lang('Contact not found!');
- return false;
- }
- if (strpos($content,'$$user/') !== null && ($user = $GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')))
- {
- $replacements += $this->contact_replacements($user,'user');
- }
- if (strpos($content,'$$calendar/') !== null)
- {
- $replacements += $this->calendar_replacements($id,strpos($content,'$$calendar/-1/') !== null);
- }
- $replacements['$$date$$'] = date($GLOBALS['egw_info']['user']['preferences']['common']['dateformat'],time()+$this->contacts->tz_offset_s);
-
- if ($this->contacts->prefs['csv_charset']) // if we have an export-charset defined, use it here to
- {
- $replacements = $GLOBALS['egw']->translation->convert($replacements,$GLOBALS['egw']->translation->charset(),$this->contacts->prefs['csv_charset']);
- }
- $content = str_replace(array_keys($replacements),array_values($replacements),$content);
-
- if (strpos($content,'$$calendar/') !== null) // remove not existing event-replacements
- {
- $content = preg_replace('/\$\$calendar\/[0-9]+\/[a-z_]+\$\$/','',$content);
- }
- $this->replacements = $replacements;
- if (strpos($content,'$$IF'))
- { //Example use to use: $$IF n_prefix~Herr~Sehr geehrter~Sehr geehrte$$
- $content = preg_replace_callback('/\$\$IF ([0-9a-z_-]+)~(.*)~(.*)~(.*)\$\$/imU',Array($this,'replace_callback'),$content);
- }
- if ($contentrepeat) $contentrep[$id] = $content;
- }
- if ($Labelrepeat)
- {
- $countpage=0;
- $count=0;
- $contentrepeatpages[$countpage] = $Labelstart.$Labeltend;
-
- foreach ($contentrep as $Label)
- {
- $count=$count+1;
- if ($count % $countlables == 0)
- {
- $countpage=$countpage+1;
- $contentrepeatpages[$countpage] = $Labelstart.$Labeltend;
- }
- $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/',$Label,$contentrepeatpages[$countpage],1);
- }
- $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/','',$contentrepeatpages[$countpage],-1); //clean empty fields
-
- switch($mimetype)
- {
- case 'application/msword':
- if (strtolower(substr($document,-4)) != '.rtf') break; // no binary word documents
- case 'application/rtf':
- case 'text/rtf':
- return $contentstart.implode('\\par \\page\\pard\\plain',$contentrepeatpages).$contentend;
- case 'application/vnd.oasis.opendocument.text':
- // todo OO writer files
- break;
- case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
- case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars
- // todo ms word xml files
- break;
- }
- $err = lang('%1 not implemented for %2!','$$labelplacement$$',$mimetype);
- return false;
- }
-
- if ($contentrepeat)
- {
- switch($mimetype)
- {
- case 'application/msword':
- if (strtolower(substr($document,-4)) != '.rtf') break; // no binary word documents
- case 'application/rtf':
- case 'text/rtf':
- return $contentstart.implode('\\par \\page\\pard\\plain',$contentrep).$contentend;
- case 'application/vnd.oasis.opendocument.text':
- return $contentstart.implode('',$contentrep).$contentend;
- break;
- case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
- case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars
- // todo ms word xml files
- break;
- }
- $err = lang('%1 not implemented for %2!','$$pagerepeat$$',$mimetype);
- return false;
- }
- return $content;
- }
-
- /**
- * Callback for preg_replace to process $$IF
- *
- * @param array $param
- * @return string
- */
- function replace_callback($param)
- {
- if (array_key_exists('$$'.$param[4].'$$',$this->replacements)) $param[4] = $this->replacements['$$'.$param[4].'$$'];
- if (array_key_exists('$$'.$param[3].'$$',$this->replacements)) $param[3] = $this->replacements['$$'.$param[3].'$$'];
- $replace = preg_match('/'.$param[2].'/',$this->replacements['$$'.$param[1].'$$']) ? $param[3] : $param[4];
- return $replace;
- }
-
- /**
- * Download document merged with contact(s)
- *
- * @param string $document vfs-path of document
- * @param array $ids array with contact id(s)
- * @return string with error-message on error, otherwise it does NOT return
- */
- function download($document,$ids)
- {
- $content_url = egw_vfs::PREFIX.$document;
- switch (($mime_type = egw_vfs::mime_content_type($document)))
- {
- case 'application/vnd.oasis.opendocument.text':
- $archive = tempnam($GLOBALS['egw_info']['server']['temp_dir'], basename($document,'.odt').'-').'.odt';
- copy($content_url,$archive);
- $content_url = 'zip://'.$archive.'#'.($content_file = 'content.xml');
- break;
- case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars
- $mime_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
- case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
- $archive = tempnam($GLOBALS['egw_info']['server']['temp_dir'], basename($document,'.dotx').'-').'.dotx';
- copy($content_url,$archive);
- $content_url = 'zip://'.$archive.'#'.($content_file = 'word/document.xml');
- $fix = array( // regular expression to fix garbled placeholders
- '/'.preg_quote('$$','/').'([a-z0-9_]+)'.
- preg_quote('','/').'/i' => '$$\\1$$',
- '/'.preg_quote('$$','/').'([a-z0-9_]+)'.
- preg_quote('$$','/').'/i' => '$$\\2$$',
- );
- break;
- }
- if (!($merged =& $this->merge($content_url,$ids,$err,$mime_type,$fix)))
- {
- return $err;
- }
- if (isset($archive))
- {
- $zip = new ZipArchive;
- if ($zip->open($archive,ZIPARCHIVE::CHECKCONS) !== true) throw new Exception("!ZipArchive::open('$archive',ZIPARCHIVE::OVERWRITE)");
- if ($zip->addFromString($content_file,$merged) !== true) throw new Exception("!ZipArchive::addFromString('$content_file',\$merged)");
- if ($zip->close() !== true) throw new Exception("!ZipArchive::close()");
- unset($zip);
- unset($merged);
- if (file_exists('/usr/bin/zip') && version_compare(PHP_VERSION,'5.3.1','<')) // fix broken zip archives generated by current php
- {
- exec('/usr/bin/zip -F '.escapeshellarg($archive));
- }
- ExecMethod2('phpgwapi.browser.content_header',basename($document),$mime_type);
- readfile($archive,'r');
- }
- else
- {
- ExecMethod2('phpgwapi.browser.content_header',basename($document),$mime_type);
- echo $merged;
- }
- $GLOBALS['egw']->common->egw_exit();
- }
-
/**
* Generate table with replacements for the preferences
*
*/
- function show_replacements()
+ public function show_replacements()
{
$GLOBALS['egw_info']['flags']['app_header'] = lang('Addressbook').' - '.lang('Replacements for inserting contacts into documents');
$GLOBALS['egw_info']['flags']['nonavbar'] = false;
- $GLOBALS['egw']->common->egw_header();
+ common::egw_header();
echo "\n";
echo ''.lang('Contact fields:')." |
";
@@ -560,6 +204,6 @@ class addressbook_merge // extends bo_merge
}
echo "
\n";
- $GLOBALS['egw']->common->egw_footer();
+ common::egw_footer();
}
}
diff --git a/etemplate/inc/class.bo_merge.inc.php b/etemplate/inc/class.bo_merge.inc.php
new file mode 100644
index 0000000000..8dfd8a2841
--- /dev/null
+++ b/etemplate/inc/class.bo_merge.inc.php
@@ -0,0 +1,454 @@
+
+ * @package addressbook
+ * @copyright (c) 2007-9 by Ralf Becker
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id$
+ */
+
+/**
+ * Document merge print
+ */
+abstract class bo_merge
+{
+ /**
+ * Instance of the addressbook_bo class
+ *
+ * @var addressbook_bo
+ */
+ var $contacts;
+
+ /**
+ * Datetime format according to user preferences
+ *
+ * @var string
+ */
+ var $datetime_format = 'Y-m-d H:i';
+
+ /**
+ * Mimetype of document processed by merge
+ *
+ * @var string
+ */
+ var $mimetype;
+
+ /**
+ * Constructor
+ *
+ * @return bo_merge
+ */
+ function __construct()
+ {
+ $this->contacts = new addressbook_bo();
+
+ $this->datetime_format = $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'].' '.
+ ($GLOBALS['egw_info']['user']['preferences']['common']['timeformat']==12 ? 'h:i a' : 'H:i');
+ }
+
+ /**
+ * Get all replacements, must be implemented in extending class
+ *
+ * Can use eg. the following high level methods:
+ * - contact_replacements($contact_id,$prefix='')
+ * - format_datetime($time,$format=null)
+ *
+ * @param int $id id of entry
+ * @param string &$content=null content to create some replacements only if they are use
+ * @return array|boolean array with replacements or false if entry not found
+ */
+ abstract protected function get_replacements($id,&$content=null);
+
+ /**
+ * Return if merge-print is implemented for given mime-type (and/or extension)
+ *
+ * @param string $mimetype eg. text/plain
+ * @param string $extension only checked for applications/msword and .rtf
+ */
+ static public function is_implemented($mimetype,$extension=null)
+ {
+ static $zip_available;
+ if (is_null($zip_available))
+ {
+ $zip_available = check_load_extension('zip') &&
+ class_exists('ZipArchive'); // some PHP has zip extension, but no ZipArchive (eg. RHEL5!)
+ }
+ switch ($mimetype)
+ {
+ case 'application/msword':
+ if (strtolower($extension) != '.rtf') break;
+ case 'application/rtf':
+ case 'text/rtf':
+ return true; // rtf files
+ case 'application/vnd.oasis.opendocument.text':
+ if (!$zip_available) break;
+ return true; // open office write xml files
+ case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
+ case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars
+ if (!$zip_available) break;
+ return true; // ms word xml format
+ default:
+ if (substr($mimetype,0,5) == 'text/')
+ {
+ return true; // text files
+ }
+ break;
+ }
+ return false;
+
+ // As browsers not always return correct mime types, one could use a negative list instead
+ //return !($mimetype == egw_vfs::DIR_MIME_TYPE || substr($mimetype,0,6) == 'image/');
+ }
+
+ /**
+ * Return replacements for a contact
+ *
+ * @param int|string|array $contact contact-array or id
+ * @param string $prefix='' prefix like eg. 'user'
+ * @return array
+ */
+ protected function contact_replacements($contact,$prefix='')
+ {
+ if (!is_array($contact))
+ {
+ $contact = $this->contacts->read($contact);
+ }
+ if (!is_array($contact)) return array();
+
+ $replacements = array();
+ foreach(array_keys($this->contacts->contact_fields) as $name)
+ {
+ $value = $contact[$name];
+ switch($name)
+ {
+ case 'created': case 'modified':
+ $value = $this->format_datetime($value);
+ break;
+ case 'bday':
+ if ($value)
+ {
+ list($y,$m,$d) = explode('-',$value);
+ $value = common::dateformatorder($y,$m,$d,true);
+ }
+ break;
+ case 'owner': case 'creator': case 'modifier':
+ $value = common::grab_owner_name($value);
+ break;
+ case 'cat_id':
+ if ($value)
+ {
+ // if cat-tree is displayed, we return a full category path not just the name of the cat
+ $use = $GLOBALS['egw_info']['server']['cat_tab'] == 'Tree' ? 'path' : 'name';
+ $cats = array();
+ foreach(is_array($value) ? $value : explode(',',$value) as $cat_id)
+ {
+ $cats[] = $GLOBALS['egw']->categories->id2name($cat_id,$use);
+ }
+ $value = implode(', ',$cats);
+ }
+ break;
+ case 'jpegphoto': // returning a link might make more sense then the binary photo
+ if ($contact['photo'])
+ {
+ $value = ($GLOBALS['egw_info']['server']['webserver_url'][0] == '/' ?
+ ($_SERVER['HTTPS'] ? 'https://' : 'http://').$_SERVER['HTTP_HOST'] : '').
+ $GLOBALS['egw']->link('/index.php',$contact['photo']);
+ }
+ break;
+ case 'tel_prefer':
+ if ($value && $contact[$value])
+ {
+ $value = $contact[$value];
+ }
+ break;
+ case 'account_id':
+ if ($value)
+ {
+ $replacements['$$'.($prefix ? $prefix.'/':'').'account_lid$$'] = $GLOBALS['egw']->accounts->id2name($value);
+ }
+ break;
+ }
+ if ($name != 'photo') $replacements['$$'.($prefix ? $prefix.'/':'').$name.'$$'] = $value;
+ }
+ // set custom fields
+ foreach($this->contacts->customfields as $name => $field)
+ {
+ $name = '#'.$name;
+ $value = (string)$contact[$name];
+ switch($field['type'])
+ {
+ case 'select-account':
+ if ($value) $value = common::grab_owner_name($value);
+ break;
+
+ case 'select':
+ if (count($field['values']) == 1 && isset($field['values']['@']))
+ {
+ $field['values'] = customfields_widget::_get_options_from_file($field['values']['@']);
+ }
+ $values = array();
+ foreach($field['rows'] > 1 ? explode(',',$value) : (array) $value as $value)
+ {
+ $values[] = $field['values'][$value];
+ }
+ $value = implode(', ',$values);
+ break;
+
+ case 'date':
+ case 'date-time':
+ if ($value)
+ {
+ $format = $field['len'] ? $field['len'] : ($field['type'] == 'date' ? 'Y-m-d' : 'Y-m-d H:i:s');
+ $date = array_combine(preg_split('/[\\/. :-]/',$format),preg_split('/[\\/. :-]/',$value));
+ $value = common::dateformatorder($date['Y'],$date['m'],$date['d'],true);
+ if (isset($date['H'])) $value .= ' '.common::formattime($date['H'],$date['i']);
+ }
+ break;
+ }
+ $replacements['$$'.($prefix ? $prefix.'/':'').$name.'$$'] = $value;
+ }
+ return $replacements;
+ }
+
+ /**
+ * Format a datetime
+ *
+ * @param int|string $time unix timestamp or Y-m-d H:i:s string (in user time!)
+ * @param string $format=null format string, default $this->datetime_format
+ * @return string
+ */
+ protected function format_datetime($time,$format=null)
+ {
+ if (is_null($format)) $format = $this->datetime_format;
+
+ if (empty($time)) return '';
+
+ return date($this->datetime_format,is_numeric($time) ? $time : strtotime($time));
+ }
+
+ /**
+ * Merges a given document with contact data
+ *
+ * @param string $document path/url of document
+ * @param array $ids array with contact id(s)
+ * @param string &$err error-message on error
+ * @param string $mimetype mimetype of complete document, eg. text/*, application/vnd.oasis.opendocument.text, application/rtf
+ * @param array $fix=null regular expression => replacement pairs eg. to fix garbled placeholders
+ * @return string|boolean merged document or false on error
+ */
+ public function &merge($document,$ids,&$err,$mimetype,array $fix=null)
+ {
+ if (!($content = file_get_contents($document)))
+ {
+ $err = lang("Document '%1' does not exist or is not readable for you!",$document);
+ return false;
+ }
+ // make currently processed mimetype available to class methods;
+ $this->mimetype = $mimetype;
+
+ // fix garbled placeholders
+ if ($fix && is_array($fix))
+ {
+ $content = preg_replace(array_keys($fix),array_values($fix),$content);
+ //die("".htmlspecialchars($content)."
\n");
+ }
+ list($contentstart,$contentrepeat,$contentend) = preg_split('/\$\$pagerepeat\$\$/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat
+ if ($mimetype == 'application/vnd.oasis.opendocument.text' && count($ids) > 1)
+ {
+ //for odt files we have to slpit the content and add a style for page break to the style area
+ list($contentstart,$contentrepeat,$contentend) = preg_split('/office:body>/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat
+ $contentstart = substr($contentstart,0,strlen($contentstart)-1); //remove "<"
+ $contentrepeat = substr($contentrepeat,0,strlen($contentrepeat)-2); //remove "";
+ // need to add page-break style to the style list
+ list($stylestart,$stylerepeat,$styleend) = preg_split('/<\/office:automatic-styles>/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document style sheets
+ $contentstart = $stylestart.'';
+ $contentstart .= '';
+ $contentend = '';
+ }
+ list($Labelstart,$Labelrepeat,$Labeltend) = preg_split('/\$\$label\$\$/',$contentrepeat,-1, PREG_SPLIT_NO_EMPTY); //get the Lable content
+ preg_match_all('/\$\$labelplacement\$\$/',$contentrepeat,$countlables, PREG_SPLIT_NO_EMPTY);
+ $countlables = count($countlables[0])+1;
+ preg_replace('/\$\$labelplacement\$\$/','',$Labelrepeat,1);
+ if ($countlables > 1) $lableprint = true;
+ if (count($ids) > 1 && !$contentrepeat)
+ {
+ $err = lang('for more then one contact in a document use the tag pagerepeat!');
+ return false;
+ }
+ foreach ((array)$ids as $id)
+ {
+ if ($contentrepeat) $content = $contentrepeat; //content to repeat
+ if ($lableprint) $content = $Labelrepeat;
+
+ // generate replacements
+ if (!($replacements = $this->get_replacements($id)))
+ {
+ $err = lang('Entry not found!');
+ return false;
+ }
+ // some general replacements: current user, date and time
+ if (strpos($content,'$$user/') !== null && ($user = $GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')))
+ {
+ $replacements += $this->contact_replacements($user,'user');
+ }
+ $now = time()+$this->contacts->tz_offset_s;
+ $replacements['$$date$$'] = $this->format_datetime($now,$GLOBALS['egw_info']['user']['preferences']['common']['dateformat']);
+ $replacements['$$datetime$$'] = $this->format_datetime($now);
+ $replacements['$$time$$'] = $this->format_datetime($now,$GLOBALS['egw_info']['user']['preferences']['common']['timeformat']==12?'h:i a':'H:i');
+
+ if ($this->contacts->prefs['csv_charset']) // if we have an export-charset defined, use it here to
+ {
+ $replacements = $GLOBALS['egw']->translation->convert($replacements,$GLOBALS['egw']->translation->charset(),$this->contacts->prefs['csv_charset']);
+ }
+ $content = str_replace(array_keys($replacements),array_values($replacements),$content);
+
+ if (strpos($content,'$$IF'))
+ { //Example use to use: $$IF n_prefix~Herr~Sehr geehrter~Sehr geehrte$$
+ $this->replacements =& $replacements;
+ $content = preg_replace_callback('/\$\$IF ([0-9a-z_-]+)~(.*)~(.*)~(.*)\$\$/imU',Array($this,'replace_callback'),$content);
+ unset($this->replacements);
+ }
+ // remove not existing replacements (eg. from calendar array)
+ if (strpos($content,'$$') !== null)
+ {
+ $content = preg_replace('/\$\$[a-z0-9_]+\$\$/i','',$content);
+ }
+ if ($contentrepeat) $contentrep[$id] = $content;
+ }
+ if ($Labelrepeat)
+ {
+ $countpage=0;
+ $count=0;
+ $contentrepeatpages[$countpage] = $Labelstart.$Labeltend;
+
+ foreach ($contentrep as $Label)
+ {
+ $count=$count+1;
+ if ($count % $countlables == 0)
+ {
+ $countpage=$countpage+1;
+ $contentrepeatpages[$countpage] = $Labelstart.$Labeltend;
+ }
+ $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/',$Label,$contentrepeatpages[$countpage],1);
+ }
+ $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/','',$contentrepeatpages[$countpage],-1); //clean empty fields
+
+ switch($mimetype)
+ {
+ case 'application/msword':
+ if (strtolower(substr($document,-4)) != '.rtf') break; // no binary word documents
+ case 'application/rtf':
+ case 'text/rtf':
+ return $contentstart.implode('\\par \\page\\pard\\plain',$contentrepeatpages).$contentend;
+ case 'application/vnd.oasis.opendocument.text':
+ // todo OO writer files
+ break;
+ case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
+ case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars
+ // todo ms word xml files
+ break;
+ }
+ $err = lang('%1 not implemented for %2!','$$labelplacement$$',$mimetype);
+ return false;
+ }
+
+ if ($contentrepeat)
+ {
+ switch($mimetype)
+ {
+ case 'application/msword':
+ if (strtolower(substr($document,-4)) != '.rtf') break; // no binary word documents
+ case 'application/rtf':
+ case 'text/rtf':
+ return $contentstart.implode('\\par \\page\\pard\\plain',$contentrep).$contentend;
+ case 'application/vnd.oasis.opendocument.text':
+ return $contentstart.implode('',$contentrep).$contentend;
+ break;
+ case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
+ case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars
+ // todo ms word xml files
+ break;
+ }
+ $err = lang('%1 not implemented for %2!','$$pagerepeat$$',$mimetype);
+ return false;
+ }
+ return $content;
+ }
+
+ /**
+ * Callback for preg_replace to process $$IF
+ *
+ * @param array $param
+ * @return string
+ */
+ private function replace_callback($param)
+ {
+ if (array_key_exists('$$'.$param[4].'$$',$this->replacements)) $param[4] = $this->replacements['$$'.$param[4].'$$'];
+ if (array_key_exists('$$'.$param[3].'$$',$this->replacements)) $param[3] = $this->replacements['$$'.$param[3].'$$'];
+ $replace = preg_match('/'.$param[2].'/',$this->replacements['$$'.$param[1].'$$']) ? $param[3] : $param[4];
+ return $replace;
+ }
+
+ /**
+ * Download document merged with contact(s)
+ *
+ * @param string $document vfs-path of document
+ * @param array $ids array with contact id(s)
+ * @return string with error-message on error, otherwise it does NOT return
+ */
+ public function download($document,$ids)
+ {
+ $content_url = egw_vfs::PREFIX.$document;
+ switch (($mime_type = egw_vfs::mime_content_type($document)))
+ {
+ case 'application/vnd.oasis.opendocument.text':
+ $archive = tempnam($GLOBALS['egw_info']['server']['temp_dir'], basename($document,'.odt').'-').'.odt';
+ copy($content_url,$archive);
+ $content_url = 'zip://'.$archive.'#'.($content_file = 'content.xml');
+ break;
+ case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars
+ $mime_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
+ case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
+ $archive = tempnam($GLOBALS['egw_info']['server']['temp_dir'], basename($document,'.dotx').'-').'.dotx';
+ copy($content_url,$archive);
+ $content_url = 'zip://'.$archive.'#'.($content_file = 'word/document.xml');
+ $fix = array( // regular expression to fix garbled placeholders
+ '/'.preg_quote('$$','/').'([a-z0-9_]+)'.
+ preg_quote('','/').'/i' => '$$\\1$$',
+ '/'.preg_quote('$$','/').'([a-z0-9_]+)'.
+ preg_quote('$$','/').'/i' => '$$\\2$$',
+ );
+ break;
+ }
+ if (!($merged =& $this->merge($content_url,$ids,$err,$mime_type,$fix)))
+ {
+ return $err;
+ }
+ if (isset($archive))
+ {
+ $zip = new ZipArchive;
+ if ($zip->open($archive,ZIPARCHIVE::CHECKCONS) !== true) throw new Exception("!ZipArchive::open('$archive',ZIPARCHIVE::OVERWRITE)");
+ if ($zip->addFromString($content_file,$merged) !== true) throw new Exception("!ZipArchive::addFromString('$content_file',\$merged)");
+ if ($zip->close() !== true) throw new Exception("!ZipArchive::close()");
+ unset($zip);
+ unset($merged);
+ if (file_exists('/usr/bin/zip') && version_compare(PHP_VERSION,'5.3.1','<')) // fix broken zip archives generated by current php
+ {
+ exec('/usr/bin/zip -F '.escapeshellarg($archive));
+ }
+ ExecMethod2('phpgwapi.browser.content_header',basename($document),$mime_type);
+ readfile($archive,'r');
+ }
+ else
+ {
+ ExecMethod2('phpgwapi.browser.content_header',basename($document),$mime_type);
+ echo $merged;
+ }
+ common::egw_exit();
+ }
+}