3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2019
35 * This class is used to retrieve and display a range of
36 * contacts that match the given criteria (specifically for
37 * results of advanced search options.
40 class CRM_Profile_Selector_Listings
extends CRM_Core_Selector_Base
implements CRM_Core_Selector_API
{
43 * Array of supported links, currently view and edit
47 public static $_links = NULL;
50 * We use desc to remind us what that column is, name is used in the tpl
54 public static $_columnHeaders;
57 * The sql params we use to get the list of contacts.
64 * The public visible fields to be shown to the user.
71 * The custom fields for this domain.
75 protected $_customFields;
78 * Cache the query object.
85 * Cache the expanded options list if any.
92 * The group id that we are editing.
99 * Do we enable mapping of users.
106 * Do we enable edit link.
110 protected $_editLink;
113 * Should we link to the UF Profile.
117 protected $_linkToUF;
120 * Store profile ids if multiple profile ids are passed using comma separated.
121 * Currently lets implement this functionality only for dialog mode
124 protected $_profileIds = [];
126 protected $_multiRecordTableName = NULL;
131 * @param array $params the params for the where clause
132 * @param array $customFields
133 * @param array $ufGroupIds
135 * @param bool $editLink
136 * @param bool $linkToUF
138 * @return \CRM_Profile_Selector_Listings
140 public function __construct(
148 $this->_params
= $params;
150 if (is_array($ufGroupIds)) {
151 $this->_profileIds
= $ufGroupIds;
152 $this->_gid
= $ufGroupIds[0];
155 $this->_profileIds
= [$ufGroupIds];
156 $this->_gid
= $ufGroupIds;
160 $this->_editLink
= $editLink;
161 $this->_linkToUF
= $linkToUF;
163 //get the details of the uf group
165 $groupId = CRM_Core_DAO
::getFieldValue('CRM_Core_BAO_UFGroup',
166 $this->_gid
, 'limit_listings_group_id'
170 // add group id to params if a uf group belong to a any group
172 if (!empty($this->_params
['group'])) {
173 $this->_params
['group'][$groupId] = 1;
176 $this->_params
['group'] = [$groupId => 1];
180 $this->_fields
= CRM_Core_BAO_UFGroup
::getListingFields(CRM_Core_Action
::VIEW
,
181 CRM_Core_BAO_UFGroup
::PUBLIC_VISIBILITY |
182 CRM_Core_BAO_UFGroup
::LISTINGS_VISIBILITY
,
183 FALSE, $this->_profileIds
186 $this->_customFields
= &$customFields;
188 $returnProperties = CRM_Contact_BAO_Contact
::makeHierReturnProperties($this->_fields
);
189 $returnProperties['contact_type'] = 1;
190 $returnProperties['contact_sub_type'] = 1;
191 $returnProperties['sort_name'] = 1;
193 $queryParams = CRM_Contact_BAO_Query
::convertFormValues($this->_params
, 1);
194 $this->_query
= new CRM_Contact_BAO_Query($queryParams, $returnProperties, $this->_fields
);
196 //the below is done for query building for multirecord custom field listing
197 //to show all the custom field multi valued records of a particular contact
198 $this->setMultiRecordTableName($this->_fields
);
200 $this->_options
= &$this->_query
->_options
;
204 * This method returns the links that are given for each search row.
207 * @param bool $editLink
208 * @param bool $ufLink
213 public static function &links($map = FALSE, $editLink = FALSE, $ufLink = FALSE, $gids = NULL) {
214 if (!self
::$_links) {
217 $viewPermission = TRUE;
219 // check view permission for each profile id, in case multiple profile ids are rendered
220 // then view action is disabled if any profile returns false
221 foreach ($gids as $profileId) {
222 $viewPermission = CRM_Core_Permission
::ufGroupValid($profileId, CRM_Core_Permission
::VIEW
);
223 if (!$viewPermission) {
229 if ($viewPermission) {
230 self
::$_links[CRM_Core_Action
::VIEW
] = [
231 'name' => ts('View'),
232 'url' => 'civicrm/profile/view',
233 'qs' => 'reset=1&id=%%id%%&gid=%%gid%%',
234 'title' => ts('View Profile Details'),
239 self
::$_links[CRM_Core_Action
::UPDATE
] = [
240 'name' => ts('Edit'),
241 'url' => 'civicrm/profile/edit',
242 'qs' => 'reset=1&id=%%id%%&gid=%%gid%%',
243 'title' => ts('Edit'),
248 self
::$_links[CRM_Core_Action
::PROFILE
] = [
249 'name' => ts('Website Profile'),
250 'url' => 'user/%%ufID%%',
252 'title' => ts('View Website Profile'),
257 self
::$_links[CRM_Core_Action
::MAP
] = [
259 'url' => 'civicrm/profile/map',
260 'qs' => 'reset=1&cid=%%id%%&gid=%%gid%%',
261 'title' => ts('Map'),
265 return self
::$_links;
269 * Getter for array of the parameters required for creating pager.
272 * @param array $params
274 public function getPagerParams($action, &$params) {
275 $status = CRM_Utils_System
::isNull($this->_multiRecordTableName
) ?
ts('Contact %%StatusMessage%%') : ts('Contact Multi Records %%StatusMessage%%');
276 $params['status'] = $status;
277 $params['csvString'] = NULL;
278 $params['rowCount'] = CRM_Utils_Pager
::ROWCOUNT
;
280 $params['buttonTop'] = 'PagerTopButton';
281 $params['buttonBottom'] = 'PagerBottomButton';
285 * Returns the column headers as an array of tuples:
286 * (name, sortName (key to the sort array))
288 * @param string $action
289 * The action being performed.
290 * @param string $output
291 * What should the result set include (web/email/csv).
294 * the column headers that need to be displayed
296 public function &getColumnHeaders($action = NULL, $output = NULL) {
297 static $skipFields = ['group', 'tag'];
298 $multipleFields = ['url'];
299 $direction = CRM_Utils_Sort
::ASCENDING
;
301 if (!isset(self
::$_columnHeaders)) {
302 self
::$_columnHeaders = [
305 'name' => ts('Name'),
306 'sort' => 'sort_name',
307 'direction' => CRM_Utils_Sort
::ASCENDING
,
308 'field_name' => 'sort_name',
312 $locationTypes = CRM_Core_PseudoConstant
::get('CRM_Core_DAO_Address', 'location_type_id');
314 foreach ($this->_fields
as $name => $field) {
315 // skip pseudo fields
316 if (substr($name, 0, 9) == 'phone_ext') {
320 if (!empty($field['in_selector']) &&
321 !in_array($name, $skipFields)
324 if (strpos($name, '-') !== FALSE) {
325 $value = explode('-', $name);
326 $fieldName = CRM_Utils_Array
::value(0, $value);
327 $lType = CRM_Utils_Array
::value(1, $value);
328 $type = CRM_Utils_Array
::value(2, $value);
330 if (!in_array($fieldName, $multipleFields)) {
331 if ($lType == 'Primary') {
332 $locationTypeName = 1;
335 $locationTypeName = $locationTypes[$lType];
338 if (in_array($fieldName, [
344 $name = "`$locationTypeName-$fieldName-$type`";
347 $name = "`$locationTypeName-$fieldName`";
351 $name = "`$locationTypeName-$fieldName`";
355 $name = "website-{$lType}-{$fieldName}";
359 self
::$_columnHeaders[] = [
360 'name' => $field['title'],
362 'direction' => $direction,
363 'field_name' => CRM_Core_BAO_UFField
::isValidFieldName($name) ?
$name : $fieldName,
366 $direction = CRM_Utils_Sort
::DONTCARE
;
371 // if we don't have any valid columns, don't add the implicit ones
372 // this allows the template to check on emptiness of column headers
374 self
::$_columnHeaders = [];
377 self
::$_columnHeaders[] = ['desc' => ts('Actions')];
380 return self
::$_columnHeaders;
384 * Returns total number of rows for the query.
389 * Total number of rows
391 public function getTotalCount($action) {
392 $additionalWhereClause = 'contact_a.is_deleted = 0';
393 $additionalFromClause = NULL;
396 if ($this->_multiRecordTableName
&&
397 !array_key_exists($this->_multiRecordTableName
, $this->_query
->_whereTables
)
399 $additionalFromClause = CRM_Utils_Array
::value($this->_multiRecordTableName
, $this->_query
->_tables
);
403 $countVal = $this->_query
->searchQuery(0, 0, NULL, TRUE, NULL, NULL, NULL,
404 $returnQuery, $additionalWhereClause, NULL, $additionalFromClause
412 $sql = preg_replace('/DISTINCT/', '', $countVal);
413 return CRM_Core_DAO
::singleValueQuery($sql);
418 * Return the qill for this selector.
422 public function getQill() {
423 return $this->_query
->qill();
427 * Returns all the rows in the given offset and rowCount.
429 * @param string $action
430 * The action being performed.
432 * The row number to start from.
433 * @param int $rowCount
434 * The number of rows to return.
435 * @param string $sort
436 * The sql string that describes the sort order.
437 * @param string $output
438 * What should the result set include (web/email/csv).
440 * @param string $extraWhereClause
443 * the total number of rows for this action
445 public function &getRows($action, $offset, $rowCount, $sort, $output = NULL, $extraWhereClause = NULL) {
447 $multipleFields = ['url'];
448 //$sort object processing for location fields
450 $vars = $sort->_vars
;
452 foreach ($vars as $key => $field) {
453 $field = $vars[$key];
454 $fieldArray = explode('-', $field['name']);
455 $fieldType = CRM_Utils_Array
::value('2', $fieldArray);
456 if (is_numeric(CRM_Utils_Array
::value('1', $fieldArray))) {
457 if (!in_array($fieldType, $multipleFields)) {
458 $locationType = new CRM_Core_DAO_LocationType();
459 $locationType->id
= $fieldArray[1];
460 $locationType->find(TRUE);
461 if ($fieldArray[0] == 'email' ||
$fieldArray[0] == 'im' ||
$fieldArray[0] == 'phone') {
462 $field['name'] = "`" . $locationType->name
. "-" . $fieldArray[0] . "-1`";
465 $field['name'] = "`" . $locationType->name
. "-" . $fieldArray[0] . "`";
469 $field['name'] = "`website-" . $fieldArray[1] . "-{$fieldType}`";
472 $varArray[$key] = $field;
474 $sort->_vars
= $varArray;
477 $additionalWhereClause = 'contact_a.is_deleted = 0';
479 if ($extraWhereClause) {
480 $additionalWhereClause .= " AND {$extraWhereClause}";
484 if ($this->_multiRecordTableName
) {
487 $this->_query
->_useGroupBy
= TRUE;
488 $result = $this->_query
->searchQuery($offset, $rowCount, $sort, NULL, NULL,
489 NULL, NULL, $returnQuery, $additionalWhereClause
493 $resQuery = preg_replace('/GROUP BY contact_a.id[\s]+ORDER BY/', ' ORDER BY', $result);
494 $result = CRM_Core_DAO
::executeQuery($resQuery);
497 // process the result of the query
500 // check if edit is configured in profile settings
502 $editLink = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_UFGroup', $this->_gid
, 'is_edit_link');
505 //FIXME : make sure to handle delete separately. CRM-4418
506 $mask = CRM_Core_Action
::mask([CRM_Core_Permission
::getPermission()]);
507 if ($editLink && ($mask & CRM_Core_Permission
::EDIT
)) {
508 // do not allow edit for anon users in joomla frontend, CRM-4668
509 $config = CRM_Core_Config
::singleton();
510 if (!$config->userFrameworkFrontend || CRM_Core_Session
::singleton()->getLoggedInContactID()) {
511 $this->_editLink
= TRUE;
514 $links = self
::links($this->_map
, $this->_editLink
, $this->_linkToUF
, $this->_profileIds
);
516 $locationTypes = CRM_Core_PseudoConstant
::get('CRM_Core_DAO_Address', 'location_type_id');
519 static $skipFields = ['group', 'tag'];
521 foreach ($this->_fields
as $key => $field) {
522 // skip pseudo fields
523 if (substr($key, 0, 9) == 'phone_ext') {
527 if (!empty($field['in_selector']) &&
528 !in_array($key, $skipFields)
530 if (strpos($key, '-') !== FALSE) {
531 $value = explode('-', $key);
532 $fieldName = CRM_Utils_Array
::value(0, $value);
533 $id = CRM_Utils_Array
::value(1, $value);
534 $type = CRM_Utils_Array
::value(2, $value);
536 if (!in_array($fieldName, $multipleFields)) {
537 $locationTypeName = NULL;
538 if (is_numeric($id)) {
539 $locationTypeName = CRM_Utils_Array
::value($id, $locationTypes);
542 if ($id == 'Primary') {
543 $locationTypeName = 1;
547 if (!$locationTypeName) {
550 $locationTypeName = str_replace(' ', '_', $locationTypeName);
551 if (in_array($fieldName, [
557 $names[] = "{$locationTypeName}-{$fieldName}-{$type}";
560 $names[] = "{$locationTypeName}-{$fieldName}";
564 $names[] = "{$locationTypeName}-{$fieldName}";
568 $names[] = "website-{$id}-{$fieldName}";
571 elseif ($field['name'] == 'id') {
572 $names[] = 'contact_id';
575 $names[] = $field['name'];
580 $multipleSelectFields = ['preferred_communication_method' => 1];
581 $multiRecordTableId = NULL;
582 if ($this->_multiRecordTableName
) {
583 $multiRecordTableId = "{$this->_multiRecordTableName}_id";
586 // we need to determine of overlay profile should be shown
587 $showProfileOverlay = CRM_Core_BAO_UFGroup
::showOverlayProfile();
589 while ($result->fetch()) {
590 $this->_query
->convertToPseudoNames($result);
592 if (isset($result->country
)) {
593 // the query returns the untranslated country name
594 $i18n = CRM_Core_I18n
::singleton();
595 $result->country
= $i18n->translate($result->country
);
599 $row[] = CRM_Contact_BAO_Contact_Utils
::getImage($result->contact_sub_type ?
$result->contact_sub_type
: $result->contact_type
,
604 if ($result->sort_name
) {
605 $row[] = $result->sort_name
;
612 foreach ($names as $name) {
613 if ($cfID = CRM_Core_BAO_CustomField
::getKeyID($name)) {
614 $row[] = CRM_Core_BAO_CustomField
::displayValue($result->$name,
619 elseif (substr($name, -4) == '-url' &&
620 !empty($result->$name)
622 $url = CRM_Utils_System
::fixURL($result->$name);
623 $typeId = substr($name, 0, -4) . "-website_type_id";
624 $typeName = CRM_Core_PseudoConstant
::getLabel('CRM_Core_DAO_Website', 'website_type_id', $result->$typeId);
626 $row[] = "<a href=\"$url\">{$result->$name} (${typeName})</a>";
629 $row[] = "<a href=\"$url\">{$result->$name}</a>";
632 elseif ($name == 'preferred_language') {
633 $row[] = CRM_Core_PseudoConstant
::getLabel('CRM_Contact_DAO_Contact', 'preferred_language', $result->$name);
635 elseif ($multipleSelectFields &&
636 array_key_exists($name, $multipleSelectFields)
638 $paramsNew = [$name => $result->$name];
639 $name = [$name => ['newName' => $name, 'groupName' => $name]];
641 CRM_Core_OptionGroup
::lookupValues($paramsNew, $name, FALSE);
642 $row[] = $paramsNew[$key];
644 elseif (strpos($name, '-im')) {
645 if (!empty($result->$name)) {
646 $providerId = $name . "-provider_id";
647 $providerName = CRM_Core_PseudoConstant
::getLabel('CRM_Core_DAO_IM', 'provider_id', $result->$providerId);
648 $row[] = $result->$name . " ({$providerName})";
654 elseif (strpos($name, '-phone-')) {
655 $phoneExtField = str_replace('phone', 'phone_ext', $name);
656 if (isset($result->$phoneExtField)) {
657 $row[] = $result->$name . " (" . $result->$phoneExtField . ")";
660 $row[] = $result->$name;
663 elseif (in_array($name, [
668 $dname = $name . '_display';
669 $row[] = $result->$dname;
671 elseif (in_array($name, [
675 $row[] = CRM_Utils_Date
::customFormat($result->$name);
677 elseif (isset($result->$name)) {
678 $row[] = $result->$name;
684 if (!empty($result->$name)) {
691 'id' => $result->contact_id
,
692 'gid' => implode(',', $this->_profileIds
),
695 // pass record id param to view url for multi record view
696 if ($multiRecordTableId && $newLinks) {
697 if ($result->$multiRecordTableId) {
698 if ($newLinks[CRM_Core_Action
::VIEW
]['url'] == 'civicrm/profile/view') {
699 $newLinks[CRM_Core_Action
::VIEW
]['qs'] .= "&multiRecord=view&recordId=%%recordId%%&allFields=1";
700 $params['recordId'] = $result->$multiRecordTableId;
705 if ($this->_linkToUF
) {
706 $ufID = CRM_Core_BAO_UFMatch
::getUFId($result->contact_id
);
708 unset($newLinks[CRM_Core_Action
::PROFILE
]);
711 $params['ufID'] = $ufID;
715 $row[] = CRM_Core_Action
::formLink($newLinks,
720 'profile.selector.row',
733 * Name of export file.
735 * @param string $output
741 public function getExportFileName($output = 'csv') {
742 return ts('CiviCRM Profile Listings');
746 * Set the _multiRecordTableName to display the result set.
748 * (according to multi record custom field values).
750 * @param array $fields
752 public function setMultiRecordTableName($fields) {
753 $customGroupId = $multiRecordTableName = NULL;
754 $selectorSet = FALSE;
756 foreach ($fields as $field => $properties) {
757 if (!CRM_Core_BAO_CustomField
::getKeyID($field)) {
760 if ($cgId = CRM_Core_BAO_CustomField
::isMultiRecordField($field)) {
761 $customGroupId = CRM_Utils_System
::isNull($customGroupId) ?
$cgId : $customGroupId;
763 //if the field is submitted set multiRecordTableName
764 if ($customGroupId) {
765 $isSubmitted = FALSE;
766 foreach ($this->_query
->_params
as $key => $value) {
767 //check the query params 'where' element
768 if ($value[0] == $field) {
775 $this->_multiRecordTableName
776 = $multiRecordTableName = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'table_name');
777 if ($multiRecordTableName) {
782 if (!empty($properties['in_selector'])) {
789 if (!isset($customGroupId) ||
!$customGroupId) {
793 //if the field is in selector and not a searchable field
794 //get the proper custom value table name
796 $this->_multiRecordTableName
797 = $multiRecordTableName = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'table_name');