* @return \CRM_Contact_DAO_SavedSearch
*/
public static function create(&$params) {
- // Auto-create unique name from label if supplied
- if (empty($params['id']) && empty($params['name']) && !empty($params['label'])) {
- $name = CRM_Utils_String::munge($params['label']);
- $existing = Civi\Api4\SavedSearch::get(FALSE)
- ->addWhere('name', 'LIKE', $name . '%')
- ->addSelect('name')
- ->execute()->column('name');
- $suffix = '';
- while (in_array($name . $suffix, $existing)) {
- $suffix = '_' . (1 + str_replace('_', '', $suffix));
- }
- $params['name'] = $name . $suffix;
- }
$loggedInContactID = CRM_Core_Session::getLoggedInContactID();
if ($loggedInContactID) {
if (empty($params['id'])) {
$entityName = CRM_Core_DAO_AllCoreTables::getBriefName($className);
\CRM_Utils_Hook::pre($op, $entityName, $record['id'] ?? NULL, $record);
- $instance = new $className();
+ $fields = static::getSupportedFields();
+ $instance = new static();
// Ensure fields exist before attempting to write to them
- $values = array_intersect_key($record, self::getSupportedFields());
+ $values = array_intersect_key($record, $fields);
$instance->copyValues($values);
+ if (empty($values['id']) && array_key_exists('name', $fields) && empty($values['name']) && !empty($values['label'])) {
+ $instance->makeNameFromLabel();
+ }
$instance->save();
if (!empty($record['custom']) && is_array($record['custom'])) {
return static::$_paths ?? [];
}
+ /**
+ * When creating a record without a supplied name,
+ * create a unique, clean name derived from the label.
+ *
+ * Note: this function does nothing unless a unique index exists for "name" column.
+ */
+ private function makeNameFromLabel() {
+ $indexNameWith = NULL;
+ // Look for a unique index which includes the "name" field
+ if (method_exists($this, 'indices')) {
+ foreach ($this->indices(FALSE) as $index) {
+ if (!empty($index['unique']) && in_array('name', $index['field'], TRUE)) {
+ $indexNameWith = $index['field'];
+ }
+ }
+ }
+ if (!$indexNameWith) {
+ // No unique index on "name", do nothing
+ return;
+ }
+ $name = CRM_Utils_String::munge($this->label, '_', 252);
+
+ // Find existing records with the same name
+ $sql = new CRM_Utils_SQL_Select($this::getTableName());
+ $sql->select(['id', 'name']);
+ $sql->where('name LIKE @name', ['@name' => $name . '%']);
+ // Include all fields that are part of the index
+ foreach (array_diff($indexNameWith, ['name']) as $field) {
+ $sql->where("`$field` = @val", ['@val' => $this->$field]);
+ }
+ $query = $sql->toSQL();
+ $existing = self::executeQuery($query)->fetchMap('id', 'name');
+ $dupes = 0;
+ $suffix = '';
+ while (in_array($name . $suffix, $existing)) {
+ $suffix = '_' . (++$dupes);
+ }
+ $this->name = $name . $suffix;
+ }
+
}
* Implements hook_civicrm_pre().
*/
function search_kit_civicrm_pre($op, $entity, $id, &$params) {
- // Supply default name/label when creating new SearchDisplay
- if ($entity === 'SearchDisplay' && $op === 'create') {
- if (empty($params['label'])) {
- $params['label'] = $params['name'];
- }
- elseif (empty($params['name'])) {
- $params['name'] = \CRM_Utils_String::munge($params['label']);
- }
- }
// When deleting a saved search, also delete the displays
// This would happen anyway in sql because of the ON DELETE CASCADE foreign key,
// But this ensures that pre and post hooks are called
<?php
namespace api\v4\SearchDisplay;
+use Civi\Api4\SavedSearch;
use Civi\Api4\SearchDisplay;
use Civi\Test\HeadlessInterface;
use Civi\Test\TransactionalInterface;
$this->assertNull($display['saved_search_id']);
}
+ public function testAutoFormatName() {
+ // Create 3 saved searches; they should all get unique names
+ $savedSearch0 = SavedSearch::create(FALSE)
+ ->addValue('label', 'My test search')
+ ->execute()->first();
+ $savedSearch1 = SavedSearch::create(FALSE)
+ ->addValue('label', 'My test search')
+ ->execute()->first();
+ $savedSearch2 = SavedSearch::create(FALSE)
+ ->addValue('label', 'My test search')
+ ->execute()->first();
+ // Name will be created from munged label
+ $this->assertEquals('My_test_search', $savedSearch0['name']);
+ // Name will have _1, _2, etc. appended to ensure it's unique
+ $this->assertEquals('My_test_search_1', $savedSearch1['name']);
+ $this->assertEquals('My_test_search_2', $savedSearch2['name']);
+
+ $display0 = SearchDisplay::create()
+ ->addValue('saved_search_id', $savedSearch0['id'])
+ ->addValue('label', 'My test display')
+ ->addValue('type', 'table')
+ ->execute()->first();
+ $display1 = SearchDisplay::create()
+ ->addValue('saved_search_id', $savedSearch0['id'])
+ ->addValue('label', 'My test display')
+ ->addValue('type', 'table')
+ ->execute()->first();
+ $display2 = SearchDisplay::create()
+ ->addValue('saved_search_id', $savedSearch1['id'])
+ ->addValue('label', 'My test display')
+ ->addValue('type', 'table')
+ ->execute()->first();
+ // Name will be created from munged label
+ $this->assertEquals('My_test_display', $display0['name']);
+ // Name will have _1 appended to ensure it's unique to savedSearch0
+ $this->assertEquals('My_test_display_1', $display1['name']);
+ // This is for a different saved search so doesn't need a number appended
+ $this->assertEquals('My_test_display', $display2['name']);
+ }
+
}