2009-08-26 19:12:43 +02:00
< ? php
/**
* EGroupware - Document merge print
*
* @ link http :// www . egroupware . org
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
* @ package addressbook
2010-02-22 00:15:05 +01:00
* @ copyright ( c ) 2007 - 10 by Ralf Becker < RalfBecker - AT - outdoor - training . de >
2009-08-26 19:12:43 +02:00
* @ 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 ;
2009-10-14 10:12:11 +02:00
/**
* Plugins registered by extending class to create a table with multiple rows
*
* $$table / $plugin $ $ ... $$endtable $ $
*
* Callback returns replacements for row $n ( stringing with 0 ) or null if no further rows
*
* @ var array $plugin => array callback ( $plugin , $id , $n )
*/
var $table_plugins = array ();
2009-08-26 19:12:43 +02:00
/**
* 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
*/
2011-01-27 18:08:40 +01:00
abstract protected function get_replacements ( $id , & $content = null );
2009-08-26 19:12:43 +02:00
/**
* 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
2009-09-01 12:25:36 +02:00
case 'application/vnd.oasis.opendocument.text' : // oo text
case 'application/vnd.oasis.opendocument.spreadsheet' : // oo spreadsheet
2009-08-26 19:12:43 +02:00
if ( ! $zip_available ) break ;
return true ; // open office write xml files
2009-09-01 16:41:48 +02:00
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : // ms word 2007 xml format
2009-08-26 19:12:43 +02:00
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d' : // mimetypes in vfs are limited to 64 chars
2009-09-01 16:41:48 +02:00
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : // ms excel 2007 xml format
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.shee' :
2009-08-26 19:12:43 +02:00
if ( ! $zip_available ) break ;
return true ; // ms word xml format
2009-11-03 12:05:09 +01:00
case 'application/xml' :
return true ; // alias for text/xml, eg. ms office 2003 word format
2009-08-26 19:12:43 +02:00
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
2011-01-27 18:08:40 +01:00
* @ param string $prefix = '' prefix like eg . 'user'
2009-08-26 19:12:43 +02:00
* @ return array
*/
2009-08-26 22:50:14 +02:00
public function contact_replacements ( $contact , $prefix = '' )
2009-08-26 19:12:43 +02:00
{
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 ;
}
2011-02-16 18:47:31 +01:00
// Add in extra cat field
$cats = array ();
foreach ( is_array ( $contact [ 'cat_id' ]) ? $contact [ 'cat_id' ] : explode ( ',' , $contact [ 'cat_id' ]) as $cat_id )
{
if ( ! $cat_id ) continue ;
2011-02-16 18:56:00 +01:00
if ( $GLOBALS [ 'egw' ] -> categories -> id2name ( $cat_id , 'main' ) != $cat_id )
{
2011-02-17 17:40:57 +01:00
$path = $GLOBALS [ 'egw' ] -> categories -> id2name ( $cat_id , 'path' );
$path = explode ( ' / ' , $path );
unset ( $path [ 0 ]); // Drop main
$cats [ $GLOBALS [ 'egw' ] -> categories -> id2name ( $cat_id , 'main' )][] = implode ( ' / ' , $path );
2011-02-16 18:56:00 +01:00
}
}
foreach ( $cats as $main => $cat ) {
$replacements [ '$$' . ( $prefix ? $prefix . '/' : '' ) . 'categories$$' ] .= $GLOBALS [ 'egw' ] -> categories -> id2name ( $main , 'name' )
. ( count ( $cat ) > 0 ? ': ' : '' ) . implode ( ', ' , $cats [ $main ]) . " \n " ;
2011-02-16 18:47:31 +01:00
}
2009-08-26 19:12:43 +02:00
return $replacements ;
}
/**
* Format a datetime
*
2009-12-24 03:00:51 +01:00
* @ param int | string | DateTime $time unix timestamp or Y - m - d H : i : s string ( in user time ! )
2009-08-26 19:12:43 +02:00
* @ param string $format = null format string , default $this -> datetime_format
2009-12-24 03:00:51 +01:00
* @ deprecated use egw_time :: to ( $time = 'now' , $format = '' )
2009-08-26 19:12:43 +02:00
* @ return string
*/
protected function format_datetime ( $time , $format = null )
{
if ( is_null ( $format )) $format = $this -> datetime_format ;
2009-12-24 03:00:51 +01:00
return egw_time :: to ( $time , $format );
2009-08-26 19:12:43 +02:00
}
/**
* 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
*/
2011-01-27 18:08:40 +01:00
public function & merge ( $document , $ids , & $err , $mimetype , array $fix = null )
2009-08-26 19:12:43 +02:00
{
if ( ! ( $content = file_get_contents ( $document )))
{
$err = lang ( " Document '%1' does not exist or is not readable for you! " , $document );
return false ;
}
2010-10-27 11:34:42 +02:00
// fix application/msword mimetype for rtf files
if ( $mimetype == 'application/msword' && strtolower ( substr ( $document , - 4 )) == '.rtf' )
{
$mimetype = 'application/rtf' ;
}
2011-01-27 18:08:40 +01:00
return $this -> merge_string ( $content , $ids , $err , $mimetype , $fix );
2010-10-27 11:34:42 +02:00
}
/**
* Merges a given document with contact data
*
* @ param string $content
* @ 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
*/
2011-01-27 18:08:40 +01:00
public function & merge_string ( $content , $ids , & $err , $mimetype , array $fix = null )
2010-10-27 11:34:42 +02:00
{
2010-03-08 22:56:05 +01:00
if ( $mimetype == 'application/xml' &&
preg_match ( '/' . preg_quote ( '<?mso-application progid="' ) . '([^"]+)' . preg_quote ( '"?>' ) . '/' , substr ( $content , 0 , 200 ), $matches ))
{
$mso_application_progid = $matches [ 1 ];
}
else
{
$mso_application_progid = '' ;
}
2009-11-04 15:18:11 +01:00
// alternative syntax using double curly brackets (eg. {{cat_id}} instead $$cat_id$$),
// agressivly removing all xml-tags eg. Word adds within placeholders
$content = preg_replace_callback ( '/{{[^}]+}}/i' , create_function ( '$p' , 'return \'$$\'.strip_tags(substr($p[0],2,-2)).\'$$\';' ), $content );
2009-08-26 19:12:43 +02:00
// 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("<pre>".htmlspecialchars($content)."</pre>\n");
}
list ( $contentstart , $contentrepeat , $contentend ) = preg_split ( '/\$\$pagerepeat\$\$/' , $content , - 1 , PREG_SPLIT_NO_EMPTY ); //get differt parts of document, seperatet by Pagerepeat
2010-12-15 11:50:29 +01:00
if ( $mimetype == 'text/plain' && count ( $ids ) > 1 )
{
// textdocuments are simple, they do not hold start and end, but they may have content before and after the $$pagerepeat$$ tag
// header and footer should not hold any $$ tags; if we find $$ tags with the header, we assume it is the pagerepeatcontent
2010-12-15 13:06:53 +01:00
$nohead = false ;
2010-12-15 11:50:29 +01:00
if ( stripos ( $contentstart , '$$' ) !== false ) $nohead = true ;
if ( $nohead )
{
$contentend = $contentrepeat ;
$contentrepeat = $contentstart ;
$contentstart = '' ;
}
}
2009-08-26 19:12:43 +02:00
if ( $mimetype == 'application/vnd.oasis.opendocument.text' && count ( $ids ) > 1 )
{
2009-12-03 17:15:23 +01:00
//for odt files we have to split the content and add a style for page break to the style area
2009-08-26 19:12:43 +02:00
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 . '<style:style style:name="P200" style:family="paragraph" style:parent-style-name="Standard"><style:paragraph-properties fo:break-before="page"/></style:style></office:automatic-styles>' ;
$contentstart .= '<office:body>' ;
$contentend = '</office:body></office:document-content>' ;
}
2009-09-13 14:06:40 +02:00
if ( $mimetype == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' && count ( $ids ) > 1 )
{
2009-12-03 17:15:23 +01:00
//for Word 2007 XML files we have to split the content and add a style for page break to the style area
2009-09-13 14:06:40 +02:00
list ( $contentstart , $contentrepeat , $contentend ) = preg_split ( '/w: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 "</";
$contentstart .= '<w:body>' ;
$contentend = '</w:body></w:document>' ;
}
2009-08-26 19:12:43 +02:00
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 );
2009-12-30 14:59:08 +01:00
$countlables = count ( $countlables [ 0 ]);
2009-08-26 19:12:43 +02:00
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
2011-01-27 18:08:40 +01:00
if ( ! ( $replacements = $this -> get_replacements ( $id , $content )))
2009-08-26 19:12:43 +02:00
{
$err = lang ( 'Entry not found!' );
return false ;
2011-01-08 17:04:44 +01:00
}
2009-08-26 19:12:43 +02:00
// 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' );
}
2009-12-24 03:00:51 +01:00
$replacements [ '$$date$$' ] = egw_time :: to ( 'now' , true );
$replacements [ '$$datetime$$' ] = egw_time :: to ( 'now' );
$replacements [ '$$time$$' ] = egw_time :: to ( 'now' , false );
2009-08-26 19:12:43 +02:00
2009-10-14 10:12:11 +02:00
// does our extending class registered table-plugins AND document contains table tags
if ( $this -> table_plugins && preg_match_all ( '/\\$\\$table\\/([A-Za-z0-9_]+)\\$\\$(.*?)\\$\\$endtable\\$\\$/s' , $content , $matches , PREG_SET_ORDER ))
2009-10-09 13:22:23 +02:00
{
2009-10-14 10:12:11 +02:00
// process each table
foreach ( $matches as $match )
{
$plugin = $match [ 1 ]; // plugin name
$callback = $this -> table_plugins [ $plugin ];
$repeat = $match [ 2 ]; // line to repeat
$repeats = '' ;
if ( isset ( $callback ))
{
2011-02-07 12:08:08 +01:00
for ( $n = 0 ; ( $row_replacements = $this -> $callback ( $plugin , $id , $n , $repeat )); ++ $n )
2009-10-14 10:12:11 +02:00
{
2010-03-08 22:56:05 +01:00
$repeats .= $this -> replace ( $repeat , $row_replacements , $mimetype , $mso_application_progid );
2009-10-14 10:12:11 +02:00
}
}
$content = str_replace ( $match [ 0 ], $repeats , $content );
}
2009-10-07 19:46:09 +02:00
}
2010-03-08 22:56:05 +01:00
$content = $this -> replace ( $content , $replacements , $mimetype , $mso_application_progid );
2009-10-14 10:12:11 +02:00
2009-08-26 19:12:43 +02:00
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 );
}
2009-09-23 15:00:59 +02:00
if ( strpos ( $content , '$$NELF' ))
{ //Example: $$NEPBR org_unit$$ sets a LF and value of org_unit, only if there is a value
$this -> replacements =& $replacements ;
$content = preg_replace_callback ( '/\$\$NELF ([0-9a-z_-]+)\$\$/imU' , Array ( $this , 'replace_callback' ), $content );
unset ( $this -> replacements );
}
if ( strpos ( $content , '$$NENVLF' ))
{ //Example: $$NEPBRNV org_unit$$ sets only a LF if there is a value for org_units, but did not add any value
$this -> replacements =& $replacements ;
$content = preg_replace_callback ( '/\$\$NENVLF ([0-9a-z_-]+)\$\$/imU' , Array ( $this , 'replace_callback' ), $content );
unset ( $this -> replacements );
}
2009-11-27 17:37:54 +01:00
if ( strpos ( $content , '$$LETTERPREFIX$$' ))
{ //Example use to use: $$LETTERPREFIX$$
$LETTERPREFIXCUSTOM = '$$LETTERPREFIXCUSTOM n_prefix title n_family$$' ;
$content = str_replace ( '$$LETTERPREFIX$$' , $LETTERPREFIXCUSTOM , $content );
}
if ( strpos ( $content , '$$LETTERPREFIXCUSTOM' ))
{ //Example use to use for a custom Letter Prefix: $$LETTERPREFIX n_prefix title n_family$$
$this -> replacements =& $replacements ;
$content = preg_replace_callback ( '/\$\$LETTERPREFIXCUSTOM ([0-9a-z_-]+)(.*)\$\$/imU' , Array ( $this , 'replace_callback' ), $content );
unset ( $this -> replacements );
}
2009-08-26 19:12:43 +02:00
// remove not existing replacements (eg. from calendar array)
if ( strpos ( $content , '$$' ) !== null )
{
2009-09-01 16:41:48 +02:00
$content = preg_replace ( '/\$\$[a-z0-9_\/]+\$\$/i' , '' , $content );
2009-08-26 19:12:43 +02:00
}
2011-02-16 18:47:31 +01:00
if ( $contentrepeat ) $contentrep [ is_array ( $id ) ? implode ( ':' , $id ) : $id ] = $content ;
2009-08-26 19:12:43 +02:00
}
if ( $Labelrepeat )
{
$countpage = 0 ;
$count = 0 ;
$contentrepeatpages [ $countpage ] = $Labelstart . $Labeltend ;
foreach ( $contentrep as $Label )
{
2009-12-30 14:59:08 +01:00
$contentrepeatpages [ $countpage ] = preg_replace ( '/\$\$labelplacement\$\$/' , $Label , $contentrepeatpages [ $countpage ], 1 );
2009-08-26 19:12:43 +02:00
$count = $count + 1 ;
2009-12-30 14:59:08 +01:00
if (( $count % $countlables ) == 0 && count ( $contentrep ) > $count ) //new page
2009-08-26 19:12:43 +02:00
{
2009-12-30 14:59:08 +01:00
$countpage = $countpage + 1 ;
2009-08-26 19:12:43 +02:00
$contentrepeatpages [ $countpage ] = $Labelstart . $Labeltend ;
}
}
$contentrepeatpages [ $countpage ] = preg_replace ( '/\$\$labelplacement\$\$/' , '' , $contentrepeatpages [ $countpage ], - 1 ); //clean empty fields
switch ( $mimetype )
{
case 'application/rtf' :
case 'text/rtf' :
return $contentstart . implode ( '\\par \\page\\pard\\plain' , $contentrepeatpages ) . $contentend ;
case 'application/vnd.oasis.opendocument.text' :
2009-09-01 12:25:36 +02:00
case 'application/vnd.oasis.opendocument.spreadsheet' :
2009-08-26 19:12:43 +02:00
// todo OO writer files
break ;
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' :
2009-09-01 16:41:48 +02:00
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' :
2009-09-13 14:06:40 +02:00
return $contentstart . implode ( '<w:br w:type="page" />' , $contentrep ) . $contentend ;
2010-12-15 11:50:29 +01:00
case 'text/plain' :
return $contentstart . implode ( " \r \n " , $contentrep ) . $contentend ;
2009-08-26 19:12:43 +02:00
}
$err = lang ( '%1 not implemented for %2!' , '$$labelplacement$$' , $mimetype );
return false ;
}
if ( $contentrepeat )
{
switch ( $mimetype )
{
case 'application/rtf' :
case 'text/rtf' :
return $contentstart . implode ( '\\par \\page\\pard\\plain' , $contentrep ) . $contentend ;
case 'application/vnd.oasis.opendocument.text' :
2009-09-01 12:25:36 +02:00
case 'application/vnd.oasis.opendocument.spreadsheet' :
2009-12-03 17:15:23 +01:00
case 'application/xml' :
2009-08-26 19:12:43 +02:00
return $contentstart . implode ( '' , $contentrep ) . $contentend ;
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' :
2009-09-01 16:41:48 +02:00
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' :
2009-09-13 14:06:40 +02:00
return $contentstart . implode ( '<w:br w:type="page" />' , $contentrep ) . $contentend ;
2010-12-15 11:50:29 +01:00
case 'text/plain' :
return $contentstart . implode ( " \r \n " , $contentrep ) . $contentend ;
2009-08-26 19:12:43 +02:00
}
$err = lang ( '%1 not implemented for %2!' , '$$pagerepeat$$' , $mimetype );
return false ;
}
return $content ;
}
2009-10-14 10:12:11 +02:00
/**
* Replace placeholders in $content of $mimetype with $replacements
*
* @ param string $content
* @ param array $replacements name => replacement pairs
* @ param string $mimetype mimetype of content
2010-03-08 22:56:05 +01:00
* @ param string $mso_application_progid = '' MS Office 2003 : 'Excel.Sheet' or 'Word.Document'
2009-10-14 10:12:11 +02:00
* @ return string
*/
2010-03-08 22:56:05 +01:00
protected function replace ( $content , array $replacements , $mimetype , $mso_application_progid = '' )
2009-10-14 10:12:11 +02:00
{
switch ( $mimetype )
{
case 'application/vnd.oasis.opendocument.text' : // open office
case 'application/vnd.oasis.opendocument.spreadsheet' :
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : // ms office 2007
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' :
2009-11-03 14:10:49 +01:00
case 'application/xml' :
2009-10-14 10:12:11 +02:00
case 'text/xml' :
2010-08-23 14:03:03 +02:00
case 'text/html' :
2009-10-14 10:12:11 +02:00
$is_xml = true ;
$charset = 'utf-8' ; // xml files --> always use utf-8
break ;
default : // div. text files --> use our export-charset, defined in addressbook prefs
$charset = $this -> contacts -> prefs [ 'csv_charset' ];
break ;
}
2010-08-23 14:03:03 +02:00
//error_log(__METHOD__."('$document', ... ,$mimetype) --> $charset (egw=".translation::charset().', export='.$this->contacts->prefs['csv_charset'].')');
2009-10-14 10:12:11 +02:00
// do we need to convert charset
2010-08-23 14:03:03 +02:00
if ( $charset && $charset != translation :: charset ())
2009-10-14 10:12:11 +02:00
{
2010-08-23 14:03:03 +02:00
$replacements = translation :: convert ( $replacements , translation :: charset (), $charset );
2009-10-14 10:12:11 +02:00
}
2010-02-22 00:15:05 +01:00
if ( $is_xml ) // zip'ed xml document (eg. OO)
2009-10-14 10:12:11 +02:00
{
2011-02-09 21:11:17 +01:00
// clean replacements from array values and html or html-entities, which mess up xml
2010-02-22 00:15:05 +01:00
foreach ( $replacements as $name => & $value )
{
2011-02-09 21:11:17 +01:00
// set unresolved array values to empty string
if ( is_array ( $value ))
{
$value = '' ;
continue ;
}
2010-02-22 00:15:05 +01:00
// decode html entities back to utf-8
2011-02-09 21:11:17 +01:00
if ( is_string ( $value ) && ( strpos ( $value , '&' ) !== false ))
2010-02-22 00:15:05 +01:00
{
$value = html_entity_decode ( $value , ENT_QUOTES , $charset );
2010-03-08 12:34:22 +01:00
2010-02-22 00:15:05 +01:00
// remove all non-decodable entities
if ( strpos ( $value , '&' ) !== false )
{
$value = preg_replace ( '/&[^; ]+;/' , '' , $value );
}
}
// remove all html tags, evtl. included
2011-02-09 21:11:17 +01:00
if ( is_string ( $value ) && ( strpos ( $value , '<' ) !== false ))
2010-02-22 00:15:05 +01:00
{
2010-03-08 22:56:05 +01:00
// replace </p> and <br /> with CRLF (remove <p> and CRLF)
$value = str_replace ( array ( " \r " , " \n " , '<p>' , '</p>' , '<br />' ), array ( '' , '' , '' , " \r \n " , " \r \n " ), $value );
2010-02-22 00:15:05 +01:00
$value = strip_tags ( $value );
}
2010-03-16 14:38:20 +01:00
// replace all control chars (C0+C1) but CR (\015), LF (\012) and TAB (\011) (eg. vertical tabulators) with space
2010-03-08 12:34:22 +01:00
// as they are not allowed in xml
2010-03-16 14:38:20 +01:00
$value = preg_replace ( '/[\000-\010\013\014\016-\037\177-\237]/u' , ' ' , $value );
2010-02-22 00:15:05 +01:00
}
2010-03-08 22:56:05 +01:00
// replace CRLF with linebreak tag of given type
switch ( $mimetype . $mso_application_progid )
{
case 'application/vnd.oasis.opendocument.text' : // open office writer
$break = '<text:line-break/>' ;
break ;
case 'application/vnd.oasis.opendocument.spreadsheet' : // open office calc
$break = '<text:line-break/>' ;
break ;
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : // ms word 2007
$break = '<w:br/>' ;
break ;
case 'application/xmlExcel.Sheet' : // Excel 2003
$break = ' ' ;
break ;
case 'application/xmlWord.Document' : // Word 2003*/
$break = '<w:br/>' ;
break ;
case 'text/html' :
$break = '<br/>' ;
break ;
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : // ms excel 2007
default :
$break = " \r \n " ;
break ;
}
2010-02-22 00:15:05 +01:00
// now decode &, < and >, which need to be encoded as entities in xml
2010-03-08 22:56:05 +01:00
$replacements = str_replace ( array ( '&' , '<' , '>' , " \r " , " \n " ), array ( '&' , '<' , '>' , '' , $break ), $replacements );
2009-10-14 10:12:11 +02:00
}
return str_replace ( array_keys ( $replacements ), array_values ( $replacements ), $content );
}
2009-08-26 19:12:43 +02:00
/**
* 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 ];
2009-10-01 21:37:16 +02:00
switch ( $this -> mimetype )
{
case 'application/rtf' :
case 'text/rtf' :
$LF = '}\par \pard\plain{' ;
break ;
case 'application/vnd.oasis.opendocument.text' :
case 'application/vnd.oasis.opendocument.spreadsheet' :
2009-12-21 11:47:02 +01:00
$LF = '</text:span></text:p><text:p><text:span>' ; //span is needed here
2009-10-01 21:37:16 +02:00
break ;
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' :
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' :
$LF = '</w:r></w:p><w:r><w:t>' ;
break ;
}
2009-09-23 15:00:59 +02:00
if ( strpos ( $param [ 0 ], '$$NELF' ) === 0 )
{ //sets a Pagebreak and value, only if the field has a value
if ( $this -> replacements [ '$$' . $param [ 1 ] . '$$' ] != '' ) $replace = $LF . $this -> replacements [ '$$' . $param [ 1 ] . '$$' ];
}
if ( strpos ( $param [ 0 ], '$$NENVLF' ) === 0 )
{ //sets a Pagebreak without any value, only if the field has a value
if ( $this -> replacements [ '$$' . $param [ 1 ] . '$$' ] != '' ) $replace = $LF ;
}
2009-11-27 17:37:54 +01:00
if ( strpos ( $param [ 0 ], '$$LETTERPREFIXCUSTOM' ) === 0 )
{ //sets a Letterprefix
$replaceprefix = array ();
2009-12-28 00:51:25 +01:00
// ToDo Stefan: $contentstart is NOT defined here!!!
2009-11-27 17:37:54 +01:00
$replaceprefix = explode ( ' ' , substr ( $param [ 0 ], 21 , strlen ( $contentstart ) - 2 ));
foreach ( $replaceprefix as $key => $nameprefix )
{
if ( $this -> replacements [ '$$' . $nameprefix . '$$' ] != '' ) $replaceprefixsort [] = $this -> replacements [ '$$' . $nameprefix . '$$' ];
}
$replace = implode ( $replaceprefixsort , ' ' );
}
2009-08-26 19:12:43 +02:00
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
*/
2011-01-27 18:08:40 +01:00
public function download ( $document , $ids )
2009-08-26 19:12:43 +02:00
{
$content_url = egw_vfs :: PREFIX . $document ;
2009-10-09 13:22:23 +02:00
switch (( $mimetype = egw_vfs :: mime_content_type ( $document )))
2009-08-26 19:12:43 +02:00
{
case 'application/vnd.oasis.opendocument.text' :
2009-09-01 12:25:36 +02:00
case 'application/vnd.oasis.opendocument.spreadsheet' :
2009-10-09 13:22:23 +02:00
$ext = $mimetype == 'application/vnd.oasis.opendocument.text' ? '.odt' : '.ods' ;
2009-09-01 12:25:36 +02:00
$archive = tempnam ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'temp_dir' ], basename ( $document , $ext ) . '-' ) . $ext ;
2009-08-26 19:12:43 +02:00
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
2009-10-09 13:22:23 +02:00
$mimetype = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ;
2009-08-26 19:12:43 +02:00
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' :
2009-10-09 16:39:55 +02:00
$archive = tempnam ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'temp_dir' ], basename ( $document , '.docx' ) . '-' ) . '.docx' ;
2009-08-26 19:12:43 +02:00
copy ( $content_url , $archive );
$content_url = 'zip://' . $archive . '#' . ( $content_file = 'word/document.xml' );
$fix = array ( // regular expression to fix garbled placeholders
'/' . preg_quote ( '$$</w:t></w:r><w:proofErr w:type="spellStart"/><w:r><w:t>' , '/' ) . '([a-z0-9_]+)' .
preg_quote ( '</w:t></w:r><w:proofErr w:type="spellEnd"/><w:r><w:t>' , '/' ) . '/i' => '$$\\1$$' ,
'/' . preg_quote ( '$$</w:t></w:r><w:proofErr w:type="spellStart"/><w:r><w:rPr><w:lang w:val="' , '/' ) .
'([a-z]{2}-[A-Z]{2})' . preg_quote ( '"/></w:rPr><w:t>' , '/' ) . '([a-z0-9_]+)' .
preg_quote ( '</w:t></w:r><w:proofErr w:type="spellEnd"/><w:r><w:rPr><w:lang w:val="' , '/' ) .
'([a-z]{2}-[A-Z]{2})' . preg_quote ( '"/></w:rPr><w:t>$$' , '/' ) . '/i' => '$$\\2$$' ,
2009-09-13 14:06:40 +02:00
'/' . preg_quote ( '$</w:t></w:r><w:proofErr w:type="spellStart"/><w:r><w:t>' , '/' ) . '([a-z0-9_]+)' .
preg_quote ( '</w:t></w:r><w:proofErr w:type="spellEnd"/><w:r><w:t>' , '/' ) . '/i' => '$\\1$' ,
'/' . preg_quote ( '$ $</w:t></w:r><w:proofErr w:type="spellStart"/><w:r><w:t>' , '/' ) . '([a-z0-9_]+)' .
preg_quote ( '</w:t></w:r><w:proofErr w:type="spellEnd"/><w:r><w:t>' , '/' ) . '/i' => '$ $\\1$ $' ,
2009-08-26 19:12:43 +02:00
);
break ;
2009-12-28 00:51:25 +01:00
case 'application/xml' :
$fix = array ( // hack to get Excel 2003 to display additional rows in tables
'/ss:ExpandedRowCount="\d+"/' => 'ss:ExpandedRowCount="9999"' ,
);
break ;
2009-09-01 16:41:48 +02:00
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.shee' :
2009-10-09 13:22:23 +02:00
$mimetype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ;
2009-09-01 16:41:48 +02:00
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' :
2009-12-28 00:51:25 +01:00
$fix = array ( // hack to get Excel 2007 to display additional rows in tables
'/ss:ExpandedRowCount="\d+"/' => 'ss:ExpandedRowCount="9999"' ,
);
2009-09-01 16:41:48 +02:00
$archive = tempnam ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'temp_dir' ], basename ( $document , '.xlsx' ) . '-' ) . '.xlsx' ;
copy ( $content_url , $archive );
$content_url = 'zip://' . $archive . '#' . ( $content_file = 'xl/sharedStrings.xml' );
break ;
2009-08-26 19:12:43 +02:00
}
2011-01-27 18:08:40 +01:00
if ( ! ( $merged =& $this -> merge ( $content_url , $ids , $err , $mimetype , $fix )))
2009-08-26 19:12:43 +02:00
{
return $err ;
}
if ( isset ( $archive ))
{
$zip = new ZipArchive ;
2011-01-13 11:57:20 +01:00
if ( $zip -> open ( $archive , ZIPARCHIVE :: CHECKCONS ) !== true )
{
error_log ( __METHOD__ . __LINE__ . " !ZipArchive::open(' $archive ',ZIPARCHIVE::CHECKCONS) failed. Trying open without validating " );
if ( $zip -> open ( $archive ) !== true ) throw new Exception ( " !ZipArchive::open(' $archive ',|ZIPARCHIVE::CHECKCONS) " );
}
2009-08-26 19:12:43 +02:00
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 );
2009-10-09 16:39:55 +02:00
if ( substr ( $mimetype , 0 , 35 ) == 'application/vnd.oasis.opendocument.' && // only open office archives need that, ms word files brake
file_exists ( '/usr/bin/zip' ) && version_compare ( PHP_VERSION , '5.3.1' , '<' )) // fix broken zip archives generated by current php
2009-08-26 19:12:43 +02:00
{
exec ( '/usr/bin/zip -F ' . escapeshellarg ( $archive ));
}
2009-10-11 13:37:46 +02:00
html :: content_header ( basename ( $document ), $mimetype , filesize ( $archive ));
2009-08-26 19:12:43 +02:00
readfile ( $archive , 'r' );
}
else
{
2009-11-14 17:08:26 +01:00
if ( $mimetype == 'application/xml' )
2009-11-03 12:55:12 +01:00
{
2009-11-14 17:08:26 +01:00
if ( strpos ( $merged , '<?mso-application progid="Word.Document"?>' ) !== false )
{
$mimetype = 'application/msword' ; // to open it automatically in word or oowriter
}
elseif ( strpos ( $merged , '<?mso-application progid="Excel.Sheet"?>' ) !== false )
{
$mimetype = 'application/vnd.ms-excel' ; // to open it automatically in excel or oocalc
}
2009-11-03 12:55:12 +01:00
}
2009-10-09 13:22:23 +02:00
ExecMethod2 ( 'phpgwapi.browser.content_header' , basename ( $document ), $mimetype );
2009-08-26 19:12:43 +02:00
echo $merged ;
}
common :: egw_exit ();
}
2011-02-04 21:04:17 +01:00
/**
* Get a list of document actions / files from the given directory
*
* @ param dir Directory to search
*
* @ return List of documents , suitable for a selectbox . The key is document_ < filename >.
*/
public static function get_documents ( $dir ) {
if ( ! $dir ) return array ();
$list = array ();
if (( $files = egw_vfs :: find ( $dir , array ( 'need_mime' => true ), true )))
{
foreach ( $files as $file )
{
// return only the mime-types we support
if ( ! self :: is_implemented ( $file [ 'mime' ], substr ( $file [ 'name' ], - 4 ))) continue ;
$list [ 'document_' . $file [ 'name' ]] = /*lang('Insert in document').': '.*/ $file [ 'name' ];
}
}
return $list ;
}
2009-08-26 19:12:43 +02:00
}