Skip to content

Commit

Permalink
Merge pull request #23094 from colemanw/contactTypeIconUI
Browse files Browse the repository at this point in the history
dev/user-interface#26 Use font-awesome icons for contact types and recent items
  • Loading branch information
colemanw authored Apr 5, 2022
2 parents 5cd2310 + 3af57cd commit 77b2699
Show file tree
Hide file tree
Showing 14 changed files with 153 additions and 62 deletions.
15 changes: 14 additions & 1 deletion CRM/Admin/Form/ContactType.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,15 @@ public function buildQuickForm() {
$enabled->freeze();
}
}
$this->addElement('text', 'image_URL', ts('Image URL'));
// TODO: Remove when dropping image_URL column
if ($this->_id) {
$imageUrl = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_ContactType', $this->_id, 'image_URL');
if ($imageUrl) {
$this->addElement('text', 'image_URL', ts('Image URL'));
}
}
$this->assign('hasImageUrl', !empty($imageUrl));
$this->add('text', 'icon', ts('Icon'), ['class' => 'crm-icon-picker', 'title' => ts('Choose Icon'), 'allowClear' => TRUE]);
$this->add('text', 'description', ts('Description'),
CRM_Core_DAO::getAttribute('CRM_Contact_DAO_ContactType', 'description')
);
Expand Down Expand Up @@ -122,6 +130,11 @@ public function postProcess() {
}
}

// If icon is set, it overrides image_URL
if (!empty($params['icon'])) {
$params['image_URL'] = '';
}

