Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct pagination while displaying contacts #1125

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions modules/contacts/hm-contacts.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,66 @@ public function reset() {
$this->data = array();
}

public function page($page, $size) {
public function page($page, $size, $data = null) {
if ($page < 1) {
return array();
}
return array_slice($this->data, (($page - 1)*$size), $size, true);
if ($data === null) {
$data = $this->data;
}
return array_slice($data, (($page - 1)*$size), $size, true);
}

public function group_by($column = 'group') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you refactor the code to group by a string version of the column name? I don't see a reason we group by a callable or support cases for array or object contacts. You can refactor calling code to ensure we are working with a standard set of data - either array of arrays or array of contact objects.

if (!is_string($column) && !is_int($column) && !is_float($column) && !is_callable($column) ) {
trigger_error('group_by(): The key should be a string, an integer, or a callback', E_USER_ERROR);
return null;
}
$func = (!is_string($column) && is_callable($column) ? $column : null);
$_key = $column;
// Load the new array, splitting by the target key
$grouped = [];
foreach ($this->data as $value) {

$column = null;
if (is_callable($func)) {
$column = call_user_func($func, $value);
} elseif (is_object($value)) {
$reflection = new ReflectionClass($value);
$property = $reflection->getProperty('data');
$property->setAccessible(true); // Make the private property accessible
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a reason why private properties are private and using reflection to bypass this is something nasty. If this is a contact object isn't $value->value($_key) enough to get you what you want?

// Get the value of the data property
$contact = $property->getValue($value);
$column = isset($contact[$_key]) ? $contact[$_key] : 'Personal Addresses';
} elseif (isset($value[$_key])) {
$column = $value[$_key];
}
if ($column === null) {
continue;
}
$grouped[$column][] = $value;
}
// Recursively build a nested grouping if more parameters are supplied
// Each grouped array value is grouped according to the next sequential key
if (func_num_args() > 2) {
$args = func_get_args();
foreach ($grouped as $key => $value) {
$params = array_merge([ $value ], array_slice($args, 2, func_num_args()));
$grouped[$key] = call_user_func_array('group_by', $params);
}
}
return $grouped;
}

public function paginate_grouped($column, $page, $size) {
$grouped = $this->group_by($column);
$paginated = [];
foreach ($grouped as $key => $group) {
$paginated[$key] = $this->page($page, $size, $group);
}
return $paginated;
}

