* @version $Id$
*/
class tinyDoc extends clsTinyButStrong
{
const VERSION = '1.0.3';
const PICTURE_DPI = 96;
const INCH_TO_CM = 2.54;
const PIXEL_TO_CM = 0.0264583333; // 2.54 / 96
private
$zipMethod = 'shell',
$zipBin = 'zip',
$unzipBin = 'unzip',
$sourcePathname = '',
$processDir = '',
$processBasename = '',
$defaultExtension = 'odt',
$xmlFilename = 'content.xml',
$defaultCharset = 'UTF-8',
$defaultIsEscape = true,
$defaultCallback = '=~encodeData',
$charset = 'UTF-8',
$isEscape = true,
$callback = '=~encodeData',
$mimetype = array(
'sxw' => 'application/vnd.sun.xml.writer',
'stw' => 'application/vnd.sun.xml.writer.template',
'sxg' => 'application/vnd.sun.xml.writer.global',
'sxc' => 'application/vnd.sun.xml.calc',
'stc' => 'application/vnd.sun.xml.calc.template',
'sxi' => 'application/vnd.sun.xml.impress',
'sti' => 'application/vnd.sun.xml.impress.template',
'sxd' => 'application/vnd.sun.xml.draw',
'std' => 'application/vnd.sun.xml.draw.template',
'sxm' => 'application/vnd.sun.xml.math',
'odt' => 'application/vnd.oasis.OpenDocument.text',
'ott' => 'application/vnd.oasis.OpenDocument.text-template',
'oth' => 'application/vnd.oasis.OpenDocument.text-web',
'odm' => 'application/vnd.oasis.OpenDocument.text-master',
'odg' => 'application/vnd.oasis.OpenDocument.graphics',
'otg' => 'application/vnd.oasis.OpenDocument.graphics-template',
'odp' => 'application/vnd.oasis.OpenDocument.presentation',
'otp' => 'application/vnd.oasis.OpenDocument.presentation-template',
'ods' => 'application/vnd.oasis.OpenDocument.spreadsheet',
'ots' => 'application/vnd.oasis.OpenDocument.spreadsheet-template',
'odc' => 'application/vnd.oasis.OpenDocument.chart',
'odf' => 'application/vnd.oasis.OpenDocument.formula',
'odb' => 'application/vnd.oasis.OpenDocument.database',
'odi' => 'application/vnd.oasis.OpenDocument.image',
'docm' => 'application/vnd.ms-word.document.macroEnabled.12',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
);
/**
* Constructor.
*/
public function __construct()
{
}
/**
* Create a new unique file from a source file
*
* @param mixed $options The pathname of source document (empty array by default)
*/
public function createFrom($options = array())
{
$this->setSourcePathname($options);
// create an unique basename to process the office document
$this->newUniqueBasename();
// create an unique directory like basename to zip/unzip files
if (!mkdir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename(), 0777, true))
{
throw new tinyDocException(sprintf('Can\'t make directory "%s"', $this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename()));
}
if (!is_dir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename()))
{
throw new tinyDocException(sprintf('Directory not found "%s"', $this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename()));
}
// copy the file source into the process dir with an unique filename
if (!copy($this->getSourcePathname(), $this->getPathname()))
{
if (!file_exists($this->getSourcePathname()))
{
throw new tinyDocException(sprintf('Can\'t copy file, source file not found "%s"', $this->getSourcePathname()));
}
else
{
throw new tinyDocException(sprintf('Can\'t copy file from "%s" to "%s"', $this->getSourcePathname(), $this->getPathname()));
}
}
}
/**
* Load the XML file from the current process file as a TBS template
*
* @param string $xmlFilename The XML file (content.xml by default)
*/
public function loadXml($xmlFilename = 'content.xml')
{
$this->setXmlFilename($xmlFilename);
// unzip the XML file into the current basename process dir
switch($this->getZipMethod())
{
case 'ziparchive':
$zip = new ZipArchive();
if ($zip->open($this->getPathname()) === true)
{
$zip->extractTo($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR, array($this->getXmlFilename()));
$zip->close();
}
break;
case 'pclzip':
require_once('pclzip.lib.php');
$zip = new PclZip($this->getPathname());
$zip->extract(PCLZIP_OPT_PATH,$this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR,
PCLZIP_OPT_BY_NAME,$this->getXmlFilename());
break;
case 'shell':
default:
$cmd = $this->getUnzipBinary();
$cmd.= ' '.escapeshellarg($this->getPathname());
$cmd.= ' -d';
$cmd.= ' '.escapeshellarg($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename());
$cmd.= ' '.escapeshellarg($this->getXmlFilename());
exec($cmd);
break;
}
// test if the XML file exist
if (!file_exists($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$this->getXmlFilename()))
{
throw new tinyDocException(sprintf('Xml file not found "%s"', $this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$this->getXmlFilename()));
}
// load the XML file as a TBS template
$this->ObjectRef = $this;
$this->LoadTemplate($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$this->getXmlFilename(), $this->getCallback());
// work around - convert apostrophe in XML file needed for TBS functions
$this->Source = str_replace(''', '\'', $this->Source);
}
/**
* Merge data with the template file
*
* Available options:
*
* - name: The tag name in the template ('block' by default)
* - type: The tag type in the template ('field' | 'block' - 'block' by default)
* - data_type: The data type ('array' by default)
* - charset: The data charset ('UTF-8' by default)
* - is_escape: If data are escaped (true by default)
* - callback: The callback to encode data ('=~encodeData' by default)
*
* @param array $options Options
* @param mixed $data Data
*/
public function mergeXml($options = array(), $data = array())
{
$name = isset($options['name']) && is_string($options['name']) ? $options['name'] : 'block';
$type = isset($options['type']) && is_string($options['type']) ? $options['type'] : 'block';
$data_type = isset($options['data_type']) && is_string($options['data_type']) ? $options['data_type'] : 'array';
$this->setCharset (isset($options['charset']) && is_string($options['charset']) ? $options['charset'] : $this->getDefaultCharset());
$this->setIsEscape(isset($options['is_escape']) && is_bool($options['is_escape']) ? $options['is_escape'] : $this->getDefaultIsEscape());
$this->setCallback(isset($options['callback']) && is_string($options['callback']) ? $options['callback'] : $this->getDefaultCallback());
switch($type)
{
case 'field':
$this->MergeField($name, $data);
break;
case 'block':
default:
$this->MergeBlock($name, $data_type, $data);
break;
}
}
/**
* Merge data with a TBS field in template
*
* @param string $name TBS field name in template
* @param mixed $data Data to merge
*/
public function mergeXmlField($name, $data)
{
$this->mergeXml(array('name' => $name, 'type' => 'field'), $data);
}
/**
* Merge data with a TBS block in template
*
* @param string $name TBS block name in template
* @param mixed $data Data to merge
*/
public function mergeXmlBlock($name, $data)
{
$this->mergeXml(array('name' => $name, 'type' => 'block'), $data);
}
/**
* Save the result of merged XML file into the current process file
*/
public function saveXml()
{
// get the source result (TBS method)
$this->Show(TBS_NOTHING);
// update the result into the current process file
switch($this->getZipMethod())
{
case 'ziparchive':
$zip = new ZipArchive();
if ($zip->open($this->getPathname()) === true)
{
$zip->addFromString($this->getXmlFilename(), $this->Source);
$zip->close();
}
break;
case 'pclzip':
require_once('pclzip.lib.php');
// save the merged result
$fdw = fopen($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$this->getXmlFilename(), "w");
fwrite($fdw, $this->Source, strlen($this->Source));
fclose ($fdw);
// change the current directory to basename in process dir
$cwd = getcwd();
chdir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR);
$zip = new PclZip($this->getPathname());
$zip->add($this->getXmlFilename());
// get back current directory
chdir($cwd);
break;
case 'shell':
default:
// save the merged result
$fdw = fopen($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$this->getXmlFilename(), "w");
fwrite($fdw, $this->Source, strlen($this->Source));
fclose ($fdw);
// change the current directory to basename in process dir
$cwd = getcwd();
chdir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR);
// zip the XML file into the archive
$cmd = $this->getZipBinary();
$cmd.= ' -m';
$cmd.= ' '.escapeshellarg($this->getPathname());
$cmd.= ' '.escapeshellarg($this->getXmlFilename());
exec($cmd);
// get back current directory
chdir($cwd);
break;
}
}
/**
* Encode the data before merge
*
* @param string $encodeData The data to merge
*
* @return string The encoded data
*/
public function encodeData($encodeData)
{
// XML charset of OpenOffice / OpenDocument / Word 2007 is utf-8
switch($this->getCharset())
{
case 'ISO 8859-1':
case 'ISO 8859-15':
$encodeData = utf8_encode($encodeData);
break;
case 'UTF-8':
case 'UTF8':
default:
break;
}
// convert special XML chars
$encodeData = str_replace('&' ,'&', $encodeData); // the '&' has to be the first to convert
$encodeData = str_replace('\'' ,''', $encodeData);
$encodeData = str_replace('"' ,'"', $encodeData);
if ($this->getIsEscape())
{
$encodeData = str_replace('<', '<', $encodeData);
$encodeData = str_replace('>', '>', $encodeData);
}
// convert TAB & LF & CR to XML
switch($this->getExtension())
{
case 'docm':
case 'docx':
case 'dotm':
case 'dotx':
$encodeData = str_replace("\r\n", '', $encodeData);
$encodeData = str_replace("\n", '', $encodeData);
$encodeData = str_replace("\r", '', $encodeData);
$encodeData = str_replace("\t", '' , $encodeData);
break;
case 'ods':
case 'sxc':
$encodeData = str_replace("\r\n", '' , $encodeData);
$encodeData = str_replace("\n", '' , $encodeData);
$encodeData = str_replace("\r", '' , $encodeData);
$encodeData = str_replace("\t", '' , $encodeData);
// work-around for EURO caracter
$encodeData = str_replace(chr(0xC2).chr(0x80) , chr(0xE2).chr(0x82).chr(0xAC), $encodeData);
break;
case 'odt':
case 'sxw':
default:
$encodeData = str_replace("\r\n", '', $encodeData);
$encodeData = str_replace("\n", '', $encodeData);
$encodeData = str_replace("\r", '', $encodeData);
$encodeData = str_replace("\t", '' , $encodeData);
// work-around for EURO caracter
$encodeData = str_replace(chr(0xC2).chr(0x80) , chr(0xE2).chr(0x82).chr(0xAC), $encodeData);
break;
}
// remove specials chars from 0x00 to 0x1F
$encodeData = preg_replace('/[\x{00}-\x{1F}]+/u', '', $encodeData);
return $encodeData;
}
/**
* Create a unique id and set the basename of process file
*/
public function newUniqueBasename()
{
$unique = md5(uniqid(rand(), true));
$this->setBasename($unique);
}
/**
* Close by remove the current process directory and subdirectory
*/
public function close()
{
// remove the current process directory
clearstatcache();
if (is_dir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename()))
{
$this->deltree($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename());
rmdir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename());
}
}
/**
* Remove a directory and his contents recursively
*
* @param string $dir The pathname the directory
*/
private function deltree($dir)
{
// changed by kevin - makes no sense on shared hosting.
// for example open basedir settings restrict access to /
// if (realpath($dir) == realpath(DIRECTORY_SEPARATOR))
if (realpath($dir) == DIRECTORY_SEPARATOR)
{
return false;
}
foreach(glob($dir.DIRECTORY_SEPARATOR.'*') as $filename)
{
if (is_dir($filename) && !is_link($filename))
{
$this->deltree($filename);
if (is_writable($filename))
{
rmdir($filename);
}
}
else
{
if (is_writable($filename))
{
unlink($filename);
}
}
}
}
/**
* Remove the current process file
*/
public function remove()
{
unlink($this->getPathname());
}
/**
* Add an external file into the current process file
*
* GIF images are not supported in WORD 2007
*
* @param string $sourcePathname The pathname of the source file to add
* @param string $archivePathname The pathname in the archive
*/
public function addFile($sourcePathname, $archivePathname)
{
if (file_exists($sourcePathname))
{
switch($this->getZipMethod())
{
case 'ziparchive':
$zip = new ZipArchive();
if ($zip->open($this->getPathname()) === true)
{
// don't work - archive corrupted
// $zip->addFile($sourcePathname, $archivePathname);
$zip->addFromString($archivePathname, file_get_contents($sourcePathname, false));
$zip->close();
}
break;
case 'pclzip':
require_once('pclzip.lib.php');
$zip = new PclZip($this->getPathname());
$zip->add($sourcePathname,
PCLZIP_OPT_ADD_PATH,$archivePathname,
PCLZIP_OPT_REMOVE_ALL_PATH);
break;
case 'shell':
default:
$dir = dirname($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$archivePathname);
if (!file_exists($dir))
{
mkdir($dir, 0777, true);
}
if (copy($sourcePathname, $this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$archivePathname))
{
// change the current directory to basename in process dir
$cwd = getcwd();
chdir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR);
// zip the file into the archive
$cmd = $this->getZipBinary();
$cmd.= ' -u';
$cmd.= ' '.escapeshellarg($this->getPathname());
$cmd.= ' '.escapeshellarg($archivePathname);
exec($cmd);
// get back current directory
chdir($cwd);
}
break;
}
}
}
/**
* This function is obsolete, use parameter 'image' to merge pictures.
*
* Hack to change in the XML source the tags for image. Only tags with [*.src] work.
* The TBS tag need to set with OpenOffice into the title field of the image property.
*
* Experimental : only for OpenDocument/OpenOffice. Image in blocks don't work with spreadsheet.
*/
public function tagXmlImage()
{
$doc = new DOMDocument();
$doc->loadXML($this->Source);
$draw_frames = $doc->getElementsByTagName('frame'); // draw:frame
foreach ($draw_frames as $draw_frame)
{
$svg_titles = $draw_frame->getElementsByTagName('title'); //svg:title
if ($svg_titles->length > 0)
{
$tag = $svg_titles->item(0)->nodeValue;
if (preg_match('/\[.*src\]/', $tag))
{
$draw_images = $draw_frame->getElementsByTagName('image'); // draw:image
if ($draw_images->length > 0)
{
$draw_image = $draw_images->item(0);
$draw_image->removeAttribute('href'); // xlink:href
$draw_image->setAttribute('xlink:href', $tag);
}
}
}
}
$this->Source = $doc->saveXML();
}
/**
* Send the response
*/
public function sendResponse($options = array())
{
header('Content-Type: '.$this->getMimetype());
header('Content-Disposition: attachment; filename="'.$this->getDownloadFilename().'"');
header('Content-Length: '.$this->getSize());
echo $this->getContent();
}
/**
* Get the basename of the current process file
*
* @return string The basename
*/
public function getBasename()
{
return $this->processBasename;
}
/**
* Get the callback method to encode data
*
* @return string The callback method
*/
public function getCallback()
{
return $this->callback;
}
/**
* Get the charset of data to be merged
*
* @return string The charset
*/
public function getCharset()
{
return $this->charset;
}
/**
* Get the binary content of the current process file
*
* @return stream The binary content
*/
public function getContent()
{
return file_get_contents($this->getPathname(), false);
}
/**
* Get the default callback method to encode data
*
* @return string The default callback method
*/
public function getDefaultCallback()
{
return $this->defaultCallback;
}
/**
* Get the default charset of data
*
* @return string The default charset
*/
public function getDefaultCharset()
{
return $this->defaultCharset;
}
/**
* Get the default extension of source file
*
* @return string The default extension
*/
public function getDefaultExtension()
{
return $this->defaultExtension;
}
/**
* Get the default if data are escaped
*
* @return string The default
*/
public function getDefaultIsEscape()
{
return $this->defaultIsEscape;
}
/**
* Get the filename to download as the current template by default
*
* @return string The download filename
*/
public function getDownloadFilename($options = array())
{
return basename($this->getSourcePathname());
}
/**
* Get the extension of the current process file
*
* @return string The extension
*/
public function getExtension()
{
$info = pathinfo($this->getSourcePathname());
return $info['extension'];
}
/**
* Get the filename of the current process file
*
* @return string The filename
*/
public function getFilename()
{
return $this->getBasename().'.'.$this->getExtension();
}
/**
* Get if data are escaped
*
* @return boolean
*/
public function getIsEscape()
{
return $this->isEscape;
}
/**
* Get the mimetype of the current process file
*
* @return string The mimetype
*/
public function getMimetype()
{
return isset($this->mimetype[$this->getExtension()]) ? $this->mimetype[$this->getExtension()] : null;
}
/**
* Get the pathname of the current process file
*
* @return string The pathname
*/
public function getPathname()
{
return $this->getProcessDir().DIRECTORY_SEPARATOR.$this->getFilename();
}
/**
* Get the process directory
*
* @return string The process dir pathname
*/
public function getProcessDir()
{
return $this->processDir;
}
/**
* Get the size of the current process file
*
* @return int The size
*/
public function getSize()
{
return filesize($this->getPathname());
}
/**
* Get the source file pathname
*
* @return string The source file pathname
*/
public function getSourcePathname()
{
return $this->sourcePathname;
}
/**
* Get the unzip binary pathfile
*
* @return string The unzip binary
*/
public function getUnzipBinary()
{
return $this->unzipBin;
}
/**
* Get the current XML filename
*
* @return string The XML filename
*/
public function getXmlFilename()
{
return $this->xmlFilename;
}
/**
* Get the zip binary pathfile
*
* @return string The zip binary
*/
public function getZipBinary()
{
return $this->zipBin;
}
/**
* Get the method name to zip/unzip
*
* @return string The zip method name
*/
public function getZipMethod()
{
return $this->zipMethod;
}
/**
* Set the basename of the current process file
*
* @param string $basename The basename
*/
public function setBasename($basename)
{
return $this->processBasename = $basename;
}
/**
* Set the callback method to encode merged data
*
* @param string $callback The callback method (TBS syntax)
*/
public function setCallback($callback)
{
$this->callback = $callback;
}
/**
* Set the charset
*
* @param string $charset The charset of the data to merge (utf-8 by default)
*/
public function setCharset($charset = 'UTF-8')
{
$this->charset = strtoupper($charset);
}
/**
* Set if '<' & '>' in merged data are escaped by '<' & '>'
*
* BE CAREFUL TO SET TO FALSE
*
* @param boolean $isEscape Escape data (true by default)
*/
public function setIsEscape($isEscape = true)
{
$this->isEscape = $isEscape;
}
/**
* Set the source file pathname
*
* @param mixed $options The sourcePathname
*/
public function setSourcePathname($options = array())
{
$sourcePathname = $options;
// test if file exist
if (!file_exists($sourcePathname))
{
throw new tinyDocException(sprintf('File not found "%s"', $sourcePathname));
}
// test if file readable
if (!is_readable($sourcePathname))
{
throw new tinyDocException(sprintf('File not readable "%s"', $sourcePathname));
}
$this->sourcePathname = $sourcePathname;
}
/**
* Set the process directory
*
* @param string $processDir The pathname of the process dir
*/
public function setProcessDir($processDir = 'tmp')
{
// changed by kevin - makes no sense on shared hosting.
// for example open basedir settings restrict access to /
// if (realpath($processDir) == realpath(DIRECTORY_SEPARATOR))
if (realpath($processDir) == DIRECTORY_SEPARATOR)
{
throw new tinyDocException(sprintf('Could not use the root for the process directory "%s"', $processDir));
}
if (!is_dir(realpath($processDir)))
{
throw new tinyDocException(sprintf('Process directory not found "%s"', $processDir));
}
if (!is_writable(realpath($processDir)))
{
throw new tinyDocException(sprintf('Process directory not writable "%s"', $processDir));
}
$this->processDir = rtrim(realpath($processDir), DIRECTORY_SEPARATOR);
}
/**
* Fix the unzip binary
*
* @param string $unzipBin The pathname of the unzip binary (unzip by default)
*/
public function setUnzipBinary($unzipBinary = 'unzip')
{
if ($this->getZipMethod() == 'shell')
{
$unzipBinary = self::escapeShellCommand($unzipBinary);
if (strlen(shell_exec($unzipBinary.' -h')) == 0)
{
throw new tinyDocException(sprintf('"%s" not executable', $unzipBinary));
}
$this->unzipBin = $unzipBinary;
}
}
/**
* Set the XML filename of the current template
*
* @param string The pathname of XML filename (content.xml by default)
*/
public function setXmlFilename($xmlFilename = 'content.xml')
{
$this->xmlFilename = $xmlFilename;
}
/**
* Fix the zip binary
*
* @param string $zipBin The pathname of the zip binary (zip by default)
*/
public function setZipBinary($zipBinary = 'zip')
{
if ($this->getZipMethod() == 'shell')
{
$zipBinary = self::escapeShellCommand($zipBinary);
if (strlen(shell_exec($zipBinary.' -h')) == 0)
{
throw new tinyDocException(sprintf('"%s" not executable', $zipBinary));
}
$this->zipBin = $zipBinary;
}
}
/**
* Fix the method to zip/unzip file.
*
* @param string $zipMethod The method are 'shell' or 'ziparchive' (shell by default)
*/
public function setZipMethod($zipMethod = 'shell')
{
$method = strtolower($zipMethod);
if (!in_array($method, array('shell', 'ziparchive', 'pclzip')))
{
throw new tinyDocException(sprintf('Zip method "%s" need to be \'shell\' or \'ziparchive\'', $method));
}
if ($method == 'ziparchive' && !class_exists('ZipArchive'))
{
throw new tinyDocException('Zip extension not loaded - check your php settings, PHP 5.2 minimum with zip extension is required');
}
$this->zipMethod = $method;
}
/**
* Redefinition of TBS method to make an exception when an alert occured
*
* @param string $Src TBS subject ???
* @param string $Msg TBS message ???
* @param string $NoErrMsg TBS no error message ???
* @param string $SrcType The TBS source type ???
*/
public function meth_Misc_Alert($Src, $Msg, $NoErrMsg=false, $SrcType=false)
{
throw new tinyDocException(sprintf('%s', $Msg));
}
/**
* Redefinition of TBS method to add parameter 'cell' to set value with native OpenDocument format in spreadsheet
*
* refactoring code from egroupware/perp_api/inc/report/ooo.inc.php
*
* @param string $Txt TBS source
* @param string $Loc TBS locator
* @param string $Value TBS data to merge
* @param string $CheckSub TBS checksub ???
*/
public function meth_Locator_Replace(&$Txt, &$Loc, &$Value, $CheckSub)
{
if (!isset($Loc->PrmLst['type']) && !isset($Loc->PrmLst['image']) && !isset($Loc->PrmLst['link']))
{
return parent::meth_Locator_Replace($Txt, $Loc, $Value, $CheckSub);
}
// keep 'Loc' position for the end
$posBeg = $Loc->PosBeg;
$posEnd = $Loc->PosEnd;
// get data
$data = isset($Value) ? $Value : null;
foreach($Loc->SubLst as $sub)
{
$data = isset($Value[$sub]) ? $Value[$sub] : null ;
}
// ----- parameter = type
if (isset($Loc->PrmLst['type']))
{
if ($data == '')
{
$Txt = substr_replace($Txt, '', $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1 );
$Loc->PosBeg = $posBeg;
$Loc->PosEnd = $posEnd;
return $Loc->PosBeg;
}
// get container enlarged to table:table-cell
$Loc->Enlarged = $this->f_Loc_EnlargeToStr($Txt, $Loc, '');
$container = substr($Txt, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1);
if ($container == '')
{
throw new tinyDocException(sprintf('getXmlFilename()));
}
// OpenOffice attributes cell - see : http://books.evc-cit.info/odbook/ch05.html#table-cells-section
switch($Loc->PrmLst['type'])
{
case 'datetime':
case 'date':
case 'dt':
case 'd':
$attribute = sprintf('office:value-type="date" office:date-value="%s"', str_replace(' ', 'T', $data));
break;
case 'time':
case 't':
list($h, $m, $s) = split(":", $data);
$attribute = sprintf('office:value-type="time" office:time-value="PT%sH%sM%sS"', $h, $m, $s);
break;
case 'percentage':
case 'percent':
case 'p':
$attribute = sprintf('office:value-type="percentage" office:value="%s"', $data);
break;
case 'currency':
case 'cur':
case 'c':
//$attribute = sprintf('office:value-type="currency" office:currency="EUR" office:value="%s"', $data); // still not necessary to fix the currency
$attribute = sprintf('office:value-type="currency" office:value="%s"', $data);
break;
case 'number':
case 'n':
case 'float':
case 'f':
$attribute = sprintf('office:value-type="float" office:value="%s"', $data);
break;
case 'int':
case 'i':
$attribute = sprintf('office:value-type="float" office:value="%d"', $data);
break;
default:
case 'string':
case 's':
$attribute = sprintf('office:value-type="string"');
break;
}
// new container
$newContainer = preg_replace('/office:value-type="string"/', $attribute, $container);
// replace the new cell containter in the main Txt
$Txt = substr_replace($Txt, $newContainer, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1 );
// correct 'Loc' to include the change of the new cell container
$delta = strlen($newContainer) - strlen($container);
$Loc->PosBeg = $posBeg + $delta;
$Loc->PosEnd = $posEnd + $delta;
$Loc->Enlarged = null;
}
// ----- parameter = image
if (isset($Loc->PrmLst['image']))
{
// get container enlarged to draw:frame
$Loc->Enlarged = $this->f_Loc_EnlargeToStr($Txt, $Loc, '');
$container = substr($Txt, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1);
if ($container == '')
{
throw new tinyDocException(sprintf('getXmlFilename()));
}
// test if data is empty or if file not exists
if ($data == '' || !file_exists($data))
{
$Txt = substr_replace($Txt, '', $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1 );
$Loc->PosBeg = $posBeg;
$Loc->PosEnd = $posEnd;
$Loc->Enlarged = null;
return $Loc->PosBeg;
}
$picture = 'Pictures/'.basename($data);
// image size
$size = @getimagesize($data);
if ($size === false)
{
throw new tinyDocException(sprintf('Invalid image format "%"', $data));
}
else
{
list ($width, $height) = $size;
}
// image ratio
$ratio = 1;
switch(strtolower($Loc->PrmLst['image']))
{
case 'fit':
if (preg_match('/svg:width="(.*?)(cm|in|mm|px)" svg:height="(.*?)(cm|in|mm|px)"/', $container, $matches))
{
$ratio_w = self::convertToCm($matches[1], $matches[2]) / self::convertToCm($width, 'px');
$ratio_h = self::convertToCm($matches[3], $matches[4]) / self::convertToCm($height,'px');
$ratio = min($ratio_w, $ratio_h);
}
break;
case 'max':
if (preg_match('/svg:width="(.*?)(cm|in|mm|px)" svg:height="(.*?)(cm|in|mm|px)"/', $container, $matches))
{
$ratio_w = self::convertToCm($matches[1], $matches[2]) / self::convertToCm($width, 'px');
$ratio_h = self::convertToCm($matches[3], $matches[4]) / self::convertToCm($height,'px');
$ratio = min(1, $ratio_w, $ratio_h);
}
break;
default:
if (preg_match('/([0-9\.]*)%/', $Loc->PrmLst['image'], $matches) > 0)
{
$ratio = $matches[1] / 100;
}
break;
}
// replace values
$newContainer = $container;
$newContainer = preg_replace('/svg:width="(.*?)"/' , sprintf('svg:width="%scm"' , self::convertToCm($width, 'px') * $ratio), $newContainer);
$newContainer = preg_replace('/svg:height="(.*?)"/', sprintf('svg:height="%scm"', self::convertToCm($height,'px') * $ratio), $newContainer);
$newContainer = preg_replace('/xlink:href="(.*?)"/', sprintf('xlink:href="%s"' , $picture), $newContainer);
// add file
$this->addFile($data, $picture);
// replace the new cell containter in the main Txt
$Txt = substr_replace($Txt, $newContainer, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1);
$Loc->PosBeg = $posBeg;
$Loc->PosEnd = $posEnd;
$Loc->Enlarged = null;
}
// ----- parameter = link
if (isset($Loc->PrmLst['link']))
{
if ($data == '')
{
$Txt = substr_replace($Txt, '', $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1 );
$Loc->PosBeg = $posBeg;
$Loc->PosEnd = $posEnd;
return $Loc->PosBeg;
}
$container = substr($Txt, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1);
$title = ($Loc->PrmLst['link'] != '1' ? $Loc->PrmLst['link'] : $data);
$newContainer = sprintf('%s', $data, $title);
$Txt = substr_replace($Txt, $newContainer, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1);
// before return, restore 'Loc' with beginning values (to work with block)
$Loc->PosBeg = $posBeg;
$Loc->PosEnd = $posEnd;
return $Loc->PosEnd;
}
// call the parent method to insert the value
$ret = parent::meth_Locator_Replace($Txt, $Loc, $Value, $CheckSub);
// before return, restore 'Loc' with beginning values (to work with block)
$Loc->PosBeg = $posBeg;
$Loc->PosEnd = $posEnd;
return $ret;
}
public static function convertToCm($value, $unit)
{
switch(strtolower($unit))
{
case 'px':
$returnValue = $value * self::INCH_TO_CM / self::PICTURE_DPI;
break;
case 'in':
$returnValue = $value * self::INCH_TO_CM;
break;
case 'mm':
$returnValue = $value / 10;
break;
default;
case 'cm':
$returnValue = $value;
break;
}
return $returnValue;
}
public static function escapeShellCommand($command)
{
if (strpos($command, ' ') !== false)
{
$command = (strpos($command, '"') === 0 ? '' : '"').$command;
$command = $command.((strrpos($command, '"') == strlen($command)-1) ? '' : '"');
}
return $command;
}
}
/**
* tinyDocException exception class
*
* @package tinyDoc
* @subpackage tinyDocException
* @author Olivier Loynet
* @version $Id$
*/
class tinyDocException extends Exception
{
}