diff --git a/modules/contacts/modules.php b/modules/contacts/modules.php index 6a6334e185..ddb70162f6 100644 --- a/modules/contacts/modules.php +++ b/modules/contacts/modules.php @@ -85,6 +85,33 @@ public function process() { } } +/** + * @subpackage contacts/handler + */ +class Hm_Handler_process_export_contacts extends Hm_Handler_Module { + public function process() { + if (array_key_exists('contact_source', $this->request->get)) { + $source = $this->request->get['contact_source']; + $contacts = $this->get('contact_store'); + $contact_list = $contacts->getAll(); + if ($source != 'all') { + $contact_list = $contacts->export($source); + } + + Hm_Functions::header('Content-Type: text/csv'); + Hm_Functions::header('Content-Disposition: attachment; filename="'.$source.'_contacts.csv"'); + $output = fopen('php://output', 'w'); + fputcsv($output, array('display_name', 'email_address', 'phone_number')); + foreach ($contact_list as $contact) { + $contact_data = is_array($contact) ? $contact : $contact->export(); + fputcsv($output, array($contact_data['display_name'], $contact_data['email_address'], $contact_data['phone_number'])); + } + fclose($output); + exit; + } + } +} + /** * @subpackage contacts/output */ @@ -107,7 +134,16 @@ protected function output() { */ class Hm_Output_contacts_content_start extends Hm_Output_Module { protected function output() { - return '
'.$this->trans('Contacts').'
'; + $contact_source_list = $this->get('contact_sources', array()); + $actions = '
'.$this->trans('Export Contacts as CSV').'
'; + $actions .= '
'.$this->trans('All Contacts').'
'; + foreach ($contact_source_list as $value) { + $actions .= '
'.$this->html_safe($this->html_safe($value).' Contacts').'
'; + } + + return '
'.$this->trans('Contacts'). '
'. + '
'.$actions.'
'; } } @@ -148,11 +184,25 @@ protected function output() { } } +/** + * @subpackage contacts/handler + */ +class Hm_Handler_check_imported_contacts extends Hm_Handler_Module +{ + public function process() + { + $imported_contact = $this->session->get('imported_contact', array()); + $this->session->del('imported_contact'); + $this->out('imported_contact', $imported_contact); + } +} + /** * @subpackage contacts/output */ class Hm_Output_contacts_list extends Hm_Output_Module { protected function output() { + $imported_contact = $this->get('imported_contact', array()); if (count($this->get('contact_sources', array())) == 0) { return '
'.$this->trans('No contact backends are enabled!'). '
'.$this->trans('At least one backend must be enabled in the config/app.php file to use contacts.').'
'; @@ -160,6 +210,13 @@ protected function output() { $per_page = 25; $current_page = $this->get('contact_page', 1); $res = '
'; + $modal = ''; + if ($imported_contact) { + $res .= + ''; + $modal .= get_import_detail_modal_content($this, $imported_contact); + } + $res .= ''; $contacts = $this->get('contact_store'); $editable = $this->get('contact_edit', array()); @@ -208,7 +265,7 @@ protected function output() { } $res .= ''; } - $res .= '
'.$this->trans('More info about import operation').'
'.$this->trans('Contacts').'
'; + $res .= ''.$modal.'
'; return $res; } } @@ -334,3 +391,79 @@ function name_map($val) { } return $val; }} + + +/** + * @subpackage contacts/functions + */ +if (!hm_exists('get_import_detail_modal_content')) { +function get_import_detail_modal_content($output_mod, $imported_contacts) { + $per_page = 10; + $page = 1; + $total_contacts = count($imported_contacts); + $total_pages = ceil($total_contacts / $per_page); + $res = ' + + + + + + + + + + '; + + for ($i = 0; $i < $total_contacts; $i++) { + $contact = $imported_contacts[$i]; + $status = $contact['status'] == "invalid email" ? "danger" : "success"; + $res .= ' + + + + + + '; + } + + $res .= '
#Display NameE-mail AddressTelephone NumberStatus
'.($i + 1).''.$output_mod->html_safe($contact['display_name']).''.$output_mod->html_safe($contact['email_address']).''.$output_mod->html_safe($contact['phone_number']).''.$output_mod->html_safe($contact['status']).'
'; + + if ($total_pages > 1) { + $res .= ''; + } + + $res .= ''; + + return ''; +}} diff --git a/modules/contacts/setup.php b/modules/contacts/setup.php index 2410c7dd0d..48e84b8d5d 100644 --- a/modules/contacts/setup.php +++ b/modules/contacts/setup.php @@ -10,6 +10,7 @@ setup_base_page('contacts', 'core'); add_handler('contacts', 'load_contacts', true, 'contacts', 'load_user_data', 'after'); +add_handler('contacts', 'check_imported_contacts', true, 'contacts', 'load_user_data', 'after'); add_output('contacts', 'contacts_content_start', true, 'contacts', 'content_section_start', 'after'); add_output('contacts', 'contacts_list', true, 'contacts', 'contacts_content_start', 'after'); add_output('contacts', 'contacts_content_end', true, 'contacts', 'contacts_list', 'after'); @@ -37,11 +38,16 @@ add_handler('ajax_delete_contact', 'load_contacts', true, 'contacts', 'load_user_data', 'after'); add_handler('ajax_delete_contact', 'save_user_data', true, 'core', 'language', 'after'); +setup_base_page('export_contact', 'core'); +add_handler('export_contact', 'load_contacts', true, 'contacts', 'load_user_data', 'after'); +add_handler('export_contact', 'process_export_contacts', true, 'contacts', 'load_contacts', 'after'); + return array( 'allowed_pages' => array( 'contacts', 'ajax_add_contact', 'ajax_delete_contact', + 'export_contact', 'ajax_autocomplete_contact' ), 'allowed_post' => array( @@ -53,16 +59,19 @@ 'edit_contact' => FILTER_DEFAULT, 'add_contact' => FILTER_DEFAULT, 'contact_source' => FILTER_DEFAULT, - 'contact_type' => FILTER_DEFAULT + 'contact_type' => FILTER_DEFAULT, + 'import_contact' => FILTER_DEFAULT, ), 'allowed_get' => array( 'contact_id' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, 'contact_page' => FILTER_VALIDATE_INT, 'contact_type' => FILTER_DEFAULT, 'contact_source' => FILTER_DEFAULT, + 'import_contact' => FILTER_DEFAULT, ), 'allowed_output' => array( 'contact_deleted' => array(FILTER_VALIDATE_INT, false), + 'imported_contact' => array(FILTER_DEFAULT, FILTER_REQUIRE_ARRAY), 'contact_suggestions' => array(FILTER_DEFAULT, FILTER_REQUIRE_ARRAY) ), ); diff --git a/modules/contacts/site.css b/modules/contacts/site.css index 6d60236732..32e77aa8bb 100644 --- a/modules/contacts/site.css +++ b/modules/contacts/site.css @@ -11,6 +11,12 @@ .show_contact { margin-right: 15px; } .contact_detail th { font-weight: normal; text-align: left; padding-right: 20px; } .contact_fld { max-width: 300px; overflow-x: hidden; text-overflow: ellipsis; } +#contact_csv { width: 80%; } +.list_actions { z-index: 100; border-left: solid 1px #ede8e6; border-bottom: solid 1px #ede8e6; position: absolute; right: 0px; top: 54px; background-color: #fafafa; font-size: 85%; padding: 30px; padding-top: 10px; display: none;} +.src_title { color: #666; font-size: 110%; padding: 5px; margin-bottom: 10px; } +.contact_import_detail td { + border-bottom: none !important; +} .mobile .contact_list { margin-left: 0px; font-size: 125%; width: 100%; } .mobile .contact_controls img { width: 20px; height: 20px; } diff --git a/modules/contacts/site.js b/modules/contacts/site.js index 61e89f6444..b9a12b7976 100644 --- a/modules/contacts/site.js +++ b/modules/contacts/site.js @@ -207,6 +207,40 @@ var add_autocomplete = function(event, class_name, list_div, fld_val) { return false; }; +var showPage = function(selected_page, total_pages) { + $('.import_body tr').hide(); + $('.page_' + selected_page).show(); + $('.page_link_selector').removeClass('active'); + $('.page_item_' + selected_page).addClass('active'); + $('.prev_page').toggleClass('disabled', selected_page === 1); + $('.next_page').toggleClass('disabled', selected_page === total_pages); +}; + +var contact_import_pagination = function() { + var selected_page = 1; + var total_pages = $('#totalPages').val(); + showPage(selected_page, total_pages); + + $('.page_link_selector').on('click', function () { + selected_page = $(this).data('page'); + showPage(selected_page, total_pages); + }); + + $('.prev_page').on('click', function () { + if (selected_page > 1) { + selected_page--; + showPage(selected_page, total_pages); + } + }); + + $('.next_page').on('click', function () { + if (selected_page < total_pages) { + selected_page++; + showPage(selected_page, total_pages); + } + }); +}; + if (hm_page_name() == 'contacts') { $('.delete_contact').on("click", function() { delete_contact($(this).data('id'), $(this).data('source'), $(this).data('type')); @@ -234,6 +268,11 @@ if (hm_page_name() == 'contacts') { } }); + $('.source_link').on("click", function () { + $('.list_actions').toggle(); $('#list_controls_menu').hide(); + return false; + }); + contact_import_pagination(); } else if (hm_page_name() == 'compose') { $('.compose_to').on('keyup', function(e) { autocomplete_contact(e, '.compose_to', '#to_contacts'); }); diff --git a/modules/local_contacts/assets/data/contact_sample.csv b/modules/local_contacts/assets/data/contact_sample.csv new file mode 100644 index 0000000000..83b3e012d5 --- /dev/null +++ b/modules/local_contacts/assets/data/contact_sample.csv @@ -0,0 +1,2 @@ +display_name,email_address,phone_number +Thomas Tester,test@example.org,1234567890 \ No newline at end of file diff --git a/modules/local_contacts/modules.php b/modules/local_contacts/modules.php index e7761062d4..e83331b6ec 100644 --- a/modules/local_contacts/modules.php +++ b/modules/local_contacts/modules.php @@ -65,6 +65,90 @@ public function process() { } } +/** + * @subpackage local_contacts/handler + */ +class Hm_Handler_process_import_contact extends Hm_Handler_Module { + public function process() { + list($success, $form) = $this->process_form(array('contact_source', 'import_contact')); + if ($success && $form['contact_source'] == 'csv') { + $file = $this->request->files['contact_csv']; + $csv = fopen($file['tmp_name'], 'r'); + if ($csv) { + $contacts = $this->get('contact_store'); + $header = fgetcsv($csv); + $expectedHeader = array('display_name', 'email_address', 'phone_number'); + + if ($header !== $expectedHeader) { + fclose($csv); + Hm_Msgs::add('ERRInvalid CSV file, please use a valid header: '.implode(', ', $expectedHeader)); + return; + } + + $contact_list = $contacts->getAll(); + $message = ''; + $update_count = 0; + $create_count = 0; + $invalid_mail_count = 0; + $import_result = []; + + + while (($data = fgetcsv($csv)) !== FALSE) { + $single_contact = [ + 'display_name' => $data[0], + 'email_address' => $data[1], + 'phone_number' => $data[2] ?? '' + ]; + $email = $data[1]; + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $single_contact['status'] = 'invalid email'; + array_push($import_result, $single_contact); + $invalid_mail_count++; + continue; + } + + $details = array('source' => 'local', 'display_name' => $data[0], 'email_address' => $email); + if (array_key_exists(2, $data) && $data[2]) { + $details['phone_number'] = $data[2]; + } + + $contactUpdated = false; + foreach ($contact_list as $key => $contact) { + if ($contact->value('email_address') == $email) { + $contacts->update_contact($key, $details); + $single_contact['status'] = 'update'; + array_push($import_result, $single_contact); + $update_count++; + $contactUpdated = true; + continue 2; + } + } + + if (!$contactUpdated) { + $contacts->add_contact($details); + $single_contact['status'] = 'new'; + array_push($import_result, $single_contact); + $create_count++; + } + } + fclose($csv); + $contacts->save(); + $this->session->record_unsaved('Contact Created'); + if (isset($import_result) && (!$create_count && !$update_count)) { + $message = 'ERR'.$create_count.' contacts created, '.$update_count.' contacts updated, '.$invalid_mail_count.' Invalid email address'; + } elseif (isset($import_result) && ($create_count || $update_count)) { + $message = $create_count.' contacts created, '.$update_count.' contacts updated, '.$invalid_mail_count.' Invalid email address'; + } else { + $message = 'ERRAn error occured'; + } + + $this->session->set('imported_contact', $import_result); + Hm_Msgs::add($message); + } + } + } +} + /** * @subpackage local_contacts/handler */ @@ -159,3 +243,26 @@ protected function output() { $this->trans('Cancel').'" />
'; } } + +/** + * @subpackage import_local_contacts/output + */ +class Hm_Output_import_contacts_form extends Hm_Output_Module { + protected function output() { + $form_class = 'contact_form'; + $button = ''; + $notice = 'Please ensure your CSV header file follows the format: display_name,email_address,phone_number'; + $title = $this->trans('Import from CSV file'); + $csv_sample_path = WEB_ROOT.'modules/local_contacts/assets/data/contact_sample.csv'; + + return '
'. + ''. + '
'. + '
'.$this->trans('download a sample csv file').'

'. + ''. + ''. + ''. + '
'.$button.'
'; + } +} diff --git a/modules/local_contacts/setup.php b/modules/local_contacts/setup.php index 86d5a974a4..800dab8011 100644 --- a/modules/local_contacts/setup.php +++ b/modules/local_contacts/setup.php @@ -8,7 +8,9 @@ add_handler('contacts', 'load_local_contacts', true, 'local_contacts', 'load_contacts', 'after'); add_handler('contacts', 'load_edit_contact', true, 'local_contacts', 'load_local_contacts', 'after'); add_handler('contacts', 'process_add_contact', true, 'local_contacts', 'load_edit_contact', 'after'); +add_handler('contacts', 'process_import_contact', true, 'local_contacts', 'load_edit_contact', 'after'); add_handler('contacts', 'process_edit_contact', true, 'local_contacts', 'load_local_contacts', 'after'); +add_output('contacts', 'import_contacts_form', true, 'contacts', 'contacts_content_start', 'after'); add_output('contacts', 'contacts_form', true, 'contacts', 'contacts_content_start', 'after'); add_handler('ajax_autocomplete_contact', 'load_local_contacts', true, 'local_contacts', 'load_contacts', 'after'); @@ -20,4 +22,6 @@ add_handler('ajax_delete_contact', 'load_local_contacts', true, 'local_contacts', 'load_contacts', 'after'); add_handler('ajax_delete_contact', 'process_delete_contact', true, 'local_contacts', 'save_user_data', 'before'); +add_handler('contacts', 'process_import_contact', true, 'local_contacts', 'login', 'after'); + return array();