public function sort($fld) {
$this->sort_fld = $fld;
uasort($this->data, array($this, 'sort_callback'));
Expand Down
86 changes: 41 additions & 45 deletions modules/contacts/modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,20 +308,15 @@ protected function output() {
$res = '<div class="contact-group contact-group-effect-scale contact-group-theme-1">';
$tabIndex = 1;
$contactGroups = [];

if ($contacts) {
foreach ($contacts->page($current_page, $per_page) as $id => $contact) {
$group = $contact->value('group');
if (!$group || empty($group)) {
$group = 'Personal Addresses'; // Set the group to "Personal Addresses" when it's null or empty
}
if (!array_key_exists($group, $contactGroups)) {
$contactGroups[$group] = [];
foreach ($contacts->paginate_grouped('group', $current_page, $per_page) as $key => $contact) {
if (!array_key_exists($key, $contactGroups)) {
$contactGroups[$key] = [];
}
$contactGroups[$group][] = $contact;
$contactGroups[$key][] = $contact;
}
}

foreach ($contactGroups as $group => $groupContacts) {
$res .= '<input type="radio" name="contact-group" ' . ($tabIndex === 1 ? 'checked ' : '') . 'id="tab' . $tabIndex . '" class="' . ($tabIndex === 1 ? 'tab-content-first' : 'tab-content-' . $tabIndex) . '">';
$res .= '<label for="tab' . $tabIndex . '">' . $this->html_safe($group) . '</label>';
Expand All @@ -332,54 +327,55 @@ protected function output() {
$res .= '<ul>';

foreach ($contactGroups as $group => $groupContacts) {

$res .= '<li class="tab-content '.($tabIndex === 1 ? 'tab-content-first' : 'tab-content-'.$tabIndex).' typography">';
$res .= '<table class="contact_list">';
$res .= '<tr><td colspan="7" class="contact_list_title"><div class="server_title">'.$this->trans('Contacts').'</div></td></tr>';
$total = count($groupContacts);

foreach ($groupContacts as $contact) {
$name = $contact->value('display_name');
if (!trim($name)) {
$name = $contact->value('fn');
}
$res .= '<tr class="contact_row_'.$this->html_safe($id).'">';
$res .= '<td><a data-id="contact_'.$this->html_safe($id).'_detail" '.
'" class="show_contact" title="'.$this->trans('Details').'">'.
'<i class="bi bi-person-fill"></i> '.
'</d><td>'.$this->html_safe($contact->value('type')).'<td><span class="contact_src">'.
($contact->value('source') == 'local' ? '' : $this->html_safe($contact->value('source'))).'</span>'.
'</td><td>' . $this->html_safe($name) . '</td>' .
'<td><div class="contact_fld">'.$this->html_safe($contact->value('email_address')).'</div></td>'.
'<td class="contact_fld"><a href="tel:'.$this->html_safe($contact->value('phone_number')).'">'.
$this->html_safe($contact->value('phone_number')).'</a></td>'.
'<td class="text-end" style="width : 100px">';
if (in_array($contact->value('type').':'.$contact->value('source'), $editable, true)) {
$res .= '<a data-id="'.$this->html_safe($contact->value('id')).'" data-type="'.$this->html_safe($contact->value('type')).'" data-source="'.$this->html_safe($contact->value('source')).
'" class="delete_contact cursor-pointer" title="'.$this->trans('Delete').'"><i class="bi bi-trash3 text-danger ms-2"></i></a>'.
'<a href="?page=contacts&amp;contact_id='.$this->html_safe($contact->value('id')).'&amp;contact_source='.
$this->html_safe($contact->value('source')).'&amp;contact_type='.
$this->html_safe($contact->value('type')).'&amp;contact_page='.$current_page.
'" class="edit_contact cursor-pointer" title="'.$this->trans('Edit').'"><i class="bi bi-gear ms-2"></i></a>';
foreach ($contact as $c) {
$name = $c->value('display_name');
if (!trim($name)) {
$name = $c->value('fn');
}

$res .= '<tr class="contact_row_'.$this->html_safe($c->value('id')).'">';
$res .= '<td><a data-id="contact_'.$this->html_safe($c->value('id')).'_detail" '.
'" class="show_contact" title="'.$this->trans('Details').'">'.
'<i class="bi bi-person-fill"></i> '.
'</d><td>'.$this->html_safe($c->value('type')).'<td><span class="contact_src">'.
($c->value('source') == 'local' ? '' : $this->html_safe($c->value('source'))).'</span>'.
'</td><td>' . $this->html_safe($name) . '</td>' .
'<td><div class="contact_fld">'.$this->html_safe($c->value('email_address')).'</div></td>'.
'<td class="contact_fld"><a href="tel:'.$this->html_safe($c->value('phone_number')).'">'.
$this->html_safe($c->value('phone_number')).'</a></td>'.
'<td class="text-end" style="width : 100px">';
if (in_array($c->value('type').':'.$c->value('source'), $editable, true)) {
$res .= '<a data-id="'.$this->html_safe($c->value('id')).'" data-type="'.$this->html_safe($c->value('type')).'" data-source="'.$this->html_safe($c->value('source')).
'" class="delete_contact cursor-pointer" title="'.$this->trans('Delete').'"><i class="bi bi-trash3 text-danger ms-2"></i></a>'.
'<a href="?page=contacts&amp;contact_id='.$this->html_safe($c->value('id')).'&amp;contact_source='.
$this->html_safe($c->value('source')).'&amp;contact_type='.
$this->html_safe($c->value('type')).'&amp;contact_page='.$current_page.
'" class="edit_contact cursor-pointer" title="'.$this->trans('Edit').'"><i class="bi bi-gear ms-2"></i></a>';
}
$res .= '<a href="?page=compose&amp;contact_id='.$this->html_safe($c->value('id')).
'" class="send_to_contact cursor-pointer" title="'.$this->trans('Send To').'">'.
'<i class="bi bi-file-earmark-text ms-2"></i></a>';

$res .= '</td></tr>';
$res .= '<tr><td id="contact_'.$this->html_safe($c->value('id')).'_detail" class="contact_detail_row" colspan="6">';
$res .= build_contact_detail($this, $c, $c->value('id')).'</td>';
$res .= '</td></tr>';
}
$res .= '<a href="?page=compose&amp;contact_id='.$this->html_safe($contact->value('id')).
'" class="send_to_contact cursor-pointer" title="'.$this->trans('Send To').'">'.
'<i class="bi bi-file-earmark-text ms-2"></i></a>';

$res .= '</td></tr>';
$res .= '<tr><td id="contact_'.$this->html_safe($id).'_detail" class="contact_detail_row" colspan="6">';
$res .= build_contact_detail($this, $contact, $id).'</td>';
$res .= '</td></tr>';
}
$res .= '<tr><td class="contact_pages" colspan="7">';
$contactsPerPage = $per_page;
$totalContacts = count($contacts->dump());
$totalContacts = count($contact);
$totalPages = ceil($totalContacts / $contactsPerPage);
$currentPage = $current_page;

if ($currentPage > 1) {
$res .= '<a href="?page=contacts&contact_page='.($currentPage - 1).'">Previous</a>';
}
if ($currentPage < $totalPages) {
if ($currentPage <= $totalPages) {
$res .= ' <a href="?page=contacts&contact_page='.($currentPage + 1).'">Next</a>';
}
$res .= '</td></tr>';
Expand Down