$contactType = CRM_Contact_BAO_ContactType::add($params);
CRM_Core_Session::setStatus(ts("The Contact Type '%1' has been saved.",
[1 => $contactType->label]
Expand Down
66 changes: 31 additions & 35 deletions CRM/Contact/BAO/Contact/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
class CRM_Contact_BAO_Contact_Utils {

/**
* Given a contact type, get the contact image.
* Given a contact type or sub_type(s), generate markup for the contact type icon.
*
* @param string $contactType
* @param string $contactTypes
* Contact type.
* @param bool $urlOnly
* If we need to return only image url.
Expand All @@ -35,47 +35,43 @@ class CRM_Contact_BAO_Contact_Utils {
* @return string
* @throws \CRM_Core_Exception
*/
public static function getImage($contactType, $urlOnly = FALSE, $contactId = NULL, $addProfileOverlay = TRUE, $contactUrl = NULL) {

static $imageInfo = [];
public static function getImage($contactTypes, $urlOnly = FALSE, $contactId = NULL, $addProfileOverlay = TRUE, $contactUrl = NULL) {
// Ensure string data is unserialized
$contactTypes = CRM_Utils_Array::explodePadded($contactTypes);

$contactType = CRM_Utils_Array::explodePadded($contactType);
$contactType = $contactType[0];
$allContactTypeInfo = \CRM_Contact_BAO_ContactType::getAllContactTypes();

if (!array_key_exists($contactType, $imageInfo)) {
$imageInfo[$contactType] = [];
$imageInfo = ['url' => NULL, 'image' => NULL];

$typeInfo = [];
$params = ['name' => $contactType];
CRM_Contact_BAO_ContactType::retrieve($params, $typeInfo);
foreach ($contactTypes as $contactType) {
$typeInfo = $allContactTypeInfo[$contactType];
// Prefer the first type/subtype with an icon
if (!empty($typeInfo['icon'])) {
break;
}

// Fall back to using image_URL if no subtypes have an icon
if (!empty($typeInfo['image_URL'])) {
$imageUrl = $typeInfo['image_URL'];
$config = CRM_Core_Config::singleton();

if (!preg_match("/^(\/|(http(s)?:)).+$/i", $imageUrl)) {
$imageUrl = $config->resourceBase . $imageUrl;
$imageUrl = CRM_Core_Config::singleton()->resourceBase . $imageUrl;
}
$imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$typeInfo['name']}-icon\" style=\"background: url('{$imageUrl}')\" title=\"{$contactType}\"></div>";
$imageInfo[$contactType]['url'] = $imageUrl;
$imageInfo['image'] = "<div class=\"icon crm-icon {$typeInfo['name']}-icon\" style=\"background: url('{$imageUrl}')\" title=\"{$contactType}\"></div>";
$imageInfo['url'] = $imageUrl;
}
else {
if (!empty($typeInfo['parent_id'])) {
$type = CRM_Contact_BAO_ContactType::getBasicType($typeInfo['name']) . '-subtype';
}
else {
$type = $typeInfo['name'] ?? NULL;
}
}

// do not add title since it hides contact name
if ($addProfileOverlay) {
$imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$type}-icon\"></div>";
}
else {
$imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$type}-icon\" title=\"{$contactType}\"></div>";
}
$imageInfo[$contactType]['url'] = NULL;
}
// If subtype doesn't have an image or an icon, use the parent type
if (empty($imageUrl) && empty($typeInfo['icon']) && !empty($typeInfo['parent'])) {
$typeInfo = $allContactTypeInfo[$typeInfo['parent']];
}

// Prefer icon over image
if (!empty($typeInfo['icon'])) {
// do not add title since it hides contact name
$title = $addProfileOverlay ? '' : htmlspecialchars($typeInfo['label']);
$imageInfo['image'] = '<i class="crm-i fa-fw ' . $typeInfo['icon'] . '" title="' . $title . '"></i>';
}

if ($addProfileOverlay) {
Expand All @@ -91,13 +87,13 @@ public static function getImage($contactType, $urlOnly = FALSE, $contactId = NUL
"reset=1&gid={$summaryOverlayProfileId}&id={$contactId}&snippet=4&is_show_email_task=1"
);

$imageInfo[$contactType]['summary-link'] = '<a href="' . $contactURL . '" data-tooltip-url="' . $profileURL . '" class="crm-summary-link">' . $imageInfo[$contactType]['image'] . '</a>';
$imageInfo['summary-link'] = '<a href="' . $contactURL . '" data-tooltip-url="' . $profileURL . '" class="crm-summary-link">' . $imageInfo['image'] . '</a>';
}
else {
$imageInfo[$contactType]['summary-link'] = $imageInfo[$contactType]['image'];
$imageInfo['summary-link'] = $imageInfo['image'];
}

return $urlOnly ? $imageInfo[$contactType]['url'] : $imageInfo[$contactType]['summary-link'];
return $urlOnly ? $imageInfo['url'] : $imageInfo['summary-link'];
}

/**
Expand Down
2 changes: 1 addition & 1 deletion CRM/Contact/BAO/ContactType.php
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,7 @@ public static function deleteCustomRowsForEntityID($customTable, $entityID) {
* @return array
* @throws \API_Exception
*/
protected static function getAllContactTypes() {
public static function getAllContactTypes() {
$cache = Civi::cache('contactTypes');
$cacheKey = 'all_' . $GLOBALS['tsLocale'];
$contactTypes = $cache->get($cacheKey);
Expand Down
52 changes: 52 additions & 0 deletions CRM/Utils/Check/Component/ContactTypes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php
/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/

/**
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
*/
class CRM_Utils_Check_Component_ContactTypes extends CRM_Utils_Check_Component {

/**
* TODO: This check should be removed when the contact_type.image_URL column is dropped
*
* @return CRM_Utils_Check_Message[]
*/
public function checkContactTypeIcons() {
if (CRM_Utils_System::version() !== CRM_Core_BAO_Domain::version()) {
return [];
}

$messages = [];
$contactTypesWithImages = \Civi\Api4\ContactType::get(FALSE)
->addWhere('image_URL', 'IS NOT EMPTY')
->addWhere('icon', 'IS EMPTY')
->execute();

if ($contactTypesWithImages->count()) {
$message = new CRM_Utils_Check_Message(
__FUNCTION__,
ts('Please select an icon for the following contact types using the new icon picker, as image urls will not be supported in future versions of CiviCRM.'),
ts('Contact type images are deprecated'),
\Psr\Log\LogLevel::WARNING,
'fa-picture-o'
);
foreach ($contactTypesWithImages as $contactType) {
$message->addAction($contactType['label'], FALSE, 'href', ['path' => 'civicrm/admin/options/subtype', 'query' => ['action' => 'update', 'id' => $contactType['id'], 'reset' => 1]], 'fa-pencil');
}
$messages[] = $message;
}

return $messages;
}

}
5 changes: 4 additions & 1 deletion CRM/Utils/Check/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,16 @@ public function addHelp($help) {
* Currently supports: api3 or href
* @param array $params
* Params to be passed to CRM.api3 or CRM.url depending on type
* @param string $icon
* Fa-icon class for the button
*/
public function addAction($title, $confirmation, $type, $params) {
public function addAction($title, $confirmation, $type, $params, $icon = NULL) {
$this->actions[] = [
'title' => $title,
'confirm' => $confirmation,
'type' => $type,
'params' => $params,
'icon' => $icon,
];
}

Expand Down
27 changes: 27 additions & 0 deletions CRM/Utils/Recent.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public static function add(
'image_url' => $others['imageUrl'] ?? NULL,
'edit_url' => $others['editUrl'] ?? NULL,
'delete_url' => $others['deleteUrl'] ?? NULL,
'icon' => $others['icon'] ?? self::getIcon($type, $others['subtype'] ?? NULL),
]
);

Expand All @@ -136,6 +137,32 @@ public static function add(
$session->set(self::STORE_NAME, self::$_recent);
}

/**
* @param $type
* @param $subType
* @return string|null
*/
private static function getIcon($type, $subType) {
$icon = NULL;
$contactTypes = CRM_Contact_BAO_ContactType::getAllContactTypes();
if (!empty($contactTypes[$type])) {
// Pick icon from contact sub-type first if available, then contact type
$subTypesAndType = array_merge((array) CRM_Utils_Array::explodePadded($subType), [$type]);
foreach ($subTypesAndType as $contactType) {
$icon = $icon ?? $contactTypes[$contactType]['icon'] ?? NULL;
}
// If no contact type icon, proceed to lookup icon from dao
$type = 'Contact';
}
if (!$icon) {
$daoClass = CRM_Core_DAO_AllCoreTables::getFullName($type);
if ($daoClass) {
$icon = $daoClass::$_icon;
}
}
return $icon ?: 'fa-gear';
}

/**
* Callback for hook_civicrm_post().
* @param \Civi\Core\Event\PostEvent $event
Expand Down
5 changes: 4 additions & 1 deletion ang/crmStatusPage/StatusPage.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ <h3 class="crm-severity-{{status.severity}}">
>
</a>
<div ng-if="status.actions" class="crm-status-item-actions">
<button ng-repeat="action in status.actions" ng-click="doAction(action)">{{ action.title }}</button>
<button type="button" ng-repeat="action in status.actions" ng-click="doAction(action)">
<i class="crm-i {{:: action.icon }}" ng-if="action.icon"></i>
{{:: action.title }}
</button>
</div>
</div>
</div>
Expand Down
15 changes: 12 additions & 3 deletions templates/CRM/Admin/Form/ContactType.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,18 @@
<td>{ts}{$contactTypeName}{/ts} {ts}(built-in){/ts}</td>
{/if}
</tr>
<tr class="crm-contact-type-form-block-image_URL">
<td class="label">{$form.image_URL.label} {help id="id-image_URL"}</td>
<td>{$form.image_URL.html|crmAddClass:'huge40'}</td>
{if $hasImageUrl}
<tr class="crm-contact-type-form-block-image_URL">
<td class="label">{$form.image_URL.label}</td>
<td>{$form.image_URL.html|crmAddClass:'huge40'}</td>
</tr>
<tr class="description status-warning">
<td></td><td>{ts}Support for Image URL will be dropped in the future. Please select an icon instead.{/ts}</td>
</tr>
{/if}
<tr class="crm-contact-type-form-block-icon">
<td class="label">{$form.icon.label}</td>
<td>{$form.icon.html}</td>
</tr>
<tr class="crm-contact-type-form-block-description">
<td class="label">{$form.description.label}
Expand Down
12 changes: 0 additions & 12 deletions templates/CRM/Admin/Page/ContactType.hlp
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,3 @@
{htxt id="id-contactSubtype-intro"}
{ts}CiviCRM comes with 3 basic (built-in) contact types: Individual, Household, and Organization. You can create additional contact types based on these basic types to further differentiate contacts (for example you might create Student, Parent, Staff, and /or Volunteer "subtypes" from the basic Individual type...). You can also re-name the built-in types. Contact subtypes are especially useful when you need to collect and display different sets of custom data for different types of contacts.{/ts}
{/htxt}

{htxt id="id-image_URL-title"}
{ts}Contact Type Icon{/ts}
{/htxt}
{htxt id="id-image_URL"}
<div>{ts 1='<span class="font-italic">sites/.../files</span>' 2='<span class="font-italic">media</span>'}Use this field to set your own icon for this Contact Type. Icon images should be 16 x 16 pixels for best fit. Enter a relative or complete URL to the image file location. Use a location outside of your CiviCRM code directory to reduce the likelihood of losing your image files during an upgrade. (For Drupal sites, you might want to use the %1 directory. For Joomla sites, the consider using the %2 directory.){/ts}</div>
Examples:
<ul>
<li>{ts}Relative URL for a default Drupal site:{/ts}<br /><span class="font-italic">../../../default/files/volunteer_contact_icon.png</span></li>
<li>{ts}Complete URL for icon accessible from an external location:{/ts}<br /><span class="font-italic">http://www.example.com/images/new_icon.gif</span></li>
</ul>
{/htxt}
2 changes: 1 addition & 1 deletion templates/CRM/Block/RecentlyViewed.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
{if $item.image_url}
<span class="icon crm-icon {if $item.subtype}{$item.subtype}{else}{$item.type}{/if}-icon" style="background: url('{$item.image_url}')"></span>
{else}
<span class="icon crm-icon {$item.type}{if $item.subtype}-subtype{/if}-icon"></span>
<i class="crm-i fa-fw {$item.icon}"></i>
{/if}
{if $item.isDeleted}<del>{/if}{$item.title}{if $item.isDeleted}</del>{/if}
</a>
Expand Down
2 changes: 1 addition & 1 deletion tests/phpunit/CRM/Activity/Form/SearchTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function testSearch(): void {
$this->assertEquals([
[
'contact_id' => '3',
'contact_type' => '<a href="/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=3" data-tooltip-url="/index.php?q=civicrm/profile/view&amp;reset=1&amp;gid=7&amp;id=3&amp;snippet=4&amp;is_show_email_task=1" class="crm-summary-link"><div class="icon crm-icon Individual-icon"></div></a>',
'contact_type' => '<a href="/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=3" data-tooltip-url="/index.php?q=civicrm/profile/view&amp;reset=1&amp;gid=7&amp;id=3&amp;snippet=4&amp;is_show_email_task=1" class="crm-summary-link"><i class="crm-i fa-fw fa-user" title=""></i></a>',
'sort_name' => 'Anderson, Anthony',
'display_name' => 'Mr. Anthony Anderson II',
'activity_id' => '1',
Expand Down
8 changes: 4 additions & 4 deletions tests/phpunit/CRM/Financial/Page/AjaxTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ public function testGetFinancialTransactionsList(): void {
$_REQUEST['return'] = TRUE;
$json = CRM_Financial_Page_AJAX::getFinancialTransactionsList();
$json = str_replace(rtrim(CIVICRM_UF_BASEURL, '/'), 'http://FIX ME', $json);
$this->assertEquals('{"sEcho": 1, "iTotalRecords": 1, "iTotalDisplayRecords": 1, "aaData": [ ["","<a href=\"/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=3\" data-tooltip-url=\"/index.php?q=civicrm/profile/view&amp;reset=1&amp;gid=7&amp;id=3&amp;snippet=4&amp;is_show_email_task=1\" class=\"crm-summary-link\"><div'
. ' class=\"icon crm-icon Individual-icon\"></div></a>","<a href=/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=3>Anderson, Anthony</a>","$100.00","12345","' . CRM_Utils_Date::customFormat(date('Ymd')) . ' 12:00 AM","' . CRM_Utils_Date::customFormat(date('Ymd')) . ' 12:00 AM",'
$this->assertEquals('{"sEcho": 1, "iTotalRecords": 1, "iTotalDisplayRecords": 1, "aaData": [ ["","<a href=\"/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=3\" data-tooltip-url=\"/index.php?q=civicrm/profile/view&amp;reset=1&amp;gid=7&amp;id=3&amp;snippet=4&amp;is_show_email_task=1\" class=\"crm-summary-link\">'
. '<i class=\"crm-i fa-fw fa-user\" title=\"\"></i></a>","<a href=/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=3>Anderson, Anthony</a>","$100.00","12345","' . CRM_Utils_Date::customFormat(date('Ymd')) . ' 12:00 AM","' . CRM_Utils_Date::customFormat(date('Ymd')) . ' 12:00 AM",'
. '"Credit Card","Completed","Donation","<span><a href=\"/index.php?q=civicrm/contact/view/contribution&amp;reset=1&amp;id=1&amp;cid=3&amp;action=view&amp;context=contribution&amp;'
. 'selectedChild=contribute\" class=\"action-item crm-hover-button\" title=\'View Contribution\' >View</a></span>"]] }', $json);
}
Expand All @@ -66,8 +66,8 @@ public function testGetFinancialTransactionsListOpenBatch(): void {
$_REQUEST['return'] = TRUE;
$json = CRM_Financial_Page_AJAX::getFinancialTransactionsList();
$json = str_replace(rtrim(CIVICRM_UF_BASEURL, '/'), 'http://FIX ME', $json);
$this->assertEquals('{"sEcho": 1, "iTotalRecords": 1, "iTotalDisplayRecords": 1, "aaData": [ ["<input type=\'checkbox\' id=\'mark_x_2\' name=\'mark_x_2\' value=\'1\' onclick=enableActions(\'x\')></input>","<a href=\"/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=3\" data-tooltip-url=\"/index.php?q=civicrm/profile/view&amp;reset=1&amp;gid=7&amp;id=3&amp;snippet=4&amp;is_show_email_task=1\" class=\"crm-summary-link\"><div'
. ' class=\"icon crm-icon Individual-icon\"></div></a>","<a href=/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=3>Anderson, Anthony</a>","$5.00","12345","' . CRM_Utils_Date::customFormat(date('Ymd')) . ' 12:00 AM","' . CRM_Utils_Date::customFormat(date('Ymd')) . ' 12:00 AM",'
$this->assertEquals('{"sEcho": 1, "iTotalRecords": 1, "iTotalDisplayRecords": 1, "aaData": [ ["<input type=\'checkbox\' id=\'mark_x_2\' name=\'mark_x_2\' value=\'1\' onclick=enableActions(\'x\')></input>","<a href=\"/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=3\" data-tooltip-url=\"/index.php?q=civicrm/profile/view&amp;reset=1&amp;gid=7&amp;id=3&amp;snippet=4&amp;is_show_email_task=1\" class=\"crm-summary-link\">'
. '<i class=\"crm-i fa-fw fa-user\" title=\"\"></i></a>","<a href=/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=3>Anderson, Anthony</a>","$5.00","12345","' . CRM_Utils_Date::customFormat(date('Ymd')) . ' 12:00 AM","' . CRM_Utils_Date::customFormat(date('Ymd')) . ' 12:00 AM",'
. '"Credit Card","Completed","Donation","<span><a href=\"/index.php?q=civicrm/contact/view/contribution&amp;reset=1&amp;id=1&amp;cid=3&amp;action=view&amp;context=contribution&amp;'
. 'selectedChild=contribute\" class=\"action-item crm-hover-button\" title=\'View Contribution\' >View</a><a href=\"#\" class=\"action-item crm-hover-button disable-action\" title=\'Assign Transaction\' onclick = \"assignRemove( 2,\'assign\' );\">Assign</a></span>"]] }', $json);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/phpunit/CRM/Member/Selector/SearchTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function testSelectorGetRows(): void {
$this->assertEquals([
'contact_id' => $this->_contactID,
'membership_id' => $membershipID,
'contact_type' => '<a href="/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=' . $this->_contactID . '" data-tooltip-url="/index.php?q=civicrm/profile/view&amp;reset=1&amp;gid=7&amp;id=' . $this->_contactID . '&amp;snippet=4&amp;is_show_email_task=1" class="crm-summary-link"><div class="icon crm-icon Individual-icon"></div></a>',
'contact_type' => '<a href="/index.php?q=civicrm/contact/view&amp;reset=1&amp;cid=' . $this->_contactID . '" data-tooltip-url="/index.php?q=civicrm/profile/view&amp;reset=1&amp;gid=7&amp;id=' . $this->_contactID . '&amp;snippet=4&amp;is_show_email_task=1" class="crm-summary-link"><i class="crm-i fa-fw fa-user" title=""></i></a>',
'sort_name' => 'Anderson, Anthony',
'membership_type' => 'General',
'membership_join_date' => date('Y-m-d'),
Expand Down
Loading

0 comments on commit 77b2699

Please sign in to comment.