diff --git a/addressbook/inc/class.addressbook_hooks.inc.php b/addressbook/inc/class.addressbook_hooks.inc.php index fd39c71b41..9a514656a6 100644 --- a/addressbook/inc/class.addressbook_hooks.inc.php +++ b/addressbook/inc/class.addressbook_hooks.inc.php @@ -304,17 +304,27 @@ class addressbook_hooks '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()), + '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', + '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', ); } diff --git a/api/src/Storage/Merge.php b/api/src/Storage/Merge.php index 4a407e89ae..ca01c49612 100644 --- a/api/src/Storage/Merge.php +++ b/api/src/Storage/Merge.php @@ -32,9 +32,26 @@ use ZipArchive; */ abstract class Merge { - + /** + * Preference, path where we will put the generated document + */ const PREF_STORE_LOCATION = "merge_store_path"; + /** + * Preference, placeholders for creating the filename of the generated document + */ + const PREF_DOCUMENT_FILENAME = "document_download_name"; + + /** + * List of placeholders + */ + const DOCUMENT_FILENAME_OPTIONS = [ + '$$document$$' => 'Template name', + '$$link_title$$' => 'Entry link-title', + '$$contact_title$$' => 'Contact link-title', + '$$current_date$$' => 'Current date', + ]; + /** * Instance of the addressbook_bo class * @@ -246,47 +263,59 @@ abstract class Merge $replacements = array(); foreach(array_keys($this->contacts->contact_fields) as $name) { - $value = $contact[$name] ?? null; + $value = $contact[$name] ?? ''; + if(!$value) + { + continue; + } switch($name) { - case 'created': case 'modified': - if($value) $value = Api\DateTime::to($value); + case 'created': + case 'modified': + if($value) + { + $value = Api\DateTime::to($value); + } break; case 'bday': - if ($value) + if($value) { - try { + try + { $value = Api\DateTime::to($value, true); } - catch (\Exception $e) { - unset($e); // ignore exception caused by wrongly formatted date + catch (\Exception $e) + { + unset($e); // ignore exception caused by wrongly formatted date } } break; - case 'owner': case 'creator': case 'modifier': + case 'owner': + case 'creator': + case 'modifier': $value = Api\Accounts::username($value); break; case 'cat_id': - if ($value) + if($value) { // if cat-tree is displayed, we return a full category path not just the name of the cat $use = $GLOBALS['egw_info']['server']['cat_tab'] == 'Tree' ? 'path' : 'name'; $cats = array(); - foreach(is_array($value) ? $value : explode(',',$value) as $cat_id) + foreach(is_array($value) ? $value : explode(',', $value) as $cat_id) { - $cats[] = $GLOBALS['egw']->categories->id2name($cat_id,$use); + $cats[] = $GLOBALS['egw']->categories->id2name($cat_id, $use); } - $value = implode(', ',$cats); + $value = implode(', ', $cats); } break; - case 'jpegphoto': // returning a link might make more sense then the binary photo - if ($contact['photo']) + case 'jpegphoto': // returning a link might make more sense then the binary photo + if($contact['photo']) { - $value = Api\Framework::getUrl(Api\Framework::link('/index.php',$contact['photo'])); + $value = Api\Framework::getUrl(Api\Framework::link('/index.php', $contact['photo'])); } break; case 'tel_prefer': - if ($value && $contact[$value]) + if($value && $contact[$value]) { $value = $contact[$value]; } @@ -1615,7 +1644,7 @@ abstract class Merge public static function get_app_class($appname) { $classname = "{$appname}_merge"; - if(class_exists($classname) && is_subclass_of($classname, 'EGroupware\\Api\\Storage\\Merge')) + if(class_exists($classname, false) && is_subclass_of($classname, 'EGroupware\\Api\\Storage\\Merge')) { $document_merge = new $classname(); } @@ -1644,14 +1673,9 @@ abstract class Merge try { - $classname = "{$app}_merge"; - if(!class_exists($classname)) - { - return $replacements; - } - $class = new $classname(); - $method = $app.'_replacements'; - if(method_exists($class,$method)) + $class = $this->get_app_class($app); + $method = $app . '_replacements'; + if(method_exists($class, $method)) { $replacements = $class->$method($id, $prefix, $content); } @@ -2447,7 +2471,7 @@ abstract class Merge $pdf = (boolean)$_REQUEST['pdf']; } - $filename = $document_merge->get_filename($_REQUEST['document']); + $filename = $document_merge->get_filename($_REQUEST['document'], $ids); $result = $document_merge->merge_file($_REQUEST['document'], $ids, $filename, '', $header); if(!is_file($result) || !is_readable($result)) @@ -2519,12 +2543,54 @@ abstract class Merge /** * Generate a filename for the merged file, without extension * - * Default is just the name of the template + * Default filename is just the name of the template. + * We use the placeholders from get_filename_placeholders() and the application's document filename preference + * to generate a custom filename. + * + * @param string $document Template filename + * @param string[] $ids List of IDs being merged * @return string */ - protected function get_filename($document) : string + protected function get_filename($document, $ids = []) : string { - return ''; + $name = ''; + if(isset($GLOBALS['egw_info']['user']['preferences'][$this->get_app()][static::PREF_DOCUMENT_FILENAME])) + { + $pref = $GLOBALS['egw_info']['user']['preferences'][$this->get_app()][static::PREF_DOCUMENT_FILENAME]; + $placeholders = $this->get_filename_placeholders($document, $ids); + + // Make values safe for VFS + foreach($placeholders as &$value) + { + $value = Api\Mail::clean_subject_for_filename($value); + } + + // Do replacement + $name = str_replace( + array_keys($placeholders), + array_values($placeholders), + is_array($pref) ? implode(' ', $pref) : $pref + ); + } + return $name; + } + + protected function get_filename_placeholders($document, $ids) + { + $ext = '.' . pathinfo($document, PATHINFO_EXTENSION); + $link_title = count($ids) == 1 ? Api\Link::title($this->get_app(), $ids[0]) : lang("multiple"); + $contact_title = count($ids) == 1 ? Api\Link::title($this->get_app(), $ids[0]) : lang("multiple"); + $current_date = str_replace('/', '-', Api\DateTime::to('now', Api\DateTime::$user_dateformat)); + + + $values = [ + '$$document$$' => basename($document, $ext), + '$$link_title$$' => $link_title, + '$$contact_title$$' => $contact_title, + '$$current_date$$' => $current_date + ]; + + return $values; } /** diff --git a/infolog/inc/class.infolog_hooks.inc.php b/infolog/inc/class.infolog_hooks.inc.php index f7837b217b..e09c7d23d1 100644 --- a/infolog/inc/class.infolog_hooks.inc.php +++ b/infolog/inc/class.infolog_hooks.inc.php @@ -471,17 +471,27 @@ class infolog_hooks 'admin' => False, ); $settings['document_dir'] = array( - 'type' => 'vfs_dirs', - 'size' => 60, - 'label' => 'Directory with documents to insert entries', - '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('infolog')).' '. - lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','info_subject').' '. - lang('The following document-types are supported:').'*.rtf, *.txt', + 'type' => 'vfs_dirs', + 'size' => 60, + 'label' => 'Directory with documents to insert entries', + '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('infolog')) . ' ' . + lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'info_subject') . ' ' . + lang('The following document-types are supported:') . '*.rtf, *.txt', 'run_lang' => false, - 'xmlrpc' => True, - 'admin' => False, - 'default' => '/templates/infolog', + 'xmlrpc' => True, + 'admin' => False, + 'default' => '/templates/infolog', + ); + $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', ); } diff --git a/infolog/inc/class.infolog_merge.inc.php b/infolog/inc/class.infolog_merge.inc.php index def1692722..c53c3370bc 100644 --- a/infolog/inc/class.infolog_merge.inc.php +++ b/infolog/inc/class.infolog_merge.inc.php @@ -64,23 +64,42 @@ class infolog_merge extends Api\Storage\Merge * @param string &$content=null content to create some replacements only if they are use * @return array|boolean */ - protected function get_replacements($id,&$content=null) + protected function get_replacements($id, &$content = null) { - if (!($replacements = $this->infolog_replacements($id, '', $content))) + if(!($replacements = $this->infolog_replacements($id, '', $content))) { return false; } return $replacements; } + /** + * Override contact filename placeholder to use info_contact + * + * @param $document + * @param $ids + * @return array|void + */ + public function get_filename_placeholders($document, $ids) + { + $placeholders = parent::get_filename_placeholders($document, $ids); + if(count($ids) == 1 && ($info = $this->bo->read($ids[0]))) + { + $placeholders['$$contact_title$$'] = $info['info_contact']['title'] ?? + (is_array($info['info_contact']) && Link::title($info['info_contact']['app'], $info['info_contact']['id'])) ?? + ''; + } + return $placeholders; + } + /** * Get infolog replacements * * @param int $id id of entry - * @param string $prefix='' prefix like eg. 'erole' + * @param string $prefix ='' prefix like eg. 'erole' * @return array|boolean */ - public function infolog_replacements($id,$prefix='', &$content = '') + public function infolog_replacements($id, $prefix = '', &$content = '') { $record = new infolog_egw_record($id); $info = array(); diff --git a/timesheet/inc/class.timesheet_hooks.inc.php b/timesheet/inc/class.timesheet_hooks.inc.php index c08fd99c58..7b9f66a0f8 100644 --- a/timesheet/inc/class.timesheet_hooks.inc.php +++ b/timesheet/inc/class.timesheet_hooks.inc.php @@ -191,17 +191,27 @@ class timesheet_hooks 'admin' => False, ); $settings['document_dir'] = array( - 'type' => 'vfs_dirs', - 'size' => 60, - 'label' => 'Directory with documents to insert entries', - '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 %1 data inserted.', lang('timesheet')).' '. - lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','ts_title').' '. - lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()), + 'type' => 'vfs_dirs', + 'size' => 60, + 'label' => 'Directory with documents to insert entries', + '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 %1 data inserted.', lang('timesheet')) . ' ' . + lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'ts_title') . ' ' . + lang('The following document-types are supported:') . implode(',', Api\Storage\Merge::get_file_extensions()), 'run_lang' => false, - 'xmlrpc' => True, - 'admin' => False, - 'default' => '/templates/timesheet', + 'xmlrpc' => True, + 'admin' => False, + 'default' => '/templates/timesheet', + ); + $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', ); }