Skip to content

Commit

Permalink
Merge pull request #904 from kambereBr/add_csv_contact_import
Browse files Browse the repository at this point in the history
Add CSV import/export functionality to the contacts module
  • Loading branch information
kroky authored Feb 22, 2024
2 parents 2a624a9 + 9626394 commit fe1c658
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 3 deletions.
137 changes: 135 additions & 2 deletions modules/contacts/modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -107,7 +134,16 @@ protected function output() {
*/
class Hm_Output_contacts_content_start extends Hm_Output_Module {
protected function output() {
return '<div class="contacts_content px-0"><div class="content_title px-3">'.$this->trans('Contacts').'</div>';
$contact_source_list = $this->get('contact_sources', array());
$actions = '<div class="src_title">'.$this->trans('Export Contacts as CSV').'</div>';
$actions .= '<div class="list_src"><a href="?page=export_contact&amp;contact_source=all">'.$this->trans('All Contacts').'</a></div>';
foreach ($contact_source_list as $value) {
$actions .= '<div class="list_src"><a href="?page=export_contact&amp;contact_source='.$this->html_safe($value).'">'.$this->html_safe($this->html_safe($value).' Contacts').'</a></div>';
}

return '<div class="contacts_content p-0"><div class="content_title d-flex gap-2 justify-content-between px-3 align-items-center"><div class="d-flex gap-2 align-items-center">'.$this->trans('Contacts'). '</div><div class="list_controls source_link d-flex gap-2 align-items-center"><a href="#" title="' . $this->trans('Export Contacts') . '" class="refresh_list">' .
'<i class="bi bi-download" width="16" height="16" onclick="listControlsMenu()"></i></a></div></div>'.
'<div class="list_actions">'.$actions.'</div>';
}
}

Expand Down Expand Up @@ -148,18 +184,39 @@ 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 '<div class="no_contact_sources">'.$this->trans('No contact backends are enabled!').
'<br />'.$this->trans('At least one backend must be enabled in the config/app.php file to use contacts.').'</div>';
}
$per_page = 25;
$current_page = $this->get('contact_page', 1);
$res = '<div class="px-3 mt-3"><table class="contact_list table">';
$modal = '';
if ($imported_contact) {
$res .=
'<tr class="contact_import_detail"><td colspan="7"><a href="#" class="show_import_detail text-danger" data-bs-toggle="modal" data-bs-target="#importDetailModal">'.$this->trans('More info about import operation').'</a></td></tr>';
$modal .= get_import_detail_modal_content($this, $imported_contact);
}

$res .= '<tr><td colspan="7" class="contact_list_title"><div class="server_title">'.$this->trans('Contacts').'</div></td></tr>';
$contacts = $this->get('contact_store');
$editable = $this->get('contact_edit', array());
Expand Down Expand Up @@ -208,7 +265,7 @@ protected function output() {
}
$res .= '</td></tr>';
}
$res .= '</table></div>';
$res .= '</table>'.$modal.'</div>';
return $res;
}
}
Expand Down Expand Up @@ -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 = '<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Display Name</th>
<th scope="col">E-mail Address</th>
<th scope="col">Telephone Number</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody class="import_body">';

for ($i = 0; $i < $total_contacts; $i++) {
$contact = $imported_contacts[$i];
$status = $contact['status'] == "invalid email" ? "danger" : "success";
$res .= '<tr class="page_'.ceil(($i + 1) / $per_page).'">
<td>'.($i + 1).'</td>
<td>'.$output_mod->html_safe($contact['display_name']).'</td>
<td>'.$output_mod->html_safe($contact['email_address']).'</td>
<td>'.$output_mod->html_safe($contact['phone_number']).'</td>
<td class="text-'.$status.'">'.$output_mod->html_safe($contact['status']).'</td>
</tr>';
}

$res .= '</tbody></table>';

if ($total_pages > 1) {
$res .= '<nav aria-label="Pagination">
<ul class="pagination justify-content-center">
<li class="prev_page '.($page == 1 ? "disabled" : "").'">
<span role="button" class="page-link" tabindex="-1" aria-disabled="true">Previous</span>
</li>';

for ($i = 1; $i <= $total_pages; $i++) {
$res .= '<li class="page_item_'.$i.' '.($page == $i ? "active" : "").' page_link_selector" data-page="'. $i .'"><span role="button" class="page-link">'.$i.'</span></li>';
}

$res .= '<li class="next_page '.($page == $total_pages ? "disabled" : "").'">
<span role="button" class="page-link">Next</span>
</li>
</ul>
</nav>';
}

$res .= '<input type="hidden" id="totalPages" value="'.$total_pages.'">';

return '<div class="modal fade" id="importDetailModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="importDetailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="importDetailModalLabel">'.$output_mod->trans('Import details').'</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div>
'.$res.'
</div>
</div>
<div class="modal-footer">
<input class="btn btn-secondary" data-bs-dismiss="modal" type="button" value="Cancel" />
</div>
</div>
</div>
</div>';
}}
11 changes: 10 additions & 1 deletion modules/contacts/setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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(
Expand All @@ -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)
),
);
Expand Down
6 changes: 6 additions & 0 deletions modules/contacts/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
39 changes: 39 additions & 0 deletions modules/contacts/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down Expand Up @@ -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'); });
Expand Down
2 changes: 2 additions & 0 deletions modules/local_contacts/assets/data/contact_sample.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
display_name,email_address,phone_number
Thomas Tester,[email protected],1234567890
Loading

0 comments on commit fe1c658

Please sign in to comment.