Skip to content

Commit

Permalink
Merge pull request #10754 from totten/master-actcase-ts
Browse files Browse the repository at this point in the history
CRM-20958 - Track creation+modification times for activities+cases
  • Loading branch information
colemanw authored Sep 6, 2017
2 parents ff8784d + ed30b24 commit 27b55ab
Show file tree
Hide file tree
Showing 14 changed files with 759 additions and 79 deletions.
4 changes: 4 additions & 0 deletions CRM/Activity/BAO/Activity.php
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ public static function deleteActivityContact($activityId, $recordTypeID = NULL)
* @return CRM_Activity_BAO_Activity|null|object
*/
public static function create(&$params) {
// CRM-20958 - These fields are managed by MySQL triggers. Watch out for clients resaving stale timestamps.
unset($params['created_date']);
unset($params['modified_date']);

// check required params
if (!self::dataExists($params)) {
throw new CRM_Core_Exception('Not enough data to create activity object');
Expand Down
46 changes: 45 additions & 1 deletion CRM/Activity/DAO/Activity.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*
* Generated from xml/schema/CRM/Activity/Activity.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
* (GenCodeChecksum:a530f1fb1a27c5a15b5d138732b4c581)
* (GenCodeChecksum:dfa63754ef6ea1a9c7148e735dd6ff8a)
*/
require_once 'CRM/Core/DAO.php';
require_once 'CRM/Utils/Type.php';
Expand Down Expand Up @@ -195,6 +195,18 @@ class CRM_Activity_DAO_Activity extends CRM_Core_DAO {
* @var boolean
*/
public $is_star;
/**
* When was the activity was created.
*
* @var timestamp
*/
public $created_date;
/**
* When was the activity (or closely related entity) was created or modified or deleted.
*
* @var timestamp
*/
public $modified_date;
/**
* Class constructor.
*/
Expand Down Expand Up @@ -642,6 +654,38 @@ static function &fields() {
'bao' => 'CRM_Activity_BAO_Activity',
'localizable' => 0,
) ,
'activity_created_date' => array(
'name' => 'created_date',
'type' => CRM_Utils_Type::T_TIMESTAMP,
'title' => ts('Created Date') ,
'description' => 'When was the activity was created.',
'required' => false,
'export' => true,
'where' => 'civicrm_activity.created_date',
'headerPattern' => '',
'dataPattern' => '',
'default' => 'NULL',
'table_name' => 'civicrm_activity',
'entity' => 'Activity',
'bao' => 'CRM_Activity_BAO_Activity',
'localizable' => 0,
) ,
'activity_modified_date' => array(
'name' => 'modified_date',
'type' => CRM_Utils_Type::T_TIMESTAMP,
'title' => ts('Modified Date') ,
'description' => 'When was the activity (or closely related entity) was created or modified or deleted.',
'required' => false,
'export' => true,
'where' => 'civicrm_activity.modified_date',
'headerPattern' => '',
'dataPattern' => '',
'default' => 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
'table_name' => 'civicrm_activity',
'entity' => 'Activity',
'bao' => 'CRM_Activity_BAO_Activity',
'localizable' => 0,
) ,
);
CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
}
Expand Down
4 changes: 4 additions & 0 deletions CRM/Case/BAO/Case.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ public static function add(&$params) {
* @return CRM_Case_BAO_Case
*/
public static function &create(&$params) {
// CRM-20958 - These fields are managed by MySQL triggers. Watch out for clients resaving stale timestamps.
unset($params['created_date']);
unset($params['modified_date']);

$transaction = new CRM_Core_Transaction();

if (!empty($params['id'])) {
Expand Down
46 changes: 45 additions & 1 deletion CRM/Case/DAO/Case.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*
* Generated from xml/schema/CRM/Case/Case.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
* (GenCodeChecksum:e45e7e2a53a945c4659cf393410a9d7a)
* (GenCodeChecksum:2a046fd795b19790f45c5d9dde06a538)
*/
require_once 'CRM/Core/DAO.php';
require_once 'CRM/Utils/Type.php';
Expand Down Expand Up @@ -97,6 +97,18 @@ class CRM_Case_DAO_Case extends CRM_Core_DAO {
* @var boolean
*/
public $is_deleted;
/**
* When was the case was created.
*
* @var timestamp
*/
public $created_date;
/**
* When was the case (or closely related entity) was created or modified or deleted.
*
* @var timestamp
*/
public $modified_date;
/**
* Class constructor.
*/
Expand Down Expand Up @@ -275,6 +287,38 @@ static function &fields() {
'bao' => 'CRM_Case_BAO_Case',
'localizable' => 0,
) ,
'case_created_date' => array(
'name' => 'created_date',
'type' => CRM_Utils_Type::T_TIMESTAMP,
'title' => ts('Created Date') ,
'description' => 'When was the case was created.',
'required' => false,
'export' => true,
'where' => 'civicrm_case.created_date',
'headerPattern' => '',
'dataPattern' => '',
'default' => 'NULL',
'table_name' => 'civicrm_case',
'entity' => 'Case',
'bao' => 'CRM_Case_BAO_Case',
'localizable' => 0,
) ,
'case_modified_date' => array(
'name' => 'modified_date',
'type' => CRM_Utils_Type::T_TIMESTAMP,
'title' => ts('Modified Date') ,
'description' => 'When was the case (or closely related entity) was created or modified or deleted.',
'required' => false,
'export' => true,
'where' => 'civicrm_case.modified_date',
'headerPattern' => '',
'dataPattern' => '',
'default' => 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
'table_name' => 'civicrm_case',
'entity' => 'Case',
'bao' => 'CRM_Case_BAO_Case',
'localizable' => 0,
) ,
);
CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
}
Expand Down
87 changes: 11 additions & 76 deletions CRM/Contact/BAO/Contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -3277,51 +3277,6 @@ public static function getTimestamps($contactId) {
}
}

/**
* Generate triggers to update the timestamp.
*
* The corresponding civicrm_contact row is updated on insert/update/delete
* to a table that extends civicrm_contact.
* Don't regenerate triggers for all such tables if only asked for one table.
*
* @param array $info
* Reference to the array where generated trigger information is being stored
* @param string|null $reqTableName
* Name of the table for which triggers are being generated, or NULL if all tables
* @param array $relatedTableNames
* Array of all core or all custom table names extending civicrm_contact
* @param string $contactRefColumn
* 'contact_id' if processing core tables, 'entity_id' if processing custom tables
*
* @link https://issues.civicrm.org/jira/browse/CRM-15602
* @see triggerInfo
*/
public static function generateTimestampTriggers(&$info, $reqTableName, $relatedTableNames, $contactRefColumn) {
// Safety
$contactRefColumn = CRM_Core_DAO::escapeString($contactRefColumn);
// If specific related table requested, just process that one
if (in_array($reqTableName, $relatedTableNames)) {
$relatedTableNames = array($reqTableName);
}

// If no specific table requested (include all related tables),
// or a specific related table requested (as matched above)
if (empty($reqTableName) || in_array($reqTableName, $relatedTableNames)) {
$info[] = array(
'table' => $relatedTableNames,
'when' => 'AFTER',
'event' => array('INSERT', 'UPDATE'),
'sql' => "\nUPDATE civicrm_contact SET modified_date = CURRENT_TIMESTAMP WHERE id = NEW.$contactRefColumn;\n",
);
$info[] = array(
'table' => $relatedTableNames,
'when' => 'AFTER',
'event' => array('DELETE'),
'sql' => "\nUPDATE civicrm_contact SET modified_date = CURRENT_TIMESTAMP WHERE id = OLD.$contactRefColumn;\n",
);
}
}

/**
* Get a list of triggers for the contact table.
*
Expand All @@ -3343,37 +3298,17 @@ public static function triggerInfo(&$info, $tableName = NULL) {
}
}

// Update timestamp for civicrm_contact itself
if ($tableName == NULL || $tableName == self::getTableName()) {
$info[] = array(
'table' => array(self::getTableName()),
'when' => 'BEFORE',
'event' => array('INSERT'),
'sql' => "\nSET NEW.created_date = CURRENT_TIMESTAMP;\n",
);
}

// Update timestamp when modifying closely related core tables
$relatedTables = array(
'civicrm_address',
'civicrm_email',
'civicrm_im',
'civicrm_phone',
'civicrm_website',
);
self::generateTimestampTriggers($info, $tableName, $relatedTables, 'contact_id');

// Update timestamp when modifying related custom-data tables
$customGroupTables = array();
$customGroupDAO = CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity('Contact');
$customGroupDAO->is_multiple = 0;
$customGroupDAO->find();
while ($customGroupDAO->fetch()) {
$customGroupTables[] = $customGroupDAO->table_name;
}
if (!empty($customGroupTables)) {
self::generateTimestampTriggers($info, $tableName, $customGroupTables, 'entity_id');
}
// Modifications to these records should update the contact timestamps.
\Civi\Core\SqlTrigger\TimestampTriggers::create('civicrm_contact', 'Contact')
->setRelations(array(
array('table' => 'civicrm_address', 'column' => 'contact_id'),
array('table' => 'civicrm_email', 'column' => 'contact_id'),
array('table' => 'civicrm_im', 'column' => 'contact_id'),
array('table' => 'civicrm_phone', 'column' => 'contact_id'),
array('table' => 'civicrm_website', 'column' => 'contact_id'),
)
)
->alterTriggerInfo($info, $tableName);

// Update phone table to populate phone_numeric field
if (!$tableName || $tableName == 'civicrm_phone') {
Expand Down
11 changes: 11 additions & 0 deletions CRM/Upgrade/Incremental/php/FourSeven.php
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,17 @@ public function upgrade_4_7_25($rev) {
'civicrm_uf_group', 'cancel_button_text', "varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Custom Text to display on the cancel button when used in create or edit mode'", TRUE);
$this->addTask('Add Submit button text column to civicrm_uf_group', 'addColumn',
'civicrm_uf_group', 'submit_button_text', "varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Custom Text to display on the submit button on profile edit/create screens'", TRUE);

$this->addTask('CRM-20958 - Add created_date to civicrm_activity', 'addColumn',
'civicrm_activity', 'created_date', "timestamp NULL DEFAULT NULL COMMENT 'When was the activity was created.'");
$this->addTask('CRM-20958 - Add modified_date to civicrm_activity', 'addColumn',
'civicrm_activity', 'modified_date', "timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'When was the activity (or closely related entity) was created or modified or deleted.'");
$this->addTask('CRM-20958 - Add created_date to civicrm_case', 'addColumn',
'civicrm_case', 'created_date', "timestamp NULL DEFAULT NULL COMMENT 'When was the case was created.'");
$this->addTask('CRM-20958 - Add modified_date to civicrm_case', 'addColumn',
'civicrm_case', 'modified_date', "timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'When was the case (or closely related entity) was created or modified or deleted.'");

return TRUE;
}


Expand Down
46 changes: 46 additions & 0 deletions CRM/Utils/Check/Component/Case.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
*/
class CRM_Utils_Check_Component_Case extends CRM_Utils_Check_Component {

const DOCTOR_WHEN = 'https://github.com/civicrm/org.civicrm.doctorwhen';

/**
* @var CRM_Case_XMLRepository
*/
Expand Down Expand Up @@ -116,4 +118,48 @@ public function checkCaseTypeNameConsistency() {
return $messages;
}

/**
* Check that the timestamp columns are populated. (CRM-20958)
*
* @return array<CRM_Utils_Check_Message>
* An empty array, or a list of warnings
*/
public function checkNullTimestamps() {
$messages = array();

$nullCount = 0;
$nullCount += CRM_Utils_SQL_Select::from('civicrm_activity')
->where('created_date IS NULL OR modified_date IS NULL')
->select('COUNT(*)')
->execute()
->fetchValue();
$nullCount += CRM_Utils_SQL_Select::from('civicrm_case')
->where('created_date IS NULL OR modified_date IS NULL')
->select('COUNT(*)')
->execute()
->fetchValue();

if ($nullCount > 0) {
$messages[] = new CRM_Utils_Check_Message(
__FUNCTION__,
'<p>' .
ts('The tables "<em>civicrm_activity</em>" and "<em>civicrm_case</em>" were updated to support two new fields, "<em>created_date</em>" and "<em>modified_date</em>". For historical data, these fields may appear blank. (%1 records have NULL timestamps.)', array(
1 => $nullCount,
)) .
'</p><p>' .
ts('At time of writing, this is not a problem. However, future extensions and improvements could rely on these fields, so it may be useful to back-fill them.') .
'</p><p>' .
ts('For further discussion, please visit %1', array(
1 => sprintf('<a href="%s" target="_blank">%s</a>', self::DOCTOR_WHEN, self::DOCTOR_WHEN),
)) .
'</p>',
ts('Timestamps for Activities and Cases'),
\Psr\Log\LogLevel::NOTICE,
'fa-clock-o'
);
}

return $messages;
}

}
33 changes: 33 additions & 0 deletions Civi/Core/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,39 @@ public function createContainer() {
->setFactory(array($class, 'singleton'));
}

$container->setDefinition('civi.activity.triggers', new Definition(
'Civi\Core\SqlTrigger\TimestampTriggers',
array('civicrm_activity', 'Activity')
))->addTag('kernel.event_listener', array('event' => 'hook_civicrm_triggerInfo', 'method' => 'onTriggerInfo'));

$container->setDefinition('civi.case.triggers', new Definition(
'Civi\Core\SqlTrigger\TimestampTriggers',
array('civicrm_case', 'Case')
))->addTag('kernel.event_listener', array('event' => 'hook_civicrm_triggerInfo', 'method' => 'onTriggerInfo'));

$container->setDefinition('civi.case.staticTriggers', new Definition(
'Civi\Core\SqlTrigger\StaticTriggers',
array(
array(
array(
'upgrade_check' => array('table' => 'civicrm_case', 'column' => 'modified_date'),
'table' => 'civicrm_case_activity',
'when' => 'AFTER',
'event' => array('INSERT'),
'sql' => "\nUPDATE civicrm_case SET modified_date = CURRENT_TIMESTAMP WHERE id = NEW.case_id;\n",
),
array(
'upgrade_check' => array('table' => 'civicrm_case', 'column' => 'modified_date'),
'table' => 'civicrm_activity',
'when' => 'BEFORE',
'event' => array('UPDATE', 'DELETE'),
'sql' => "\nUPDATE civicrm_case SET modified_date = CURRENT_TIMESTAMP WHERE id IN (SELECT ca.case_id FROM civicrm_case_activity ca WHERE ca.activity_id = OLD.id);\n",
),
),
)
))
->addTag('kernel.event_listener', array('event' => 'hook_civicrm_triggerInfo', 'method' => 'onTriggerInfo'));

$container->setDefinition('civi_token_compat', new Definition(
'Civi\Token\TokenCompatSubscriber',
array()
Expand Down
Loading

0 comments on commit 27b55ab

Please sign in to comment.