diff --git a/addressbook/inc/class.addressbook_merge.inc.php b/addressbook/inc/class.addressbook_merge.inc.php index 4fa9bc4671..df94c4a7c0 100644 --- a/addressbook/inc/class.addressbook_merge.inc.php +++ b/addressbook/inc/class.addressbook_merge.inc.php @@ -5,7 +5,7 @@ * @link http://www.egroupware.org * @author Ralf Becker * @package addressbook - * @copyright (c) 2007/8 by Ralf Becker + * @copyright (c) 2007-9 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @version $Id$ */ @@ -38,6 +38,40 @@ class addressbook_merge // extends bo_merge $this->contacts =& new addressbook_bo(); } + /** + * 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 function is_implemented($mimetype,$extension=null) + { + switch ($mimetype) + { + case 'application/msword': + if (strtolower($extension) != '.rtf') break; + case 'application/rtf': + return true; // rtf files + case 'application/vnd.oasis.opendocument.text': + if (!check_load_extension('zip')) break; + return true; // open office write xml files + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars + if (!check_load_extension('zip')) break; + return true; // ms word xml format + default: + if (substr($mimetype,0,5) == 'text/') + { + return true; // text files + } + break; + } + return false; + + // As browsers not always return correct mime types, one could use a negative list instead + //return !($mimetype == egw_vfs::DIR_MIME_TYPE || substr($mimetype,0,6) == 'image/'); + } + /** * Return replacements for a contact * @@ -220,14 +254,15 @@ class addressbook_merge // extends bo_merge /** * Merges a given document with contact data * - * @param string $document vfs-path of document + * @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 * @return string/boolean merged document or false on error */ - function merge($document,$ids,&$err) + function &merge($document,$ids,&$err,$mimetype) { - if (!($content = file_get_contents(egw_vfs::PREFIX.$document))) + if (!($content = file_get_contents($document))) { $err = lang("Document '%1' does not exist or is not readable for you!",$document); return false; @@ -235,9 +270,9 @@ class addressbook_merge // extends bo_merge list($contentstart,$contentrepeat,$contentend) = preg_split('/\$\$pagerepeat\$\$/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat list($Labelstart,$Labelrepeat,$Labeltend) = preg_split('/\$\$label\$\$/',$contentrepeat,-1, PREG_SPLIT_NO_EMPTY); //get the Lable content preg_match_all('/\$\$labelplacement\$\$/',$contentrepeat,$countlables, PREG_SPLIT_NO_EMPTY); - $countlables =count($countlables[0])+1; + $countlables = count($countlables[0])+1; preg_replace('/\$\$labelplacement\$\$/','',$Labelrepeat,1); - if ($countlables>1) $lableprint = true; + if ($countlables > 1) $lableprint = true; if (count($ids) > 1 && !$contentrepeat) { $err = lang('for more then one contact in a document use the tag pagerepeat!'); @@ -278,13 +313,9 @@ class addressbook_merge // extends bo_merge { //Example use to use: $$IF n_prefix~Herr~Sehr geehrter~Sehr geehrte$$ $content = preg_replace_callback('/\$\$IF ([0-9a-z_-]+)~(.*)~(.*)~(.*)\$\$/imU',Array($this,'replace_callback'),$content); } - if ($contentrepeat) $contentrep[$id] = $content; } - - if ($Labelrepeat) - { $countpage=0; $count=0; @@ -299,21 +330,55 @@ class addressbook_merge // extends bo_merge $contentrepeatpages[$countpage] = $Labelstart.$Labeltend; } $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/',$Label,$contentrepeatpages[$countpage],1); - } - $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/','',$contentrepeatpages[$countpage],-1); //clean empty fields - return $contentstart.implode('\\par \\page\\pard\\plain',$contentrepeatpages).$contentend; + + switch($mimetype) + { + case 'application/msword': + if (strtolower(substr($document,-4)) != '.rtf') break; // no binary word documents + case 'application/rtf': + return $contentstart.implode('\\par \\page\\pard\\plain',$contentrepeatpages).$contentend; + case 'application/vnd.oasis.opendocument.text': + // todo OO writer files + break; + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars + // todo ms word xml files + break; + } + $err = lang('%1 not implemented for %2!','$$labelplacement$$',$mimetype); + return false; } if ($contentrepeat) { - return $contentstart.implode('\\par \\page\\pard\\plain',$contentrep).$contentend; + switch($mimetype) + { + case 'application/msword': + if (strtolower(substr($document,-4)) != '.rtf') break; // no binary word documents + case 'application/rtf': + return $contentstart.implode('\\par \\page\\pard\\plain',$contentrep).$contentend; + case 'application/vnd.oasis.opendocument.text': + // todo OO writer files + break; + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars + // todo ms word xml files + break; + } + $err = lang('%1 not implemented for %2!','$$pagerepeat$$',$mimetype); + return false; } return $content; } - + /** + * Callback for preg_replace to process $$IF + * + * @param array $param + * @return string + */ function replace_callback($param) { if (array_key_exists('$$'.$param[4].'$$',$this->replacements)) $param[4] = $this->replacements['$$'.$param[4].'$$']; @@ -331,14 +396,45 @@ class addressbook_merge // extends bo_merge */ function download($document,$ids) { - if (!($merged = $this->merge($document,$ids,$err))) + $content_url = egw_vfs::PREFIX.$document; + switch (($mime_type = egw_vfs::mime_content_type($document))) + { + case 'application/vnd.oasis.opendocument.text': + $archive = tempnam($GLOBALS['egw_info']['server']['temp_dir'], basename($document,'.odt').'-').'.odt'; + copy($content_url,$archive); + $content_url = 'zip://'.$archive.'#'.($content_file = 'content.xml'); + break; + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars + $archive = tempnam($GLOBALS['egw_info']['server']['temp_dir'], basename($document,'.dotx').'-').'.dotx'; + copy($content_url,$archive); + $content_url = 'zip://'.$archive.'#'.($content_file = 'word/document.xml'); + break; + } + if (!($merged =& $this->merge($content_url,$ids,$err,$mime_type))) { return $err; } - $mime_type = egw_vfs::mime_content_type($document); - ExecMethod2('phpgwapi.browser.content_header',basename($document),$mime_type); - echo $merged; - + if (isset($archive)) + { + $zip = new ZipArchive; + if ($zip->open($archive,ZIPARCHIVE::CHECKCONS) !== true) throw new Exception("!ZipArchive::open('$archive',ZIPARCHIVE::OVERWRITE)"); + if ($zip->addFromString($content_file,$merged) !== true) throw new Exception("!ZipArchive::addFromString('$content_file',\$merged)"); + if ($zip->close() !== true) throw new Exception("!ZipArchive::close()"); + unset($zip); + unset($merged); + if (file_exists('/usr/bin/zip')) // fix broken zip archives generated by current php + { + exec('/usr/bin/zip -F '.escapeshellarg($archive)); + } + ExecMethod2('phpgwapi.browser.content_header',basename($document),$mime_type); + readfile($archive,'r'); + } + else + { + ExecMethod2('phpgwapi.browser.content_header',basename($document),$mime_type); + echo $merged; + } $GLOBALS['egw']->common->egw_exit(); } @@ -371,17 +467,26 @@ class addressbook_merge // extends bo_merge $n++; } + echo '

