2010-02-15 23:39:13 +01:00
< ? php
2017-04-29 01:01:12 +02:00
2010-02-15 23:39:13 +01:00
require 'Segment.php' ;
2017-04-29 01:01:12 +02:00
2010-02-15 23:39:13 +01:00
class OdfException extends Exception
2017-04-29 01:01:12 +02:00
{
}
2010-02-15 23:39:13 +01:00
/**
* Templating class for odt file
* You need PHP 5.2 at least
* You need Zip Extension or PclZip library
*
2017-04-29 01:01:12 +02:00
* @ copyright 2008 - Julien Pauli - Cyril PIERRE de GEYER - Anaska ( http :// www . anaska . com )
* @ copyright 2010 - 2015 - Laurent Destailleur - eldy @ users . sourceforge . net
* @ copyright 2010 - Vikas Mahajan - http :// vikasmahajan . wordpress . com
* @ copyright 2012 - Stephen Larroque - lrq3000 @ gmail . com
2019-09-23 21:55:30 +02:00
* @ license https :// www . gnu . org / copyleft / gpl . html GPL License
2015-09-05 00:43:13 +02:00
* @ version 1.5 . 0
2010-02-15 23:39:13 +01:00
*/
class Odf
{
2010-03-13 17:05:36 +01:00
protected $config = array (
2013-04-20 11:03:23 +02:00
'ZIP_PROXY' => 'PclZipProxy' , // PclZipProxy, PhpZipProxy
'DELIMITER_LEFT' => '{' ,
'DELIMITER_RIGHT' => '}' ,
'PATH_TO_TMP' => '/tmp'
);
protected $file ;
protected $contentXml ; // To store content of content.xml file
2015-09-05 00:43:13 +02:00
protected $metaXml ; // To store content of meta.xml file
2013-04-20 11:03:23 +02:00
protected $stylesXml ; // To store content of styles.xml file
protected $manifestXml ; // To store content of META-INF/manifest.xml file
protected $tmpfile ;
protected $tmpdir = '' ;
protected $images = array ();
protected $vars = array ();
protected $segments = array ();
2017-10-10 18:54:30 +02:00
2015-09-05 00:43:13 +02:00
public $creator ;
public $title ;
public $subject ;
public $userdefined = array ();
2017-10-10 18:54:30 +02:00
2013-04-20 11:03:23 +02:00
const PIXEL_TO_CM = 0.026458333 ;
2017-10-10 18:54:30 +02:00
2013-04-20 11:03:23 +02:00
/**
* Class constructor
*
2017-04-29 01:01:12 +02:00
* @ param string $filename The name of the odt file
* @ param string $config Array of config data
2013-04-20 11:03:23 +02:00
* @ throws OdfException
*/
public function __construct ( $filename , $config = array ())
{
clearstatcache ();
if ( ! is_array ( $config )) {
throw new OdfException ( 'Configuration data must be provided as array' );
}
foreach ( $config as $configKey => $configValue ) {
if ( array_key_exists ( $configKey , $this -> config )) {
$this -> config [ $configKey ] = $configValue ;
2010-03-13 17:05:36 +01:00
}
2013-04-20 11:03:23 +02:00
}
2010-03-13 17:05:36 +01:00
2013-04-20 11:03:23 +02:00
$md5uniqid = md5 ( uniqid ());
if ( $this -> config [ 'PATH_TO_TMP' ]) $this -> tmpdir = preg_replace ( '|[\/]$|' , '' , $this -> config [ 'PATH_TO_TMP' ]); // Remove last \ or /
$this -> tmpdir .= ( $this -> tmpdir ? '/' : '' ) . $md5uniqid ;
$this -> tmpfile = $this -> tmpdir . '/' . $md5uniqid . '.odt' ; // We keep .odt extension to allow OpenOffice usage during debug.
2010-05-12 19:15:02 +02:00
2013-04-20 11:03:23 +02:00
// A working directory is required for some zip proxy like PclZipProxy
if ( in_array ( $this -> config [ 'ZIP_PROXY' ], array ( 'PclZipProxy' )) && ! is_dir ( $this -> config [ 'PATH_TO_TMP' ])) {
throw new OdfException ( 'Temporary directory ' . $this -> config [ 'PATH_TO_TMP' ] . ' must exists' );
}
2010-05-12 19:15:02 +02:00
2013-04-20 11:03:23 +02:00
// Create tmp direcoty (will be deleted in destructor)
if ( ! file_exists ( $this -> tmpdir )) {
$result = mkdir ( $this -> tmpdir );
}
2010-02-28 23:49:06 +01:00
2013-04-20 11:03:23 +02:00
// Load zip proxy
$zipHandler = $this -> config [ 'ZIP_PROXY' ];
if ( ! defined ( 'PCLZIP_TEMPORARY_DIR' )) define ( 'PCLZIP_TEMPORARY_DIR' , $this -> tmpdir );
include_once ( 'zip/' . $zipHandler . '.php' );
if ( ! class_exists ( $this -> config [ 'ZIP_PROXY' ])) {
throw new OdfException ( $this -> config [ 'ZIP_PROXY' ] . ' class not found - check your php settings' );
}
$this -> file = new $zipHandler ( $this -> tmpdir );
2010-03-13 17:05:36 +01:00
2013-04-20 11:03:23 +02:00
if ( $this -> file -> open ( $filename ) !== true ) { // This also create the tmpdir directory
throw new OdfException ( " Error while Opening the file ' $filename ' - Check your odt filename " );
2010-03-13 17:05:36 +01:00
}
2013-04-20 11:03:23 +02:00
if (( $this -> contentXml = $this -> file -> getFromName ( 'content.xml' )) === false ) {
throw new OdfException ( " Nothing to parse - Check that the content.xml file is correctly formed in source file ' $filename ' " );
}
if (( $this -> manifestXml = $this -> file -> getFromName ( 'META-INF/manifest.xml' )) === false ) {
throw new OdfException ( " Something is wrong with META-INF/manifest.xml in source file ' $filename ' " );
}
2015-09-05 00:43:13 +02:00
if (( $this -> metaXml = $this -> file -> getFromName ( 'meta.xml' )) === false ) {
throw new OdfException ( " Nothing to parse - Check that the meta.xml file is correctly formed in source file ' $filename ' " );
}
2013-04-20 11:03:23 +02:00
if (( $this -> stylesXml = $this -> file -> getFromName ( 'styles.xml' )) === false ) {
throw new OdfException ( " Nothing to parse - Check that the styles.xml file is correctly formed in source file ' $filename ' " );
}
$this -> file -> close ();
//print "tmpdir=".$tmpdir;
//print "filename=".$filename;
//print "tmpfile=".$tmpfile;
copy ( $filename , $this -> tmpfile );
2017-10-10 18:54:30 +02:00
// Now file has been loaded, we must move the [!-- BEGIN and [!-- END tags outside the
2016-06-22 20:22:22 +02:00
// <table:table-row tag and clean bad lines tags.
2013-04-20 11:03:23 +02:00
$this -> _moveRowSegments ();
}
/**
* Assing a template variable
*
2017-04-29 01:01:12 +02:00
* @ param string $key Name of the variable within the template
* @ param string $value Replacement value
* @ param bool $encode If true , special XML characters are encoded
2017-10-10 18:54:30 +02:00
* @ param string $charset Charset
2013-04-20 11:03:23 +02:00
* @ throws OdfException
* @ return odf
*/
public function setVars ( $key , $value , $encode = true , $charset = 'ISO-8859' )
{
$tag = $this -> config [ 'DELIMITER_LEFT' ] . $key . $this -> config [ 'DELIMITER_RIGHT' ];
// TODO Warning string may be:
// <text:span text:style-name="T13">{</text:span><text:span text:style-name="T12">aaa</text:span><text:span text:style-name="T13">}</text:span>
// instead of {aaa} so we should enhance this function.
//print $key.'-'.$value.'-'.strpos($this->contentXml, $this->config['DELIMITER_LEFT'] . $key . $this->config['DELIMITER_RIGHT']).'<br>';
2017-04-29 01:01:12 +02:00
if ( strpos ( $this -> contentXml , $tag ) === false && strpos ( $this -> stylesXml , $tag ) === false ) {
2013-04-11 18:38:50 +02:00
//if (strpos($this->contentXml, '">'. $key . '</text;span>') === false) {
2013-04-20 11:03:23 +02:00
throw new OdfException ( " var $key not found in the document " );
//}
2013-04-11 18:38:50 +02:00
}
2014-05-07 19:13:02 +02:00
$value = $this -> htmlToUTFAndPreOdf ( $value );
2013-04-20 11:03:23 +02:00
$value = $encode ? htmlspecialchars ( $value ) : $value ;
$value = ( $charset == 'ISO-8859' ) ? utf8_encode ( $value ) : $value ;
2014-05-07 19:13:02 +02:00
$value = $this -> preOdfToOdf ( $value );
$this -> vars [ $tag ] = $value ;
2013-04-20 11:03:23 +02:00
return $this ;
}
2014-05-07 19:13:02 +02:00
/**
* Function to convert a HTML string into an ODT string
*
* @ param string $value String to convert
*/
public function htmlToUTFAndPreOdf ( $value )
{
// We decode into utf8, entities
$value = dol_html_entity_decode ( $value , ENT_QUOTES );
// We convert html tags
$ishtml = dol_textishtml ( $value );
if ( $ishtml )
{
// If string is "MYPODUCT - Desc <strong>bold</strong> with é accent<br />\n<br />\nUn texto en español ?"
// Result after clean must be "MYPODUCT - Desc bold with é accent\n\nUn texto en español ?"
// We want to ignore \n and we want all <br> to be \n
$value = preg_replace ( '/(\r\n|\r|\n)/i' , '' , $value );
$value = preg_replace ( '/<br>/i' , " \n " , $value );
$value = preg_replace ( '/<br\s+[^<>\/]*>/i' , " \n " , $value );
$value = preg_replace ( '/<br\s+[^<>\/]*\/>/i' , " \n " , $value );
//$value=preg_replace('/<strong>/','__lt__text:p text:style-name=__quot__bold__quot____gt__',$value);
//$value=preg_replace('/<\/strong>/','__lt__/text:p__gt__',$value);
$value = dol_string_nohtmltag ( $value , 0 );
}
return $value ;
}
/**
* Function to convert a HTML string into an ODT string
*
* @ param string $value String to convert
*/
public function preOdfToOdf ( $value )
{
$value = str_replace ( " \n " , " <text:line-break/> " , $value );
//$value = str_replace("__lt__", "<", $value);
//$value = str_replace("__gt__", ">", $value);
//$value = str_replace("__quot__", '"', $value);
return $value ;
}
2013-04-20 11:03:23 +02:00
/**
* Evaluating php codes inside the ODT and output the buffer ( print , echo ) inplace of the code
*
2015-09-05 00:43:13 +02:00
* @ return int 0
2013-04-20 11:03:23 +02:00
*/
public function phpEval ()
{
preg_match_all ( '/[\{\<]\?(php)?\s+(?P<content>.+)\?[\}\>]/iU' , $this -> contentXml , $matches ); // detecting all {?php code ?} or <?php code ? >
2016-06-22 20:08:29 +02:00
$nbfound = count ( $matches [ 'content' ]);
2017-10-10 18:54:30 +02:00
for ( $i = 0 ; $i < $nbfound ; $i ++ )
2016-06-22 20:08:29 +02:00
{
2013-04-20 11:03:23 +02:00
try {
2013-04-07 17:27:35 +02:00
$ob_output = '' ; // flush the output for each code. This var will be filled in by the eval($code) and output buffering : any print or echo or output will be redirected into this variable
$code = $matches [ 'content' ][ $i ];
ob_start ();
eval ( $code );
$ob_output = ob_get_contents (); // send the content of the buffer into $ob_output
$this -> contentXml = str_replace ( $matches [ 0 ][ $i ], $ob_output , $this -> contentXml );
ob_end_clean ();
2013-04-20 11:03:23 +02:00
} catch ( Exception $e ) {
ob_end_clean ();
$this -> contentXml = str_replace ( $matches [ 0 ][ $i ], 'ERROR: there was a problem while evaluating this portion of code, please fix it: ' . $e , $this -> contentXml );
2010-03-13 17:05:36 +01:00
}
2013-04-20 11:03:23 +02:00
}
return 0 ;
}
/**
* Assign a template variable as a picture
*
* @ param string $key name of the variable within the template
* @ param string $value path to the picture
* @ throws OdfException
* @ return odf
*/
public function setImage ( $key , $value )
{
$filename = strtok ( strrchr ( $value , '/' ), '/.' );
$file = substr ( strrchr ( $value , '/' ), 1 );
$size = @ getimagesize ( $value );
if ( $size === false ) {
throw new OdfException ( " Invalid image " );
}
list ( $width , $height ) = $size ;
$width *= self :: PIXEL_TO_CM ;
$height *= self :: PIXEL_TO_CM ;
$xml = <<< IMG
2013-04-07 17:27:35 +02:00
< draw : frame draw : style - name = " fr1 " draw : name = " $filename " text : anchor - type = " aschar " svg : width = " { $width } cm " svg : height = " { $height } cm " draw : z - index = " 3 " >< draw : image xlink : href = " Pictures/ $file " xlink : type = " simple " xlink : show = " embed " xlink : actuate = " onLoad " /></ draw : frame >
2010-02-15 23:39:13 +01:00
IMG ;
2013-04-20 11:03:23 +02:00
$this -> images [ $value ] = $file ;
$this -> setVars ( $key , $xml , false );
return $this ;
}
/**
* Move segment tags for lines of tables
2016-06-22 20:08:29 +02:00
* This function is called automatically within the constructor , so this -> contentXml is clean before any other thing
2013-04-20 11:03:23 +02:00
*
* @ return void
*/
private function _moveRowSegments ()
{
2016-06-22 20:08:29 +02:00
// Replace BEGIN<text:s/>xxx into BEGIN xxx
$this -> contentXml = preg_replace ( '/\[!--\sBEGIN<text:s[^>]>(row.[\S]*)\s--\]/sm' , '[!-- BEGIN \\1 --]' , $this -> contentXml );
// Replace END<text:s/>xxx into END xxx
$this -> contentXml = preg_replace ( '/\[!--\sEND<text:s[^>]>(row.[\S]*)\s--\]/sm' , '[!-- END \\1 --]' , $this -> contentXml );
2017-10-10 18:54:30 +02:00
2016-06-22 20:08:29 +02:00
// Search all possible rows in the document
2013-04-20 11:03:23 +02:00
$reg1 = " #<table:table-row[^>]*>(.*)</table:table-row>#smU " ;
preg_match_all ( $reg1 , $this -> contentXml , $matches );
for ( $i = 0 , $size = count ( $matches [ 0 ]); $i < $size ; $i ++ ) {
// Check if the current row contains a segment row.*
$reg2 = '#\[!--\sBEGIN\s(row.[\S]*)\s--\](.*)\[!--\sEND\s\\1\s--\]#sm' ;
if ( preg_match ( $reg2 , $matches [ 0 ][ $i ], $matches2 )) {
$balise = str_replace ( 'row.' , '' , $matches2 [ 1 ]);
// Move segment tags around the row
$replace = array (
'[!-- BEGIN ' . $matches2 [ 1 ] . ' --]' => '' ,
'[!-- END ' . $matches2 [ 1 ] . ' --]' => '' ,
'<table:table-row' => '[!-- BEGIN ' . $balise . ' --]<table:table-row' ,
'</table:table-row>' => '</table:table-row>[!-- END ' . $balise . ' --]'
);
$replacedXML = str_replace ( array_keys ( $replace ), array_values ( $replace ), $matches [ 0 ][ $i ]);
$this -> contentXml = str_replace ( $matches [ 0 ][ $i ], $replacedXML , $this -> contentXml );
}
}
}
/**
* Merge template variables
2016-07-29 16:24:02 +02:00
* Called at the beginning of the _save function
2013-04-20 11:03:23 +02:00
*
2015-09-05 00:43:13 +02:00
* @ param string $type 'content' , 'styles' or 'meta'
2013-04-20 11:03:23 +02:00
* @ return void
*/
private function _parse ( $type = 'content' )
{
2015-11-02 15:44:15 +01:00
// Search all tags fou into condition to complete $this->vars, so we will proceed all tests even if not defined
2015-11-12 09:24:04 +01:00
$reg = '@\[!--\sIF\s([{}a-zA-Z0-9\.\,_]+)\s--\]@smU' ;
2015-11-02 15:44:15 +01:00
preg_match_all ( $reg , $this -> contentXml , $matches , PREG_SET_ORDER );
2017-10-10 18:54:30 +02:00
2015-11-02 15:44:15 +01:00
//var_dump($this->vars);exit;
foreach ( $matches as $match ) // For each match, if there is no entry into this->vars, we add it
{
if ( ! empty ( $match [ 1 ]) && ! isset ( $this -> vars [ $match [ 1 ]]))
{
$this -> vars [ $match [ 1 ]] = '' ; // Not defined, so we set it to '', we just need entry into this->vars for next loop
}
}
//var_dump($this->vars);exit;
2017-10-10 18:54:30 +02:00
2013-04-20 11:03:23 +02:00
// Conditionals substitution
2015-09-05 00:43:13 +02:00
// Note: must be done before static substitution, else the variable will be replaced by its value and the conditional won't work anymore
2015-11-02 15:44:15 +01:00
foreach ( $this -> vars as $key => $value )
2010-03-13 17:05:36 +01:00
{
2013-04-20 11:03:23 +02:00
// If value is true (not 0 nor false nor null nor empty string)
2015-11-02 15:44:15 +01:00
if ( $value )
2013-04-20 11:03:23 +02:00
{
2016-07-29 16:31:29 +02:00
//dol_syslog("Var ".$key." is defined, we remove the IF, ELSE and ENDIF ");
//$sav=$this->contentXml;
2013-04-20 11:03:23 +02:00
// Remove the IF tag
$this -> contentXml = str_replace ( '[!-- IF ' . $key . ' --]' , '' , $this -> contentXml );
// Remove everything between the ELSE tag (if it exists) and the ENDIF tag
$reg = '@(\[!--\sELSE\s' . $key . '\s--\](.*))?\[!--\sENDIF\s' . $key . '\s--\]@smU' ; // U modifier = all quantifiers are non-greedy
$this -> contentXml = preg_replace ( $reg , '' , $this -> contentXml );
2016-07-29 16:31:29 +02:00
/* if ( $sav != $this -> contentXml )
2016-07-29 16:24:02 +02:00
{
dol_syslog ( " We found a IF and it was processed " );
2016-07-29 16:31:29 +02:00
//var_dump($sav);exit;
} */
2013-04-20 11:03:23 +02:00
}
// Else the value is false, then two cases: no ELSE and we're done, or there is at least one place where there is an ELSE clause, then we replace it
else
{
2016-07-29 16:31:29 +02:00
//dol_syslog("Var ".$key." is not defined, we remove the IF, ELSE and ENDIF ");
//$sav=$this->contentXml;
2013-04-20 11:03:23 +02:00
// Find all conditional blocks for this variable: from IF to ELSE and to ENDIF
$reg = '@\[!--\sIF\s' . $key . '\s--\](.*)(\[!--\sELSE\s' . $key . '\s--\](.*))?\[!--\sENDIF\s' . $key . '\s--\]@smU' ; // U modifier = all quantifiers are non-greedy
preg_match_all ( $reg , $this -> contentXml , $matches , PREG_SET_ORDER );
foreach ( $matches as $match ) { // For each match, if there is an ELSE clause, we replace the whole block by the value in the ELSE clause
if ( ! empty ( $match [ 3 ])) $this -> contentXml = str_replace ( $match [ 0 ], $match [ 3 ], $this -> contentXml );
2010-03-13 17:05:36 +01:00
}
2013-04-20 11:03:23 +02:00
// Cleanup the other conditional blocks (all the others where there were no ELSE clause, we can just remove them altogether)
$this -> contentXml = preg_replace ( $reg , '' , $this -> contentXml );
2016-07-29 16:31:29 +02:00
/* if ( $sav != $this -> contentXml )
2016-07-29 16:24:02 +02:00
{
dol_syslog ( " We found a IF and it was processed " );
//var_dump($sav);exit;
2016-07-29 16:31:29 +02:00
} */
2010-03-13 17:05:36 +01:00
}
}
2010-05-12 19:15:02 +02:00
2015-09-05 00:43:13 +02:00
// Static substitution
2013-04-20 11:03:23 +02:00
if ( $type == 'content' ) $this -> contentXml = str_replace ( array_keys ( $this -> vars ), array_values ( $this -> vars ), $this -> contentXml );
if ( $type == 'styles' ) $this -> stylesXml = str_replace ( array_keys ( $this -> vars ), array_values ( $this -> vars ), $this -> stylesXml );
2015-09-05 00:43:13 +02:00
if ( $type == 'meta' ) $this -> metaXml = str_replace ( array_keys ( $this -> vars ), array_values ( $this -> vars ), $this -> metaXml );
2017-10-10 18:54:30 +02:00
2013-04-20 11:03:23 +02:00
}
/**
* Add the merged segment to the document
*
2016-06-22 20:08:29 +02:00
* @ param Segment $segment Segment
2013-04-20 11:03:23 +02:00
* @ throws OdfException
* @ return odf
*/
public function mergeSegment ( Segment $segment )
{
if ( ! array_key_exists ( $segment -> getName (), $this -> segments )) {
throw new OdfException ( $segment -> getName () . 'cannot be parsed, has it been set yet ?' );
}
$string = $segment -> getName ();
// $reg = '@<text:p[^>]*>\[!--\sBEGIN\s' . $string . '\s--\](.*)\[!--.+END\s' . $string . '\s--\]<\/text:p>@smU';
$reg = '@\[!--\sBEGIN\s' . $string . '\s--\](.*)\[!--.+END\s' . $string . '\s--\]@smU' ;
$this -> contentXml = preg_replace ( $reg , $segment -> getXmlParsed (), $this -> contentXml );
return $this ;
}
/**
* Display all the current template variables
*
* @ return string
*/
public function printVars ()
{
return print_r ( '<pre>' . print_r ( $this -> vars , true ) . '</pre>' , true );
}
/**
* Display the XML content of the file from odt document
* as it is at the moment
*
* @ return string
*/
public function __toString ()
{
return $this -> contentXml ;
}
/**
* Display loop segments declared with setSegment ()
*
* @ return string
*/
public function printDeclaredSegments ()
{
return '<pre>' . print_r ( implode ( ' ' , array_keys ( $this -> segments )), true ) . '</pre>' ;
}
/**
2016-03-22 19:24:48 +01:00
* Declare a segment in order to use it in a loop .
* Extract the segment and store it into $this -> segments [] . Return it for next call .
2013-04-20 11:03:23 +02:00
*
2016-06-22 20:08:29 +02:00
* @ param string $segment Segment
2013-04-20 11:03:23 +02:00
* @ throws OdfException
* @ return Segment
*/
public function setSegment ( $segment )
{
if ( array_key_exists ( $segment , $this -> segments )) {
return $this -> segments [ $segment ];
}
// $reg = "#\[!--\sBEGIN\s$segment\s--\]<\/text:p>(.*)<text:p\s.*>\[!--\sEND\s$segment\s--\]#sm";
$reg = " # \ [!-- \ sBEGIN \ s $segment\s -- \ ](.*) \ [!-- \ sEND \ s $segment\s -- \ ]#sm " ;
if ( preg_match ( $reg , html_entity_decode ( $this -> contentXml ), $m ) == 0 ) {
2016-06-22 20:08:29 +02:00
throw new OdfException ( " ' " . $segment . " ' segment not found in the document. The tag [!-- BEGIN xxx --] or [!-- END xxx --] is not present into content file. " );
2013-04-20 11:03:23 +02:00
}
$this -> segments [ $segment ] = new Segment ( $segment , $m [ 1 ], $this );
return $this -> segments [ $segment ];
}
/**
* Save the odt file on the disk
*
* @ param string $file name of the desired file
* @ throws OdfException
* @ return void
*/
public function saveToDisk ( $file = null )
{
if ( $file !== null && is_string ( $file )) {
if ( file_exists ( $file ) && ! ( is_file ( $file ) && is_writable ( $file ))) {
throw new OdfException ( 'Permission denied : can\'t create ' . $file );
2010-03-13 17:05:36 +01:00
}
2013-04-20 11:03:23 +02:00
$this -> _save ();
copy ( $this -> tmpfile , $file );
} else {
$this -> _save ();
2010-03-13 17:05:36 +01:00
}
2013-04-20 11:03:23 +02:00
}
/**
* Write output file onto disk
*
* @ throws OdfException
* @ return void
*/
private function _save ()
{
$res = $this -> file -> open ( $this -> tmpfile ); // tmpfile is odt template
$this -> _parse ( 'content' );
$this -> _parse ( 'styles' );
2015-09-05 00:43:13 +02:00
$this -> _parse ( 'meta' );
2013-04-20 11:03:23 +02:00
2015-09-05 00:43:13 +02:00
$this -> setMetaData ();
//print $this->metaXml;exit;
2017-10-10 18:54:30 +02:00
2013-04-20 11:03:23 +02:00
if ( ! $this -> file -> addFromString ( 'content.xml' , $this -> contentXml )) {
2015-09-05 00:43:13 +02:00
throw new OdfException ( 'Error during file export addFromString content' );
}
if ( ! $this -> file -> addFromString ( 'meta.xml' , $this -> metaXml )) {
throw new OdfException ( 'Error during file export addFromString meta' );
2013-04-20 11:03:23 +02:00
}
if ( ! $this -> file -> addFromString ( 'styles.xml' , $this -> stylesXml )) {
2015-09-05 00:43:13 +02:00
throw new OdfException ( 'Error during file export addFromString styles' );
2013-04-20 11:03:23 +02:00
}
2017-10-10 18:54:30 +02:00
2013-04-20 11:03:23 +02:00
foreach ( $this -> images as $imageKey => $imageValue ) {
// Add the image inside the ODT document
$this -> file -> addFile ( $imageKey , 'Pictures/' . $imageValue );
// Add the image to the Manifest (which maintains a list of images, necessary to avoid "Corrupt ODT file. Repair?" when opening the file with LibreOffice)
$this -> addImageToManifest ( $imageValue );
}
if ( ! $this -> file -> addFromString ( './META-INF/manifest.xml' , $this -> manifestXml )) {
throw new OdfException ( 'Error during file export: manifest.xml' );
}
$this -> file -> close ();
}
2015-09-05 00:43:13 +02:00
/**
* Update Meta information
* < dc : date > 2013 - 03 - 16 T14 : 06 : 25 </ dc : date >
*
* @ return void
*/
public function setMetaData ()
{
if ( empty ( $this -> creator )) $this -> creator = '' ;
2017-10-10 18:54:30 +02:00
2015-09-05 00:43:13 +02:00
$this -> metaXml = preg_replace ( '/<dc:date>.*<\/dc:date>/' , '<dc:date>' . gmdate ( " Y-m-d \T H:i:s " ) . '</dc:date>' , $this -> metaXml );
$this -> metaXml = preg_replace ( '/<dc:creator>.*<\/dc:creator>/' , '<dc:creator>' . htmlspecialchars ( $this -> creator ) . '</dc:creator>' , $this -> metaXml );
$this -> metaXml = preg_replace ( '/<dc:title>.*<\/dc:title>/' , '<dc:title>' . htmlspecialchars ( $this -> title ) . '</dc:title>' , $this -> metaXml );
$this -> metaXml = preg_replace ( '/<dc:subject>.*<\/dc:subject>/' , '<dc:subject>' . htmlspecialchars ( $this -> subject ) . '</dc:subject>' , $this -> metaXml );
2017-10-10 18:54:30 +02:00
2015-09-05 00:43:13 +02:00
if ( count ( $this -> userdefined ))
{
foreach ( $this -> userdefined as $key => $val )
{
$this -> metaXml = preg_replace ( '<meta:user-defined meta:name="' . $key . '"/>' , '' , $this -> metaXml );
$this -> metaXml = preg_replace ( '/<meta:user-defined meta:name="' . $key . '">.*<\/meta:user-defined>/' , '' , $this -> metaXml );
$this -> metaXml = str_replace ( '</office:meta>' , '<meta:user-defined meta:name="' . $key . '">' . htmlspecialchars ( $val ) . '</meta:user-defined></office:meta>' , $this -> metaXml );
}
}
}
2017-10-10 18:54:30 +02:00
2013-04-20 11:03:23 +02:00
/**
* Update Manifest file according to added image files
*
* @ param string $file Image file to add into manifest content
2015-09-05 00:43:13 +02:00
* @ return void
2013-04-20 11:03:23 +02:00
*/
public function addImageToManifest ( $file )
{
// Get the file extension
$ext = substr ( strrchr ( $val , '.' ), 1 );
// Create the correct image XML entry to add to the manifest (this is necessary because ODT format requires that we keep a list of the images in the manifest.xml)
$add = ' <manifest:file-entry manifest:media-type="image/' . $ext . '" manifest:full-path="Pictures/' . $file . '"/>' . " \n " ;
// Append the image to the manifest
$this -> manifestXml = str_replace ( '</manifest:manifest>' , $add . '</manifest:manifest>' , $this -> manifestXml ); // we replace the manifest closing tag by the image XML entry + manifest closing tag (this results in appending the data, we do not overwrite anything)
}
/**
* Export the file as attached file by HTTP
*
* @ param string $name ( optional )
* @ throws OdfException
* @ return void
*/
public function exportAsAttachedFile ( $name = " " )
{
$this -> _save ();
if ( headers_sent ( $filename , $linenum )) {
throw new OdfException ( " headers already sent ( $filename at $linenum ) " );
2010-03-13 17:05:36 +01:00
}
2010-05-12 19:15:02 +02:00
2013-04-20 11:03:23 +02:00
if ( $name == " " )
2010-03-13 17:05:36 +01:00
{
2013-04-20 11:03:23 +02:00
$name = md5 ( uniqid ()) . " .odt " ;
2010-03-13 17:05:36 +01:00
}
2010-05-12 19:15:02 +02:00
2013-04-20 11:03:23 +02:00
header ( 'Content-type: application/vnd.oasis.opendocument.text' );
header ( 'Content-Disposition: attachment; filename="' . $name . '"' );
header ( 'Content-Length: ' . filesize ( $this -> tmpfile ));
readfile ( $this -> tmpfile );
}
/**
* Convert the ODT file to PDF and export the file as attached file by HTTP
* Note : you need to have JODConverter and OpenOffice or LibreOffice installed and executable on the same system as where this php script will be executed . You also need to chmod + x odt2pdf . sh
*
2018-02-15 13:15:21 +01:00
* @ param string $name Name of ODT file to generate before generating PDF
2013-04-20 11:03:23 +02:00
* @ throws OdfException
* @ return void
*/
public function exportAsAttachedPDF ( $name = " " )
{
global $conf ;
2014-05-07 19:13:02 +02:00
2017-10-10 18:54:30 +02:00
if ( $name == " " ) $name = " temp " . md5 ( uniqid ());
2013-04-20 11:03:23 +02:00
2013-04-22 15:50:26 +02:00
dol_syslog ( get_class ( $this ) . '::exportAsAttachedPDF $name=' . $name , LOG_DEBUG );
2013-04-20 11:03:23 +02:00
$this -> saveToDisk ( $name );
$execmethod = ( empty ( $conf -> global -> MAIN_EXEC_USE_POPEN ) ? 1 : 2 ); // 1 or 2
2017-10-10 18:54:30 +02:00
// Method 1 sometimes hang the server.
2014-05-07 19:13:02 +02:00
2018-02-15 13:15:21 +01:00
// Export to PDF using LibreOffice
2018-02-15 10:15:27 +01:00
if ( $conf -> global -> MAIN_ODT_AS_PDF == 'libreoffice' )
2015-02-22 16:01:01 +01:00
{
2018-02-15 14:38:21 +01:00
// using windows libreoffice that must be in path
// using linux/mac libreoffice that must be in path
// Note PHP Config "fastcgi.impersonate=0" must set to 0 - Default is 1
2019-04-27 17:31:14 +02:00
$command = 'soffice --headless --convert-to pdf --outdir ' . escapeshellarg ( dirname ( $name )) . " " . escapeshellarg ( $name );
2018-02-15 13:15:21 +01:00
}
elseif ( preg_match ( '/unoconv/' , $conf -> global -> MAIN_ODT_AS_PDF ))
2015-02-22 16:01:01 +01:00
{
2017-10-10 18:54:30 +02:00
// If issue with unoconv, see https://github.com/dagwieers/unoconv/issues/87
// MAIN_ODT_AS_PDF should be "sudo -u unoconv /usr/bin/unoconv" and userunoconv must have sudo to be root by adding file /etc/sudoers.d/unoconv with content www-data ALL=(unoconv) NOPASSWD: /usr/bin/unoconv .
// Try this with www-data user: /usr/bin/unoconv -vvvv -f pdf /tmp/document-example.odt
// It must return:
//Verbosity set to level 4
//Using office base path: /usr/lib/libreoffice
//Using office binary path: /usr/lib/libreoffice/program
//DEBUG: Connection type: socket,host=127.0.0.1,port=2002;urp;StarOffice.ComponentContext
//DEBUG: Existing listener not found.
//DEBUG: Launching our own listener using /usr/lib/libreoffice/program/soffice.bin.
//LibreOffice listener successfully started. (pid=9287)
//Input file: /tmp/document-example.odt
//unoconv: file `/tmp/document-example.odt' does not exist.
//unoconv: RuntimeException during import phase:
//Office probably died. Unsupported URL <file:///tmp/document-example.odt>: "type detection failed"
//DEBUG: Terminating LibreOffice instance.
//DEBUG: Waiting for LibreOffice instance to exit
2018-02-15 13:23:13 +01:00
// If it fails:
// - set shell of user to bash instead of nologin.
2017-10-10 18:54:30 +02:00
// - set permission to read/write to user on home directory /var/www so user can create the libreoffice , dconf and .cache dir and files then set permission back
$command = $conf -> global -> MAIN_ODT_AS_PDF . ' ' . escapeshellcmd ( $name );
//$command = '/usr/bin/unoconv -vvv '.escapeshellcmd($name);
2015-02-22 16:01:01 +01:00
}
else
{
2017-10-10 18:54:30 +02:00
// deprecated old method
2018-02-15 13:15:21 +01:00
$tmpname = preg_replace ( '/\.odt/i' , '' , $name );
2014-05-07 19:13:02 +02:00
2017-10-10 18:54:30 +02:00
if ( ! empty ( $conf -> global -> MAIN_DOL_SCRIPTS_ROOT ))
{
2018-02-15 13:15:21 +01:00
$command = $conf -> global -> MAIN_DOL_SCRIPTS_ROOT . '/scripts/odt2pdf/odt2pdf.sh ' . escapeshellcmd ( $tmpname ) . ' ' . ( is_numeric ( $conf -> global -> MAIN_ODT_AS_PDF ) ? 'jodconverter' : $conf -> global -> MAIN_ODT_AS_PDF );
2017-10-10 18:54:30 +02:00
}
else
{
dol_syslog ( get_class ( $this ) . '::exportAsAttachedPDF is used but the constant MAIN_DOL_SCRIPTS_ROOT with path to script directory was not defined.' , LOG_WARNING );
2018-02-15 13:15:21 +01:00
$command = '../../scripts/odt2pdf/odt2pdf.sh ' . escapeshellcmd ( $tmpname ) . ' ' . ( is_numeric ( $conf -> global -> MAIN_ODT_AS_PDF ) ? 'jodconverter' : $conf -> global -> MAIN_ODT_AS_PDF );
2018-02-15 10:15:27 +01:00
}
2017-10-10 18:54:30 +02:00
}
2014-05-07 19:13:02 +02:00
2013-04-22 15:50:26 +02:00
//$dirname=dirname($name);
//$command = DOL_DOCUMENT_ROOT.'/includes/odtphp/odt2pdf.sh '.$name.' '.$dirname;
2017-10-10 18:54:30 +02:00
2013-04-22 12:08:35 +02:00
dol_syslog ( get_class ( $this ) . '::exportAsAttachedPDF $execmethod=' . $execmethod . ' Run command=' . $command , LOG_DEBUG );
2019-04-27 17:31:14 +02:00
$retval = 0 ; $output_arr = array ();
2013-04-20 11:03:23 +02:00
if ( $execmethod == 1 )
2010-03-13 17:05:36 +01:00
{
2013-04-20 11:03:23 +02:00
exec ( $command , $output_arr , $retval );
2010-03-13 17:05:36 +01:00
}
2013-04-20 11:03:23 +02:00
if ( $execmethod == 2 )
2010-03-13 17:05:36 +01:00
{
2017-10-10 18:54:30 +02:00
$outputfile = DOL_DATA_ROOT . '/odt2pdf.log' ;
2018-02-15 10:15:27 +01:00
2013-04-20 11:03:23 +02:00
$ok = 0 ;
$handle = fopen ( $outputfile , 'w' );
if ( $handle )
{
2013-04-22 15:50:26 +02:00
dol_syslog ( get_class ( $this ) . " Run command " . $command , LOG_DEBUG );
2017-10-10 18:54:30 +02:00
fwrite ( $handle , $command . " \n " );
2013-04-20 11:03:23 +02:00
$handlein = popen ( $command , 'r' );
while ( ! feof ( $handlein ))
{
$read = fgets ( $handlein );
2017-10-10 18:54:30 +02:00
fwrite ( $handle , $read );
2013-04-20 11:03:23 +02:00
$output_arr [] = $read ;
2010-03-13 17:05:36 +01:00
}
2013-04-20 11:03:23 +02:00
pclose ( $handlein );
fclose ( $handle );
2010-03-13 17:05:36 +01:00
}
2013-04-20 11:03:23 +02:00
if ( ! empty ( $conf -> global -> MAIN_UMASK )) @ chmod ( $outputfile , octdec ( $conf -> global -> MAIN_UMASK ));
2010-03-13 17:05:36 +01:00
}
2010-05-12 19:15:02 +02:00
2017-10-10 18:54:30 +02:00
if ( $retval == 0 )
2010-05-12 19:15:02 +02:00
{
2013-04-22 12:08:35 +02:00
dol_syslog ( get_class ( $this ) . '::exportAsAttachedPDF $ret_val=' . $retval , LOG_DEBUG );
2019-04-27 17:31:14 +02:00
$filename = '' ; $linenum = 0 ;
2010-03-13 17:05:36 +01:00
if ( headers_sent ( $filename , $linenum )) {
throw new OdfException ( " headers already sent ( $filename at $linenum ) " );
2010-02-15 23:39:13 +01:00
}
2010-03-13 17:05:36 +01:00
2013-04-22 15:50:26 +02:00
if ( ! empty ( $conf -> global -> MAIN_DISABLE_PDF_AUTOUPDATE )) {
2018-02-15 13:15:21 +01:00
$name = preg_replace ( '/\.od(x|t)/i' , '' , $name );
2013-04-20 11:03:23 +02:00
header ( 'Content-type: application/pdf' );
header ( 'Content-Disposition: attachment; filename="' . $name . '.pdf"' );
2018-02-15 13:15:21 +01:00
readfile ( $name . " .pdf " );
2013-04-22 15:50:26 +02:00
}
2014-01-06 19:27:12 +01:00
if ( ! empty ( $conf -> global -> MAIN_ODT_AS_PDF_DEL_SOURCE ))
2018-02-15 13:15:21 +01:00
{
unlink ( $name );
}
2013-04-20 11:03:23 +02:00
} else {
2013-04-22 15:50:26 +02:00
dol_syslog ( get_class ( $this ) . '::exportAsAttachedPDF $ret_val=' . $retval , LOG_DEBUG );
2019-04-27 17:31:14 +02:00
dol_syslog ( get_class ( $this ) . '::exportAsAttachedPDF $output_arr=' . var_export ( $output_arr , true ), LOG_DEBUG );
2014-05-07 19:13:02 +02:00
2013-04-22 15:50:26 +02:00
if ( $retval == 126 ) {
throw new OdfException ( 'Permission execute convert script : ' . $command );
}
else {
2019-04-27 17:31:14 +02:00
$errorstring = '' ;
2013-04-22 15:50:26 +02:00
foreach ( $output_arr as $line ) {
2019-04-27 17:31:14 +02:00
$errorstring .= $line . " <br> " ;
2013-04-22 15:50:26 +02:00
}
2019-04-27 17:31:14 +02:00
throw new OdfException ( 'ODT to PDF convert fail (option MAIN_ODT_AS_PDF is ' . $conf -> global -> MAIN_ODT_AS_PDF . ', command was ' . $command . ', retval=' . $retval . ') : ' . $errorstring );
2013-04-22 15:50:26 +02:00
}
2010-02-15 23:39:13 +01:00
}
2013-04-20 11:03:23 +02:00
}
/**
* Returns a variable of configuration
*
2017-04-29 01:01:12 +02:00
* @ param string $configKey Config key
* @ return string The requested variable of configuration
2013-04-20 11:03:23 +02:00
*/
public function getConfig ( $configKey )
{
if ( array_key_exists ( $configKey , $this -> config )) {
return $this -> config [ $configKey ];
}
return false ;
}
/**
* Returns the temporary working file
*
* @ return string le chemin vers le fichier temporaire de travail
*/
public function getTmpfile ()
{
return $this -> tmpfile ;
}
/**
* Delete the temporary file when the object is destroyed
*/
public function __destruct ()
{
if ( file_exists ( $this -> tmpfile )) {
unlink ( $this -> tmpfile );
2010-03-13 17:05:36 +01:00
}
2010-02-28 23:49:06 +01:00
2013-04-20 11:03:23 +02:00
if ( file_exists ( $this -> tmpdir )) {
$this -> _rrmdir ( $this -> tmpdir );
rmdir ( $this -> tmpdir );
}
}
/**
* Empty the temporary working directory recursively
2017-10-10 18:54:30 +02:00
*
2017-04-29 01:01:12 +02:00
* @ param string $dir The temporary working directory
2013-04-20 11:03:23 +02:00
* @ return void
*/
private function _rrmdir ( $dir )
{
if ( $handle = opendir ( $dir )) {
2019-06-18 20:15:04 +02:00
while (( $file = readdir ( $handle )) !== false ) {
2013-04-20 11:03:23 +02:00
if ( $file != '.' && $file != '..' ) {
if ( is_dir ( $dir . '/' . $file )) {
$this -> _rrmdir ( $dir . '/' . $file );
rmdir ( $dir . '/' . $file );
} else {
unlink ( $dir . '/' . $file );
2010-03-13 17:05:36 +01:00
}
}
}
2013-04-20 11:03:23 +02:00
closedir ( $handle );
2010-03-13 17:05:36 +01:00
}
2013-04-20 11:03:23 +02:00
}
2014-11-01 21:00:28 +01:00
/**
* return the value present on odt in [ valuename ][ / valuename ]
2017-10-10 18:54:30 +02:00
*
2017-04-29 01:01:12 +02:00
* @ param string $valuename Balise in the template
* @ return string The value inside the balise
2014-11-01 21:00:28 +01:00
*/
public function getvalue ( $valuename )
{
$searchreg = " / \\ [ " . $valuename . " \\ ](.*) \\ [ \\ / " . $valuename . " \\ ]/ " ;
2015-02-22 16:01:01 +01:00
preg_match ( $searchreg , $this -> contentXml , $matches );
2014-11-01 21:00:28 +01:00
$this -> contentXml = preg_replace ( $searchreg , " " , $this -> contentXml );
return $matches [ 1 ];
}
2010-02-15 23:39:13 +01:00
}