diff --git a/addressbook/inc/class.addressbook_hooks.inc.php b/addressbook/inc/class.addressbook_hooks.inc.php index 9a514656a6..9a59821007 100644 --- a/addressbook/inc/class.addressbook_hooks.inc.php +++ b/addressbook/inc/class.addressbook_hooks.inc.php @@ -291,41 +291,8 @@ class addressbook_hooks if ($GLOBALS['egw_info']['user']['apps']['filemanager']) { - $settings['default_document'] = array( - 'type' => 'vfs_file', - 'size' => 60, - 'label' => 'Default document to insert contacts', - 'name' => 'default_document', - 'help' => lang('If you specify a document (full vfs path) here, %1 displays an extra document icon for each entry. That icon allows to download the specified document with the data inserted.', lang('addressbook')).' '. - lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','n_fn').' '. - lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()), - 'run_lang' => false, - 'xmlrpc' => True, - 'admin' => False, - ); - $settings['document_dir'] = array( - 'type' => 'vfs_dirs', - 'size' => 60, - 'label' => 'Directory with documents to insert contacts', - 'name' => 'document_dir', - 'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the data inserted.', lang('addressbook')) . ' ' . - lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'n_fn') . ' ' . - lang('The following document-types are supported:') . implode(',', Api\Storage\Merge::get_file_extensions()), - 'run_lang' => false, - 'xmlrpc' => True, - 'admin' => False, - 'default' => '/templates/addressbook', - ); - $settings[Api\Storage\Merge::PREF_DOCUMENT_FILENAME] = array( - 'type' => 'taglist', - 'label' => 'Document download filename', - 'name' => 'document_download_name', - 'values' => Api\Storage\Merge::DOCUMENT_FILENAME_OPTIONS, - 'help' => 'Choose the default filename for downloaded documents.', - 'xmlrpc' => True, - 'admin' => False, - 'default' => 'document', - ); + $merge = new Api\Contacts\Merge(); + $settings += $merge->merge_preferences(); } if ($GLOBALS['egw_info']['user']['apps']['felamimail'] || $GLOBALS['egw_info']['user']['apps']['mail']) diff --git a/api/src/Storage/Merge.php b/api/src/Storage/Merge.php index f27137a172..f86a651bb7 100644 --- a/api/src/Storage/Merge.php +++ b/api/src/Storage/Merge.php @@ -32,6 +32,16 @@ use ZipArchive; */ abstract class Merge { + /** + * Preference, path where we look for merge templates + */ + public const PREF_TEMPLATE_DIR = 'document_dir'; + + /** + * Preference, path to special documents that are listed first + */ + public const PREF_DEFAULT_TEMPLATE = 'default_document'; + /** * Preference, path where we will put the generated document */ @@ -104,21 +114,21 @@ abstract class Merge public $export_limit; public $public_functions = array( - "merge_entries" => true + "merge_entries" => true ); /** * Configuration for HTML Tidy to clean up any HTML content that is kept */ public static $tidy_config = array( - 'output-xml' => true, // Entity encoding - 'show-body-only' => true, - 'output-encoding' => 'utf-8', - 'input-encoding' => 'utf-8', - 'quote-ampersand' => false, // Prevent double encoding - 'quote-nbsp' => true, // XSLT can handle spaces easier - 'preserve-entities' => true, - 'wrap' => 0, // Wrapping can break output + 'output-xml' => true, // Entity encoding + 'show-body-only' => true, + 'output-encoding' => 'utf-8', + 'input-encoding' => 'utf-8', + 'quote-ampersand' => false, // Prevent double encoding + 'quote-nbsp' => true, // XSLT can handle spaces easier + 'preserve-entities' => true, + 'wrap' => 0, // Wrapping can break output ); /** @@ -156,8 +166,8 @@ abstract class Merge $this->contacts = new Api\Contacts(); - $this->datetime_format = $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'].' '. - ($GLOBALS['egw_info']['user']['preferences']['common']['timeformat']==12 ? 'h:i a' : 'H:i'); + $this->datetime_format = $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'] . ' ' . + ($GLOBALS['egw_info']['user']['preferences']['common']['timeformat'] == 12 ? 'h:i a' : 'H:i'); $this->export_limit = self::getExportLimit(); } @@ -171,8 +181,8 @@ abstract class Merge { $accountsel = new uiaccountsel(); - return ''. - $accountsel->selection('newsettings[export_limit_excepted]','export_limit_excepted',$config['export_limit_excepted'],'both',4); + return '' . + $accountsel->selection('newsettings[export_limit_excepted]', 'export_limit_excepted', $config['export_limit_excepted'], 'both', 4); } /** @@ -183,10 +193,10 @@ abstract class Merge * - 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 + * @param string &$content =null content to create some replacements only if they are use * @return array|boolean array with replacements or false if entry not found */ - abstract protected function get_replacements($id,&$content=null); + abstract protected function get_replacements($id, &$content = null); /** * Return if merge-print is implemented for given mime-type (and/or extension) @@ -194,47 +204,56 @@ abstract class Merge * @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 public function is_implemented($mimetype, $extension = null) { - static $zip_available=null; - if (is_null($zip_available)) + static $zip_available = null; + if(is_null($zip_available)) { $zip_available = check_load_extension('zip') && - class_exists('ZipArchive'); // some PHP has zip extension, but no ZipArchive (eg. RHEL5!) + class_exists('ZipArchive'); // some PHP has zip extension, but no ZipArchive (eg. RHEL5!) } - switch ($mimetype) + switch($mimetype) { case 'application/msword': - if (strtolower($extension) != '.rtf') break; + if(strtolower($extension) != '.rtf') + { + break; + } case 'application/rtf': case 'text/rtf': - return true; // rtf files - case 'application/vnd.oasis.opendocument.text': // oo text - case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet + return true; // rtf files + case 'application/vnd.oasis.opendocument.text': // oo text + case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet case 'application/vnd.oasis.opendocument.presentation': case 'application/vnd.oasis.opendocument.text-template': case 'application/vnd.oasis.opendocument.spreadsheet-template': case 'application/vnd.oasis.opendocument.presentation-template': - if (!$zip_available) break; - return true; // open office write xml files - case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms word 2007 xml format - case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars + if(!$zip_available) + { + break; + } + return true; // open office write xml files + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms word 2007 xml format + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars case 'application/vnd.ms-word.document.macroenabled.12': - case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': // ms excel 2007 xml format + case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': // ms excel 2007 xml format case 'application/vnd.openxmlformats-officedocument.spreadsheetml.shee': case 'application/vnd.ms-excel.sheet.macroenabled.12': - if (!$zip_available) break; - return true; // ms word xml format + if(!$zip_available) + { + break; + } + return true; // ms word xml format case 'application/xml': - return true; // alias for text/xml, eg. ms office 2003 word format + return true; // alias for text/xml, eg. ms office 2003 word format case 'message/rfc822': return true; // ToDo: check if you are theoretical able to send mail case 'application/x-yaml': - return true; // yaml file, plain text with marginal syntax support for multiline replacements + return true; // yaml file, plain text with marginal syntax support for multiline replacements default: - if (substr($mimetype,0,5) == 'text/') + if(substr($mimetype, 0, 5) == 'text/') { - return true; // text files + return true; // text files } break; } @@ -252,13 +271,16 @@ abstract class Merge * @param boolean $ignore_acl =false true: no acl check * @return array */ - public function contact_replacements($contact,$prefix='',$ignore_acl=false, &$content = '') + public function contact_replacements($contact, $prefix = '', $ignore_acl = false, &$content = '') { - if (!is_array($contact)) + if(!is_array($contact)) { $contact = $this->contacts->read($contact, $ignore_acl); } - if (!is_array($contact)) return array(); + if(!is_array($contact)) + { + return array(); + } $replacements = array(); foreach(array_keys($this->contacts->contact_fields) as $name) @@ -362,15 +384,15 @@ abstract class Merge } // Format date cfs per user Api\Preferences if($this->mimetype !== 'application/x-yaml' && $contact[$name] && - ($field['type'] == 'date' || $field['type'] == 'date-time')) + ($field['type'] == 'date' || $field['type'] == 'date-time')) { - $this->date_fields[] = '#'.$name; - $replacements['$$'.($prefix ? $prefix.'/':'').$name.'$$'] = Api\DateTime::to($contact[$name], $field['type'] == 'date' ? true : ''); + $this->date_fields[] = '#' . $name; + $replacements['$$' . ($prefix ? $prefix . '/' : '') . $name . '$$'] = Api\DateTime::to($contact[$name], $field['type'] == 'date' ? true : ''); } - $replacements['$$'.($prefix ? $prefix.'/':'').$name.'$$'] = + $replacements['$$' . ($prefix ? $prefix . '/' : '') . $name . '$$'] = // use raw data for yaml, no user-preference specific formatting $this->mimetype == 'application/x-yaml' || $field['type'] == 'htmlarea' ? (string)$contact[$name] : - Customfields::format($field, (string)$contact[$name]); + Customfields::format($field, (string)$contact[$name]); } if($content && strpos($content, '$$#') !== FALSE) @@ -380,22 +402,27 @@ abstract class Merge // Add in extra cat field $cats = array(); - foreach(is_array($contact['cat_id']) ? $contact['cat_id'] : explode(',',$contact['cat_id']) as $cat_id) + foreach(is_array($contact['cat_id']) ? $contact['cat_id'] : explode(',', $contact['cat_id']) as $cat_id) { - if(!$cat_id) continue; - if($GLOBALS['egw']->categories->id2name($cat_id,'main') != $cat_id) + if(!$cat_id) + { + continue; + } + if($GLOBALS['egw']->categories->id2name($cat_id, 'main') != $cat_id) { $path = explode(' / ', $GLOBALS['egw']->categories->id2name($cat_id, 'path')); unset($path[0]); // Drop main - $cats[$GLOBALS['egw']->categories->id2name($cat_id,'main')][] = implode(' / ', $path); - } elseif($cat_id) { + $cats[$GLOBALS['egw']->categories->id2name($cat_id, 'main')][] = implode(' / ', $path); + } + elseif($cat_id) + { $cats[$cat_id] = array(); } } - $replacements['$$'.($prefix ? $prefix.'/':'').'categories$$'] = ''; + $replacements['$$' . ($prefix ? $prefix . '/' : '') . 'categories$$'] = ''; foreach($cats as $main => $cat) { - $replacements['$$'.($prefix ? $prefix.'/':'').'categories$$'] .= $GLOBALS['egw']->categories->id2name($main,'name') + $replacements['$$' . ($prefix ? $prefix . '/' : '') . 'categories$$'] .= $GLOBALS['egw']->categories->id2name($main, 'name') . (count($cat) > 0 ? ': ' : '') . implode(', ', $cats[$main]) . "\n"; } return $replacements; @@ -411,11 +438,11 @@ abstract class Merge * @param only_app Restrict links to only given application * @param exclude Exclude links to these applications * @param style String One of: - * 'title' - plain text, just the title of the link - * 'link' - URL to the entry - * 'href' - HREF tag wrapped around the title + * 'title' - plain text, just the title of the link + * 'link' - URL to the entry + * 'href' - HREF tag wrapped around the title */ - protected function get_links($app, $id, $only_app='', $exclude = array(), $style = 'title') + protected function get_links($app, $id, $only_app = '', $exclude = array(), $style = 'title') { $links = Api\Link::get_links($app, $id, $only_app); $link_titles = array(); @@ -425,34 +452,40 @@ abstract class Merge if(!is_array($link_info) && $only_app && $only_app[0] !== '!') { $link_info = array( - 'app' => $only_app, - 'id' => $link_info + 'app' => $only_app, + 'id' => $link_info ); } - if($exclude && in_array($link_info['id'], $exclude)) continue; + if($exclude && in_array($link_info['id'], $exclude)) + { + continue; + } $title = Api\Link::title($link_info['app'], $link_info['id']); - + if($style == 'href' || $style == 'link') { $link = Api\Link::view($link_info['app'], $link_info['id'], $link_info); if($link_info['app'] != Api\Link::VFS_APPNAME) { // Set app to false so we always get an external link - $link = str_replace(',', '%2C', $GLOBALS['egw']->framework->link('/index.php',$link, false)); + $link = str_replace(',', '%2C', $GLOBALS['egw']->framework->link('/index.php', $link, false)); } else { $link = Api\Framework::link($link, array()); } // Prepend site - if ($link[0] == '/') $link = Api\Framework::getUrl($link); + if($link[0] == '/') + { + $link = Api\Framework::getUrl($link); + } $title = $style == 'href' ? Api\Html::a_href(Api\Html::htmlspecialchars($title), $link) : $link; } $link_titles[] = $title; } - return implode("\n",$link_titles); + return implode("\n", $link_titles); } /** @@ -469,7 +502,7 @@ abstract class Merge { $array = array(); $pattern = '@\$\$(links_attachments|links|attachments|link)\/?(title|href|link)?\/?([a-z]*)\$\$@'; - static $link_cache=null; + static $link_cache = null; $matches = null; if(preg_match_all($pattern, $content, $matches)) { @@ -495,38 +528,44 @@ abstract class Merge if($app != Api\Link::VFS_APPNAME) { // Set app to false so we always get an external link - $link = str_replace(',', '%2C', $GLOBALS['egw']->framework->link('/index.php',$link, false)); + $link = str_replace(',', '%2C', $GLOBALS['egw']->framework->link('/index.php', $link, false)); } else { $link = Api\Framework::link($link, array()); } // Prepend site - if ($link[0] == '/') $link = Api\Framework::getUrl($link); + if($link[0] == '/') + { + $link = Api\Framework::getUrl($link); + } // Formatting if($matches[2][$i] == 'title') { $link = $title; } - else if($matches[2][$i] == 'href') + else { - // Turn on HTML style parsing or the link will be escaped - $this->parse_html_styles = true; - $link = Api\Html::a_href(Api\Html::htmlspecialchars($title), $link); + if($matches[2][$i] == 'href') + { + // Turn on HTML style parsing or the link will be escaped + $this->parse_html_styles = true; + $link = Api\Html::a_href(Api\Html::htmlspecialchars($title), $link); + } } - $array['$$'.($prefix?$prefix.'/':'').$placeholder.'$$'] = $link; + $array['$$' . ($prefix ? $prefix . '/' : '') . $placeholder . '$$'] = $link; break; case 'links': - $link_app = $matches[3][$i] ? $matches[3][$i] : '!'.Api\Link::VFS_APPNAME; - $array['$$'.($prefix?$prefix.'/':'').$placeholder.'$$'] = $this->get_links($app, $id, $link_app, array(),$matches[2][$i]); + $link_app = $matches[3][$i] ? $matches[3][$i] : '!' . Api\Link::VFS_APPNAME; + $array['$$' . ($prefix ? $prefix . '/' : '') . $placeholder . '$$'] = $this->get_links($app, $id, $link_app, array(), $matches[2][$i]); break; case 'attachments': - $array['$$'.($prefix?$prefix.'/':'').$placeholder.'$$'] = $this->get_links($app, $id, Api\Link::VFS_APPNAME,array(),$matches[2][$i]); + $array['$$' . ($prefix ? $prefix . '/' : '') . $placeholder . '$$'] = $this->get_links($app, $id, Api\Link::VFS_APPNAME, array(), $matches[2][$i]); break; default: - $array['$$'.($prefix?$prefix.'/':'').$placeholder.'$$'] = $this->get_links($app, $id, $matches[3][$i], array(), $matches[2][$i]); + $array['$$' . ($prefix ? $prefix . '/' : '') . $placeholder . '$$'] = $this->get_links($app, $id, $matches[3][$i], array(), $matches[2][$i]); break; } $link_cache[$id][$placeholder] = $array[$placeholder]; @@ -553,7 +592,7 @@ abstract class Merge if(!$GLOBALS['egw_info']['user']['apps']['stylite']) { - $replacements['$$'.$prefix.'share$$'] = lang('EPL Only'); + $replacements['$$' . $prefix . 'share$$'] = lang('EPL Only'); return $replacements; } @@ -562,7 +601,7 @@ abstract class Merge if($share) { - $replacements['$$'.$prefix.'share$$'] = $link = Api\Sharing::share2link($share); + $replacements['$$' . $prefix . 'share$$'] = $link = Api\Sharing::share2link($share); } return $replacements; @@ -589,7 +628,8 @@ abstract class Merge // Need to create the share here. // No way to know here if it should be writable, or who it's going to - $mode = /* ? ? Sharing::WRITABLE :*/ Api\Sharing::READONLY; + $mode = /* ? ? Sharing::WRITABLE :*/ + Api\Sharing::READONLY; $recipients = array(); $extra = array(); @@ -603,15 +643,18 @@ abstract class Merge * * @param int|string|DateTime $time unix timestamp or Y-m-d H:i:s string (in user time!) * @param string $format =null format string, default $this->datetime_format - * @deprecated use Api\DateTime::to($time='now',$format='') * @return string + * @deprecated use Api\DateTime::to($time='now',$format='') */ - protected function format_datetime($time,$format=null) + protected function format_datetime($time, $format = null) { trigger_error(__METHOD__ . ' is deprecated, use Api\DateTime::to($time, $format)', E_USER_DEPRECATED); - if (is_null($format)) $format = $this->datetime_format; + if(is_null($format)) + { + $format = $this->datetime_format; + } - return Api\DateTime::to($time,$format); + return Api\DateTime::to($time, $format); } /** @@ -623,19 +666,19 @@ abstract class Merge */ public static function is_export_limit_excepted() { - static $is_excepted=null; + static $is_excepted = null; - if (is_null($is_excepted)) + if(is_null($is_excepted)) { $is_excepted = isset($GLOBALS['egw_info']['user']['apps']['admin']); // check export-limit and fail if user tries to export more entries then allowed - if (!$is_excepted && (is_array($export_limit_excepted = $GLOBALS['egw_info']['server']['export_limit_excepted']) || - is_array($export_limit_excepted = unserialize($export_limit_excepted)))) + if(!$is_excepted && (is_array($export_limit_excepted = $GLOBALS['egw_info']['server']['export_limit_excepted']) || + is_array($export_limit_excepted = unserialize($export_limit_excepted)))) { - $id_and_memberships = $GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'],true); + $id_and_memberships = $GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'], true); $id_and_memberships[] = $GLOBALS['egw_info']['user']['account_id']; - $is_excepted = (bool) array_intersect($id_and_memberships, $export_limit_excepted); + $is_excepted = (bool)array_intersect($id_and_memberships, $export_limit_excepted); } } return $is_excepted; @@ -646,30 +689,36 @@ abstract class Merge * * @param string $app ='common' checks and validates app_limit, if not set returns the global limit * @return mixed - no if no export is allowed, false if there is no restriction and int as there is a valid restriction - * you may have to cast the returned value to int, if you want to use it as number + * you may have to cast the returned value to int, if you want to use it as number */ - public static function getExportLimit($app='common') + public static function getExportLimit($app = 'common') { - static $exportLimitStore=array(); - if (empty($app)) $app='common'; + static $exportLimitStore = array(); + if(empty($app)) + { + $app = 'common'; + } //error_log(__METHOD__.__LINE__.' called with app:'.$app); - if (!array_key_exists($app,$exportLimitStore)) + if(!array_key_exists($app, $exportLimitStore)) { //error_log(__METHOD__.__LINE__.' -> '.$app_limit.' '.function_backtrace()); $exportLimitStore[$app] = $GLOBALS['egw_info']['server']['export_limit']; - if ($app !='common') + if($app != 'common') { - $app_limit = Api\Hooks::single('export_limit',$app); - if ($app_limit) $exportLimitStore[$app] = $app_limit; + $app_limit = Api\Hooks::single('export_limit', $app); + if($app_limit) + { + $exportLimitStore[$app] = $app_limit; + } } //error_log(__METHOD__.__LINE__.' building cache for app:'.$app.' -> '.$exportLimitStore[$app]); - if (empty($exportLimitStore[$app])) + if(empty($exportLimitStore[$app])) { $exportLimitStore[$app] = false; return false; } - if (is_numeric($exportLimitStore[$app])) + if(is_numeric($exportLimitStore[$app])) { $exportLimitStore[$app] = (int)$exportLimitStore[$app]; } @@ -691,12 +740,24 @@ abstract class Merge * * @return bool - true if no export is allowed or a limit is set, false if there is no restriction */ - public static function hasExportLimit($app_limit,$checkas='AND') + public static function hasExportLimit($app_limit, $checkas = 'AND') { - if (strtoupper($checkas) == 'ISALLOWED') return (empty($app_limit) || ($app_limit !='no' && $app_limit > 0) ); - if (empty($app_limit)) return false; - if ($app_limit == 'no') return true; - if ($app_limit > 0) return true; + if(strtoupper($checkas) == 'ISALLOWED') + { + return (empty($app_limit) || ($app_limit != 'no' && $app_limit > 0)); + } + if(empty($app_limit)) + { + return false; + } + if($app_limit == 'no') + { + return true; + } + if($app_limit > 0) + { + return true; + } } /** @@ -709,31 +770,34 @@ abstract class Merge * @param array $fix =null regular expression => replacement pairs eg. to fix garbled placeholders * @return string|boolean merged document or false on error */ - public function &merge($document,$ids,&$err,$mimetype,array $fix=null) + public function &merge($document, $ids, &$err, $mimetype, array $fix = null) { - if (!($content = file_get_contents($document))) + if(!($content = file_get_contents($document))) { - $err = lang("Document '%1' does not exist or is not readable for you!",$document); + $err = lang("Document '%1' does not exist or is not readable for you!", $document); $ret = false; return $ret; } - if (self::hasExportLimit($this->export_limit) && !self::is_export_limit_excepted() && count($ids) > (int)$this->export_limit) + if(self::hasExportLimit($this->export_limit) && !self::is_export_limit_excepted() && count($ids) > (int)$this->export_limit) { - $err = lang('No rights to export more than %1 entries!',(int)$this->export_limit); + $err = lang('No rights to export more than %1 entries!', (int)$this->export_limit); $ret = false; return $ret; } // fix application/msword mimetype for rtf files - if ($mimetype == 'application/msword' && strtolower(substr($document,-4)) == '.rtf') + if($mimetype == 'application/msword' && strtolower(substr($document, -4)) == '.rtf') { $mimetype = 'application/rtf'; } - try { - $content = $this->merge_string($content,$ids,$err,$mimetype,$fix); - } catch (\Exception $e) { + try + { + $content = $this->merge_string($content, $ids, $err, $mimetype, $fix); + } + catch (\Exception $e) + { _egw_log_exception($e); $err = $e->getMessage(); $ret = false; @@ -742,51 +806,51 @@ abstract class Merge return $content; } - protected function apply_styles (&$content, $mimetype, $mso_application_progid=null) + protected function apply_styles(&$content, $mimetype, $mso_application_progid = null) { - if (!isset($mso_application_progid)) + if(!isset($mso_application_progid)) { $matches = null; $mso_application_progid = $mimetype == 'application/xml' && - preg_match('/'.preg_quote('', '/').'/',substr($content,0,200),$matches) ? - $matches[1] : ''; + preg_match('/' . preg_quote('', '/') . '/', substr($content, 0, 200), $matches) ? + $matches[1] : ''; } // Tags we can replace with the target document's version $replace_tags = array(); - switch($mimetype.$mso_application_progid) + switch($mimetype . $mso_application_progid) { - case 'application/vnd.oasis.opendocument.text': // oo text - case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet + case 'application/vnd.oasis.opendocument.text': // oo text + case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet case 'application/vnd.oasis.opendocument.presentation': case 'application/vnd.oasis.opendocument.text-template': case 'application/vnd.oasis.opendocument.spreadsheet-template': case 'application/vnd.oasis.opendocument.presentation-template': $doc = new DOMDocument(); $xslt = new XSLTProcessor(); - $doc->load(EGW_INCLUDE_ROOT.'/api/templates/default/Merge/openoffice.xslt'); + $doc->load(EGW_INCLUDE_ROOT . '/api/templates/default/Merge/openoffice.xslt'); $xslt->importStyleSheet($doc); //echo $content;die(); break; - case 'application/xmlWord.Document': // Word 2003*/ - case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms office 2007 + case 'application/xmlWord.Document': // Word 2003*/ + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms office 2007 case 'application/vnd.ms-word.document.macroenabled.12': case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': case 'application/vnd.ms-excel.sheet.macroenabled.12': // It seems easier to split the parent tags here $replace_tags = array( // Tables, lists don't go inside - '/<(ol|ul|table)( [^>]*)?>/' => '<$1$2>', - '/<\/(ol|ul|table)>/' => '', + '/<(ol|ul|table)( [^>]*)?>/' => '<$1$2>', + '/<\/(ol|ul|table)>/' => '', // Fix for things other than text (newlines) inside table row '/<(td)( [^>]*)?>((?!))(.*?)<\/td>[\s]*?/' => '<$1$2>$4', // Remove extra whitespace - '/]*?)>[^:print:]*?(.*?)<\/li>/' => '$2', // This doesn't get it all - '/[\s]+(.*?)<\/w:t>/' => '$1', + '/]*?)>[^:print:]*?(.*?)<\/li>/' => '$2', // This doesn't get it all + '/[\s]+(.*?)<\/w:t>/' => '$1', // Remove spans with no attributes, linebreaks inside them cause problems - '/(.*?)<\/span>/' => '$1' + '/(.*?)<\/span>/' => '$1' ); - $content = preg_replace(array_keys($replace_tags),array_values($replace_tags),$content); + $content = preg_replace(array_keys($replace_tags), array_values($replace_tags), $content); /* In the case where you have something like (invalid - mismatched tags), @@ -796,14 +860,15 @@ abstract class Merge $count = $i = 0; do { - $content = preg_replace('/(.*?)<\/span>/','$1',$content, -1, $count); + $content = preg_replace('/(.*?)<\/span>/', '$1', $content, -1, $count); $i++; - } while($count > 0 && $i < 10); + } + while($count > 0 && $i < 10); $doc = new DOMDocument(); $xslt = new XSLTProcessor(); $xslt_file = $mimetype == 'application/xml' ? 'wordml.xslt' : 'msoffice.xslt'; - $doc->load(EGW_INCLUDE_ROOT.'/api/templates/default/Merge/'.$xslt_file); + $doc->load(EGW_INCLUDE_ROOT . '/api/templates/default/Merge/' . $xslt_file); $xslt->importStyleSheet($doc); break; } @@ -822,8 +887,9 @@ abstract class Merge $content = $xslt->transformToXml($element); //echo $content;die(); // Word 2003 needs two declarations, add extra declaration back in - if($mimetype == 'application/xml' && $mso_application_progid == 'Word.Document' && strpos($content, ''.$content; + if($mimetype == 'application/xml' && $mso_application_progid == 'Word.Document' && strpos($content, '' . $content; } // Validate /* @@ -845,12 +911,12 @@ abstract class Merge * @param string $charset =null charset to override default set by mimetype or export charset * @return string|boolean merged document or false on error */ - public function &merge_string($_content,$ids,&$err,$mimetype,array $fix=null,$charset=null) + public function &merge_string($_content, $ids, &$err, $mimetype, array $fix = null, $charset = null) { $ids = empty($ids) ? [] : (array)$ids; $matches = null; - if ($mimetype == 'application/xml' && - preg_match('/'.preg_quote('', '/').'/',substr($_content,0,200),$matches)) + if($mimetype == 'application/xml' && + preg_match('/' . preg_quote('', '/') . '/', substr($_content, 0, 200), $matches)) { $mso_application_progid = $matches[1]; } @@ -860,33 +926,38 @@ abstract class Merge } // 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', function($matches) + $content = preg_replace_callback('/{{[^}]+}}/i', function ($matches) { - return '$$'.strip_tags(substr($matches[0], 2, -2)).'$$'; - }, $_content); + return '$$' . strip_tags(substr($matches[0], 2, -2)) . '$$'; + }, $_content); // Handle escaped placeholder markers in RTF, they won't match when escaped if($mimetype == 'application/rtf') { - $content = preg_replace('/\\\{\\\{([^\\}]+)\\\}\\\}/i','$$\1$$',$content); + $content = preg_replace('/\\\{\\\{([^\\}]+)\\\}\\\}/i', '$$\1$$', $content); } // make currently processed mimetype available to class methods; $this->mimetype = $mimetype; // fix garbled placeholders - if ($fix && is_array($fix)) + if($fix && is_array($fix)) { - $content = preg_replace(array_keys($fix),array_values($fix),$content); + $content = preg_replace(array_keys($fix), array_values($fix), $content); //die("
".htmlspecialchars($content)."
\n"); } - list($contentstart,$contentrepeat,$contentend) = preg_split('/\$\$pagerepeat\$\$/',$content,-1, PREG_SPLIT_NO_EMPTY)+[null,null,null]; //get differt parts of document, seperatet by Pagerepeat - if ($mimetype == 'text/plain' && $ids && count($ids) > 1) + list($contentstart, $contentrepeat, $contentend) = preg_split('/\$\$pagerepeat\$\$/', $content, -1, PREG_SPLIT_NO_EMPTY) + [null, + null, + null]; //get differt parts of document, seperatet by Pagerepeat + if($mimetype == 'text/plain' && $ids && 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 $nohead = false; - if (stripos($contentstart,'$$') !== false) $nohead = true; - if ($nohead) + if(stripos($contentstart, '$$') !== false) + { + $nohead = true; + } + if($nohead) { $contentend = $contentrepeat; $contentrepeat = $contentstart; @@ -894,51 +965,58 @@ abstract class Merge } } - if (in_array($mimetype, array('application/vnd.oasis.opendocument.text','application/vnd.oasis.opendocument.text-template')) && count($ids) > 1) + if(in_array($mimetype, array('application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.text-template')) && count($ids) > 1) { if(strpos($content, '$$pagerepeat') === false) { //for odt files we have to split the content and add a style for page break to the style area - list($contentstart,$contentrepeat,$contentend) = preg_split('/office:body>/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat - $contentstart = substr($contentstart,0,strlen($contentstart)-1); //remove "<" - $contentrepeat = substr($contentrepeat,0,strlen($contentrepeat)-2); //remove "/', $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 "/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document style sheets - $contentstart = $stylestart.''; + list($stylestart, $stylerepeat, $styleend) = preg_split('/<\/office:automatic-styles>/', $content, -1, PREG_SPLIT_NO_EMPTY); //get differt parts of document style sheets + $contentstart = $stylestart . ''; $contentstart .= ''; $contentend = ''; } else { // Template specifies where to repeat - list($contentstart,$contentrepeat,$contentend) = preg_split('/\$\$pagerepeat\$\$/',$content,-1, PREG_SPLIT_NO_EMPTY); //get different parts of document, seperated by pagerepeat + list($contentstart, $contentrepeat, $contentend) = preg_split('/\$\$pagerepeat\$\$/', $content, -1, PREG_SPLIT_NO_EMPTY); //get different parts of document, seperated by pagerepeat } } - if (in_array($mimetype, array('application/vnd.ms-word.document.macroenabled.12', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')) && count($ids) > 1) + if(in_array($mimetype, array('application/vnd.ms-word.document.macroenabled.12', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')) && count($ids) > 1) { //for Word 2007 XML files we have to split the content and add a style for page break to the style area - 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 "/', $content, -1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat + $contentstart = substr($contentstart, 0, strlen($contentstart) - 1); //remove "'; $contentend = ''; } - list($Labelstart,$Labelrepeat,$Labeltend) = preg_split('/\$\$label\$\$/',$contentrepeat,-1, PREG_SPLIT_NO_EMPTY)+[null,null,null]; //get the label content - preg_match_all('/\$\$labelplacement\$\$/',$contentrepeat,$countlables, PREG_SPLIT_NO_EMPTY); + list($Labelstart, $Labelrepeat, $Labeltend) = preg_split('/\$\$label\$\$/', $contentrepeat, -1, PREG_SPLIT_NO_EMPTY) + [null, + null, + null]; //get the label content + preg_match_all('/\$\$labelplacement\$\$/', $contentrepeat, $countlables, PREG_SPLIT_NO_EMPTY); $countlables = count($countlables[0]); - preg_replace('/\$\$labelplacement\$\$/','',$Labelrepeat,1); + preg_replace('/\$\$labelplacement\$\$/', '', $Labelrepeat, 1); $lableprint = $countlables > 1; - if (count($ids) > 1 && !$contentrepeat) + if(count($ids) > 1 && !$contentrepeat) { $err = lang('for more than one contact in a document use the tag pagerepeat!'); $ret = false; return $ret; } - if ($this->report_memory_usage) error_log(__METHOD__."(count(ids)=".count($ids).") strlen(contentrepeat)=".strlen($contentrepeat).', strlen(labelrepeat)='.strlen($Labelrepeat)); - - if ($contentrepeat) + if($this->report_memory_usage) { - $content_stream = fopen('php://temp','r+'); + error_log(__METHOD__ . "(count(ids)=" . count($ids) . ") strlen(contentrepeat)=" . strlen($contentrepeat) . ', strlen(labelrepeat)=' . strlen($Labelrepeat)); + } + + if($contentrepeat) + { + $content_stream = fopen('php://temp', 'r+'); fwrite($content_stream, $contentstart); $joiner = ''; switch($mimetype) @@ -947,8 +1025,8 @@ abstract class Merge case 'text/rtf': $joiner = '\\par \\page\\pard\\plain'; break; - case 'application/vnd.oasis.opendocument.text': // oo text - case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet + case 'application/vnd.oasis.opendocument.text': // oo text + case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet case 'application/vnd.oasis.opendocument.presentation': case 'application/vnd.oasis.opendocument.text-template': case 'application/vnd.oasis.opendocument.spreadsheet-template': @@ -968,20 +1046,26 @@ abstract class Merge $joiner = "\r\n"; break; default: - $err = lang('%1 not implemented for %2!','$$pagerepeat$$',$mimetype); + $err = lang('%1 not implemented for %2!', '$$pagerepeat$$', $mimetype); $ret = false; return $ret; } } - foreach ((array)$ids as $n => $id) + foreach((array)$ids as $n => $id) { - if ($contentrepeat) $content = $contentrepeat; //content to repeat - if ($lableprint) $content = $Labelrepeat; + if($contentrepeat) + { + $content = $contentrepeat; + } //content to repeat + if($lableprint) + { + $content = $Labelrepeat; + } // generate replacements; if exception is thrown, catch it set error message and return false try { - if(!($replacements = $this->get_replacements($id,$content))) + if(!($replacements = $this->get_replacements($id, $content))) { $err = lang('Entry not found!'); $ret = false; @@ -995,114 +1079,120 @@ abstract class Merge $ret = false; return $ret; } - if ($this->report_memory_usage) error_log(__METHOD__."() $n: $id ".Api\Vfs::hsize(memory_get_usage(true))); + if($this->report_memory_usage) + { + error_log(__METHOD__ . "() $n: $id " . Api\Vfs::hsize(memory_get_usage(true))); + } // some general replacements: current user, date and time if(strpos($content, '$$user/') !== false && ($user = $GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'], 'person_id'))) { $replacements += $this->contact_replacements($user, 'user', false, $content); $replacements['$$user/primary_group$$'] = $GLOBALS['egw']->accounts->id2name($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'], 'account_primary_group')); } - $replacements['$$date$$'] = Api\DateTime::to('now',true); + $replacements['$$date$$'] = Api\DateTime::to('now', true); $replacements['$$datetime$$'] = Api\DateTime::to('now'); - $replacements['$$time$$'] = Api\DateTime::to('now',false); + $replacements['$$time$$'] = Api\DateTime::to('now', false); $app = $this->get_app(); $replacements += $this->share_placeholder($app, $id, '', $content); // 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)) + if($this->table_plugins && preg_match_all('/\\$\\$table\\/([A-Za-z0-9_]+)\\$\\$(.*?)\\$\\$endtable\\$\\$/s', $content, $matches, PREG_SET_ORDER)) { // process each table foreach($matches as $match) { - $plugin = $match[1]; // plugin name + $plugin = $match[1]; // plugin name $callback = $this->table_plugins[$plugin]; - $repeat = $match[2]; // line to repeat + $repeat = $match[2]; // line to repeat $repeats = ''; - if (isset($callback)) + if(isset($callback)) { - for($n = 0; ($row_replacements = $this->$callback($plugin,$id,$n,$repeat)); ++$n) + for($n = 0; ($row_replacements = $this->$callback($plugin, $id, $n, $repeat)); ++$n) { $_repeat = $this->process_commands($repeat, $row_replacements); - $repeats .= $this->replace($_repeat,$row_replacements,$mimetype,$mso_application_progid); + $repeats .= $this->replace($_repeat, $row_replacements, $mimetype, $mso_application_progid); } } - $content = str_replace($match[0],$repeats,$content); + $content = str_replace($match[0], $repeats, $content); } } - $content = $this->process_commands($this->replace($content,$replacements,$mimetype,$mso_application_progid,$charset), $replacements); + $content = $this->process_commands($this->replace($content, $replacements, $mimetype, $mso_application_progid, $charset), $replacements); // remove not existing replacements (eg. from calendar array) - if (strpos($content,'$$') !== null) + if(strpos($content, '$$') !== null) { - $content = preg_replace('/\$\$[a-z0-9_\/]+\$\$/i','',$content); + $content = preg_replace('/\$\$[a-z0-9_\/]+\$\$/i', '', $content); } - if ($contentrepeat) + if($contentrepeat) { fwrite($content_stream, ($n == 0 ? '' : $joiner) . $content); } if($lableprint) { - $contentrep[is_array($id) ? implode(':',$id) : $id] = $content; + $contentrep[is_array($id) ? implode(':', $id) : $id] = $content; } } - if ($Labelrepeat) + if($Labelrepeat) { - $countpage=0; - $count=0; - $contentrepeatpages[$countpage] = $Labelstart.$Labeltend; + $countpage = 0; + $count = 0; + $contentrepeatpages[$countpage] = $Labelstart . $Labeltend; - foreach ($contentrep as $Label) + foreach($contentrep as $Label) { - $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/',$Label,$contentrepeatpages[$countpage],1); - $count=$count+1; - if (($count % $countlables) == 0 && count($contentrep)>$count) //new page + $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/', $Label, $contentrepeatpages[$countpage], 1); + $count = $count + 1; + if(($count % $countlables) == 0 && count($contentrep) > $count) //new page { - $countpage = $countpage+1; - $contentrepeatpages[$countpage] = $Labelstart.$Labeltend; + $countpage = $countpage + 1; + $contentrepeatpages[$countpage] = $Labelstart . $Labeltend; } } - $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/','',$contentrepeatpages[$countpage],-1); //clean empty fields + $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/', '', $contentrepeatpages[$countpage], -1); //clean empty fields switch($mimetype) { case 'application/rtf': case 'text/rtf': - $ret = $contentstart.implode('\\par \\page\\pard\\plain',$contentrepeatpages).$contentend; + $ret = $contentstart . implode('\\par \\page\\pard\\plain', $contentrepeatpages) . $contentend; break; case 'application/vnd.oasis.opendocument.text': case 'application/vnd.oasis.opendocument.presentation': case 'application/vnd.oasis.opendocument.text-template': case 'application/vnd.oasis.opendocument.presentation-template': - $ret = $contentstart.implode('',$contentrepeatpages).$contentend; + $ret = $contentstart . implode('', $contentrepeatpages) . $contentend; break; case 'application/vnd.oasis.opendocument.spreadsheet': case 'application/vnd.oasis.opendocument.spreadsheet-template': - $ret = $contentstart.implode('',$contentrepeatpages).$contentend; + $ret = $contentstart . implode('', $contentrepeatpages) . $contentend; break; case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': case 'application/vnd.ms-word.document.macroenabled.12': case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': case 'application/vnd.ms-excel.sheet.macroenabled.12': - $ret = $contentstart.implode('',$contentrepeatpages).$contentend; + $ret = $contentstart . implode('', $contentrepeatpages) . $contentend; break; case 'text/plain': - $ret = $contentstart.implode("\r\n",$contentrep).$contentend; + $ret = $contentstart . implode("\r\n", $contentrep) . $contentend; break; default: - $err = lang('%1 not implemented for %2!','$$labelplacement$$',$mimetype); + $err = lang('%1 not implemented for %2!', '$$labelplacement$$', $mimetype); $ret = false; } return $ret; } - if ($contentrepeat) + if($contentrepeat) { fwrite($content_stream, $contentend); rewind($content_stream); $content = stream_get_contents($content_stream); } - if ($this->report_memory_usage) error_log(__METHOD__."() returning ".Api\Vfs::hsize(memory_get_peak_usage(true))); + if($this->report_memory_usage) + { + error_log(__METHOD__ . "() returning " . Api\Vfs::hsize(memory_get_peak_usage(true))); + } return $content; } @@ -1117,54 +1207,57 @@ abstract class Merge * @param string $charset =null charset to override default set by mimetype or export charset * @return string */ - protected function replace($content,array $replacements,$mimetype,$mso_application_progid='',$charset=null) + protected function replace($content, array $replacements, $mimetype, $mso_application_progid = '', $charset = null) { switch($mimetype) { - case 'application/vnd.oasis.opendocument.text': // open office + case 'application/vnd.oasis.opendocument.text': // open office case 'application/vnd.oasis.opendocument.spreadsheet': case 'application/vnd.oasis.opendocument.presentation': case 'application/vnd.oasis.opendocument.text-template': case 'application/vnd.oasis.opendocument.spreadsheet-template': case 'application/vnd.oasis.opendocument.presentation-template': - case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms office 2007 + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms office 2007 case 'application/vnd.ms-word.document.macroenabled.12': case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': case 'application/vnd.ms-excel.sheet.macroenabled.12': case 'application/xml': case 'text/xml': $is_xml = true; - $charset = 'utf-8'; // xml files --> always use utf-8 + $charset = 'utf-8'; // xml files --> always use utf-8 break; case 'application/rtf': case 'text/rtf': - $charset = 'iso-8859-1'; // rtf seems to user iso-8859-1 or equivalent windows charset, not utf-8 + $charset = 'iso-8859-1'; // rtf seems to user iso-8859-1 or equivalent windows charset, not utf-8 break; case 'text/html': $is_xml = true; $matches = null; - if (preg_match('/ use our export-charset, defined in addressbook prefs - if (empty($charset)) $charset = $this->contacts->prefs['csv_charset']; + default: // div. text files --> use our export-charset, defined in addressbook prefs + if(empty($charset)) + { + $charset = $this->contacts->prefs['csv_charset']; + } break; } //error_log(__METHOD__."('$document', ... ,$mimetype) --> $charset (egw=".Api\Translation::charset().', export='.$this->contacts->prefs['csv_charset'].')'); // do we need to convert charset - if ($charset && $charset != Api\Translation::charset()) + if($charset && $charset != Api\Translation::charset()) { - $replacements = Api\Translation::convert($replacements,Api\Translation::charset(),$charset); + $replacements = Api\Translation::convert($replacements, Api\Translation::charset(), $charset); } // Date only placeholders for timestamps @@ -1172,14 +1265,14 @@ abstract class Merge { foreach($this->date_fields as $field) { - if(($value = $replacements['$$'.$field.'$$'] ?? null)) + if(($value = $replacements['$$' . $field . '$$'] ?? null)) { - $time = Api\DateTime::createFromFormat('+'.Api\DateTime::$user_dateformat.' '.Api\DateTime::$user_timeformat.'*', $value); - $replacements['$$'.$field.'/date$$'] = $time ? $time->format(Api\DateTime::$user_dateformat) : ''; + $time = Api\DateTime::createFromFormat('+' . Api\DateTime::$user_dateformat . ' ' . Api\DateTime::$user_timeformat . '*', $value); + $replacements['$$' . $field . '/date$$'] = $time ? $time->format(Api\DateTime::$user_dateformat) : ''; } } } - if ($is_xml) // zip'ed xml document (eg. OO) + if($is_xml) // zip'ed xml document (eg. OO) { // Numeric fields $names = array(); @@ -1187,35 +1280,35 @@ abstract class Merge // Tags we can replace with the target document's version $replace_tags = array(); // only keep tags, if we have xsl extension available - if (class_exists('XSLTProcessor') && class_exists('DOMDocument') && $this->parse_html_styles) + if(class_exists('XSLTProcessor') && class_exists('DOMDocument') && $this->parse_html_styles) { - switch($mimetype.$mso_application_progid) + switch($mimetype . $mso_application_progid) { case 'text/html': $replace_tags = array( - '','','','','','','
    ','