'.lang('Custom fields').":

"; + foreach($this->contacts->customfields as $name => $field) + { + echo '$$#'.$name.'$$'.$field['label']."\n"; + } + echo '

'.lang('General fields:')."

"; foreach(array( 'date' => lang('Date'), 'user/n_fn' => lang('Name of current user, all other contact fields are valid too'), 'user/account_lid' => lang('Username'), 'pagerepeat' => lang('For serial letter use this tag. Put the content, you want to repeat between two Tags.'), + 'label' => lang('Use this tag for addresslabels. Put the content, you want to repeat, between two tags.'), + 'labelplacement' => lang('Tag to mark positions for address labels'), 'IF fieldname' => lang('Example $$IF n_prefix~Mr~Hello Mr.~Hello Ms.$$ - search the field "n_prefix", for "Mr", if found, write Hello Mr., else write Hello Ms.'), ) as $name => $label) { echo '$$'.$name.'$$'.$label."\n"; } + $GLOBALS['egw']->translation->add_app('calendar'); echo '

'.lang('Calendar fields:')." # = 1, 2, ..., 20, -1

"; foreach(array( diff --git a/addressbook/inc/class.addressbook_ui.inc.php b/addressbook/inc/class.addressbook_ui.inc.php index bbb2824906..39fec68d10 100644 --- a/addressbook/inc/class.addressbook_ui.inc.php +++ b/addressbook/inc/class.addressbook_ui.inc.php @@ -2025,11 +2025,7 @@ $readonlys['button[vcard]'] = true; foreach($files as $file) { // return only the mime-types we support - if (!($file['mime'] == 'application/rtf' || - $file['mime'] == 'application/msword' && !strcasecmp(substr($file['name'],-4),'.rtf') || - substr($file['mime'],0,5) == 'text/')) continue; - // As browsers not always return the right mime_type, you could use a negative list instead - //if ($file['mime'] == egw_vfs::DIR_MIME_TYPE || substr($file['mime'],0,6) == 'image/') continue; + if (!addressbook_merge::is_implemented($file['mime'],substr($file['name'],-4))) continue; $actions['document-'.$file['name']] = /*lang('Insert in document').': '.*/$file['name']; } diff --git a/addressbook/setup/egw_de.lang b/addressbook/setup/egw_de.lang index b1f4d16d51..f3baa341e3 100644 --- a/addressbook/setup/egw_de.lang +++ b/addressbook/setup/egw_de.lang @@ -3,6 +3,7 @@ %1 contact(s) %2, %3 failed because of insufficent rights !!! addressbook de %1 Kontakt(e) %2, %3 nicht wegen fehlender Rechte !!! %1 contacts updated (%2 errors). addressbook de %1 Kontakte aktualisiert (%2 Fehler). %1 fields in %2 other organisation member(s) changed addressbook de %1 Felder in %2 Mitglied(ern) der Organisation geändert +%1 not implemented for %2! addressbook de %1 nicht implementiert für %2! %1 records imported addressbook de %1 Datensätze importiert %1 records read (not yet imported, you may go %2back%3 and uncheck test import) addressbook de %1 Datensätze gelesen (noch nicht importiert, sie können %2zurück%3 gehen und Test-Import auschalten) %1 starts with '%2' addressbook de %1 beginnt mit '%2' @@ -361,6 +362,7 @@ street common de Straße subject for email addressbook de Betreff der Email successfully imported %1 records into your addressbook. addressbook de %1 Kontakte wurden erfolgreich in Ihr Adressbuch importiert suffix addressbook de Zusatz +tag to mark positions for address labels addressbook de Tag um die Positionen von Adressetiktetten zu markieren tel home addressbook de Telefon privat telephony integration admin de Telefonie Integration test import (show importable records only in browser) addressbook de Test-Import (zeigt importierbare Datensätze nur im Browser an) @@ -390,6 +392,7 @@ use an extra category tab? addressbook de Separaten Reiter für Kategorien verwe use an extra tab for private custom fields? admin de Separaten Reiter für private benutzerdefinierte Felder verwenden? use country list addressbook de Länderliste benutzen use setup for a full account-migration admin de für eine komplette Benutzer Migration setup verwenden +use this tag for addresslabels. put the content, you want to repeat, between two tags. addressbook de Benutzen Sie diesen Tag für Adressetiketten. Plazieren Sie den Inhalt, der wiederholt werden soll, zwischen zwei Tags. used for links and for the own sorting of the list addressbook de wird für Verküpfungen und die eigene Sortierung der Liste benützt vcard common de VCard vcards require a first name entry. addressbook de VCards benötigen einen Vornamen. diff --git a/addressbook/setup/egw_en.lang b/addressbook/setup/egw_en.lang index 8e021bb0c7..68d487c7a3 100644 --- a/addressbook/setup/egw_en.lang +++ b/addressbook/setup/egw_en.lang @@ -3,6 +3,7 @@ %1 contact(s) %2, %3 failed because of insufficent rights !!! addressbook en %1 contact(s) %2, %3 failed because of insufficent rights !!! %1 contacts updated (%2 errors). addressbook en %1 contacts updated (%2 errors). %1 fields in %2 other organisation member(s) changed addressbook en %1 fields in %2 other organisation member(s) changed +%1 not implemented for %2! addressbook en %1 not implemented for %2! %1 records imported addressbook en %1 records imported %1 records read (not yet imported, you may go %2back%3 and uncheck test import) addressbook en %1 records read (not yet imported, you may go %2back%3 and uncheck Test Import) %1 starts with '%2' addressbook en %1 starts with '%2' @@ -361,6 +362,7 @@ street common en Street subject for email addressbook en Subject for email successfully imported %1 records into your addressbook. addressbook en Successfully imported %1 record(s) into your addressbook. suffix addressbook en Suffix +tag to mark positions for address labels addressbook en Tag to mark positions for address labels tel home addressbook en tel home telephony integration admin en Telephony integration test import (show importable records only in browser) addressbook en Test Import (show importable records only in browser) @@ -390,6 +392,7 @@ use an extra category tab? addressbook en Use an extra category tab? use an extra tab for private custom fields? admin en Use an extra tab for private custom fields? use country list addressbook en Use Country List use setup for a full account-migration admin en use setup for a full account-migration +use this tag for addresslabels. put the content, you want to repeat, between two tags. addressbook en Use this tag for addresslabels. Put the content, you want to repeat, between two tags. used for links and for the own sorting of the list addressbook en used for links and for the own sorting of the list vcard common en VCard vcards require a first name entry. addressbook en VCards require a first name entry.