Commit 47d49039 authored by KevinLevie's avatar KevinLevie
Browse files

Add support for FinancialAccount, FinancialType, CaseType, Setting

parent 3fba2cfd
......@@ -35,19 +35,22 @@ class CRM_Civiconfig_Config {
public function getSupportedEntityTypes() {
// TODO: make this list configurable.
return array(
'CivicrmSetting',
'ContactType',
'MembershipType',
'RelationshipType',
'OptionGroup',
'Group',
'Tag',
'FinancialAccount',
'FinancialType',
'EventType',
'ActivityType',
'Tag',
'LocationType',
'CaseType',
'CustomGroup',
// customGroup as last one because it might need one of the previous ones (option group, relationship types)
// DO NOT INCLUDE CustomField, because custom fields are updated together
// with custom groups.
// CustomGroup as last one because it might need one of the previous ones (option group, relationship types)
// DO NOT INCLUDE CustomField, because custom fields are updated together with custom groups.
);
}
}
\ No newline at end of file
......@@ -11,7 +11,7 @@ abstract class CRM_Civiconfig_Entity {
/**
* Method to create or update any entity
*
* @param array $params
* @param array $params Parameters
* @return mixed
* @throws Exception when error from API RelationshipType Create
*/
......
<?php
/**
* Class CRM_Civiconfig_Entity_CaseType.
*
* @author Kevin Levie (CiviCooP) <kevin.levie@civicoop.org>
* @license AGPL-3.0
*/
class CRM_Civiconfig_Entity_CaseType extends CRM_Civiconfig_Entity {
protected $_apiParams = array();
/**
* CRM_Civiconfig_Entity_CaseType constructor.
*/
public function __construct() {
$this->_apiParams = array();
}
/**
* Method to validate params passed to create
*
* @param $params
* @throws Exception when required param not found
*/
private function validateCreateParams($params) {
if (!isset($params['name']) || empty($params['name'])) {
throw new \CRM_Civiconfig_EntityException("Missing mandatory parameter 'name' in class " . get_class() . ".");
}
// The line below is in a strange place in the code. But I'll keep it
// there, because it is there as well for every other entity type.
$this->_apiParams = $params;
}
/**
* Method to create or update a case type
*
* @param array $params
* @return mixed
* @throws Exception if an API error occurs
*/
public function create(array $params) {
$this->validateCreateParams($params);
$existing = $this->getWithName($this->_apiParams['name']);
if (isset($existing['id'])) {
$this->_apiParams['id'] = $existing['id'];
}
try {
civicrm_api3('FinancialType', 'Create', $this->_apiParams);
} catch (\CiviCRM_API3_Exception $ex) {
throw new \CRM_Civiconfig_EntityException('Could not create or update financial type with name '.$this->_apiParams['name'] . '. Error from API CaseType.Create: '.$ex->getMessage() . '.');
}
}
/**
* Function to get the case type by name
*
* @param string $name
* @return array|bool
* @access public
* @static
*/
public function getWithName($name) {
try {
return civicrm_api3('CaseType', 'Getsingle',
array('name' => $name));
} catch (\CiviCRM_API3_Exception $ex) {
return FALSE;
}
}
}
\ No newline at end of file
<?php
/**
* Class CRM_Civiconfig_Entity_CivicrmSetting.
* This class adds support for updating CiviCRM system settings that are stored
* in the civicrm_setting table using the Setting.Create API.
*
* @author Kevin Levie (CiviCooP) <kevin.levie@civicoop.org>
* @license AGPL-3.0
*/
class CRM_Civiconfig_Entity_CivicrmSetting extends CRM_Civiconfig_Entity {
protected $_apiParams = [];
/**
* CRM_Civiconfig_Entity_CivicrmSetting constructor.
*/
public function __construct() {
$this->_apiParams = [];
}
/**
* Creates/updates all objects at once.
* Overloading createAll here because we can add all settings in one API call.
*
* @param array $paramsArray
* @return bool Success
* @throws Exception if an API error occurs
*/
public function createAll($paramsArray) {
if (!is_array($paramsArray) || count($paramsArray) == 0) {
return FALSE;
}
$this->_apiParams = $paramsArray;
try {
civicrm_api3('Setting', 'create', $this->_apiParams);
} catch (\CiviCRM_API3_Exception $ex) {
throw new \CRM_Civiconfig_EntityException('Could not create or update CiviCRM settings. Error from API Setting.Create: ' . $ex->getMessage() . '.');
}
return TRUE;
}
/**
* Method to create or update a CiviCRM setting (redirects to createAll above).
*
* @param array $params Parameters
* @return bool Success
*/
public function create(array $params) {
return $this->createAll([$params]);
}
}
\ No newline at end of file
......@@ -124,8 +124,12 @@ class CRM_Civiconfig_Entity_CustomField extends CRM_Civiconfig_Entity {
try {
$existingCustomFields = civicrm_api3('CustomField', 'Get', array('custom_group_id' => $customGroupId));
foreach ($existingCustomFields['values'] as $existingId => $existingField) {
// if existing field not in config custom data, delete custom field
if (!isset($configCustomGroupData['fields'][$existingField['name']])) {
// If existing field not in config custom data, delete custom field
// Fix KL: check field['name'] if the custom groups array doesn't use names as keys
if (
!isset($configCustomGroupData['fields'][$existingField['name']]) &&
!in_array($existingField['name'], array_column($configCustomGroupData['fields'], 'name'))
) {
civicrm_api3('CustomField', 'Delete', array('id' => $existingId));
}
}
......
<?php
/**
* Class CRM_Civiconfig_Entity_FinancialAccount.
*
* @author Kevin Levie (CiviCooP) <kevin.levie@civicoop.org>
* @license AGPL-3.0
*/
class CRM_Civiconfig_Entity_FinancialAccount extends CRM_Civiconfig_Entity {
protected $_apiParams = array();
/**
* CRM_Civiconfig_Entity_FinancialAccount constructor.
*/
public function __construct() {
$this->_apiParams = array();
}
/**
* Method to validate params passed to create
*
* @param $params
* @throws Exception when required param not found
*/
private function validateCreateParams($params) {
if (!isset($params['name']) || empty($params['name'])) {
throw new \CRM_Civiconfig_EntityException("Missing mandatory parameter 'name' in class " . get_class() . ".");
}
// The line below is in a strange place in the code. But I'll keep it
// there, because it is there as well for every other entity type.
$this->_apiParams = $params;
}
/**
* Method to create or update a financial account
*
* @param array $params
* @return mixed
* @throws Exception if an API error occurs
*/
public function create(array $params) {
$this->validateCreateParams($params);
$existing = $this->getWithName($this->_apiParams['name']);
if (isset($existing['id'])) {
$this->_apiParams['id'] = $existing['id'];
}
try {
civicrm_api3('FinancialAccount', 'Create', $this->_apiParams);
} catch (\CiviCRM_API3_Exception $ex) {
throw new \CRM_Civiconfig_EntityException('Could not create or update financial account with name '.$this->_apiParams['name'] . '. Error from API FinancialAccount.Create: '.$ex->getMessage() . '.');
}
}
/**
* Function to get the financial account by name
*
* @param string $name
* @return array|bool
* @access public
* @static
*/
public function getWithName($name) {
try {
return civicrm_api3('FinancialAccount', 'Getsingle',
array('name' => $name));
} catch (\CiviCRM_API3_Exception $ex) {
return FALSE;
}
}
}
\ No newline at end of file
<?php
/**
* Class CRM_Civiconfig_Entity_FinancialType.
*
* @author Kevin Levie (CiviCooP) <kevin.levie@civicoop.org>
* @license AGPL-3.0
*/
class CRM_Civiconfig_Entity_FinancialType extends CRM_Civiconfig_Entity {
protected $_apiParams = array();
/**
* CRM_Civiconfig_Entity_FinancialType constructor.
*/
public function __construct() {
$this->_apiParams = array();
}
/**
* Method to validate params passed to create
*
* @param $params
* @throws Exception when required param not found
*/
private function validateCreateParams($params) {
if (!isset($params['name']) || empty($params['name'])) {
throw new \CRM_Civiconfig_EntityException("Missing mandatory parameter 'name' in class " . get_class() . ".");
}
// The line below is in a strange place in the code. But I'll keep it
// there, because it is there as well for every other entity type.
$this->_apiParams = $params;
}
/**
* Method to create or update a financial type
*
* @param array $params
* @return mixed
* @throws Exception if an API error occurs
*/
public function create(array $params) {
$this->validateCreateParams($params);
$existing = $this->getWithName($this->_apiParams['name']);
if (isset($existing['id'])) {
$this->_apiParams['id'] = $existing['id'];
}
try {
civicrm_api3('FinancialType', 'Create', $this->_apiParams);
} catch (\CiviCRM_API3_Exception $ex) {
throw new \CRM_Civiconfig_EntityException('Could not create or update financial type with name '.$this->_apiParams['name']
.'. Error from API FinancialType.Create: '.$ex->getMessage() . '.');
}
}
/**
* Function to get the financial type by name
*
* @param string $name
* @return array|bool
* @access public
* @static
*/
public function getWithName($name) {
try {
return civicrm_api3('FinancialType', 'Getsingle',
array('name' => $name));
} catch (\CiviCRM_API3_Exception $ex) {
return FALSE;
}
}
}
\ No newline at end of file
......@@ -32,7 +32,8 @@ class CRM_Civiconfig_Entity_Group extends CRM_Civiconfig_Entity {
/**
* Method to create or update group
*
* @param $params
* @param array $params
* @return void
* @throws Exception when error in API Group Create or when missing mandatory param name
* @access public
*/
......
......@@ -57,10 +57,10 @@ class CRM_Civiconfig_Loader {
$entityTypeConfig = new $configClass();
$entityTypeConfig->createAll($params);
$ret[$entityType] = "SUCCESS";
$ret[$entityType] = "{$entityType}: SUCCESS";
} catch (\CRM_Civiconfig_EntityException $e) {
$ret[$entityType] = "ERROR: " . $e->getMessage();
$ret[$entityType] = "{$entityType}: ERROR - " . $e->getMessage();
}
}
......
org.civicoop.configitems
========================
This CiviCRM native extension allows you to define all kinds of configuration items from JSON files, and create or update them in CiviCRM using the *Civiconfig.LoadJson* API method.
This CiviCRM native extension allows you to define all kinds of configuration items from JSON files, and create or update them on any CiviCRM installation using an API call (`Civiconfig.LoadJson`).
Thanks to Emphanos and their customer IIDA for funding the original version of this extension.
### How to use
- Install the latest version of this extension (see [Releases](releases)).
- Create a resources directory where you'll keep your custom JSON configuration files. For instance */sites/default/modules/civicrm_resources* or */wp-content/uploads/civicrm/resources*, or a directory within another (generic) extension. It's a good idea to version your resources directory using Git.
- Call the API method Civiconfig.LoadJson, and pass the full path to your resources directory in the *path* parameter.
- The configuration items will then be added or updated from the JSON files in this directory (or you'll see an error if the parameters or configuration files weren't valid). See below for the items types that are supported.
- Install the latest version of this extension (see [Releases](https://github.com/civicoop/org.civicoop.configitems/releases)).
- Create a resources directory where you'll keep your custom JSON configuration files. For instance */sites/default/modules/civicrm_resources* or */wp-content/uploads/civicrm/resources*, or a directory within another (generic) extension. It's a good idea to version your resources directory using Git.
- Make a copy of your database. Though unlikely, syntax errors in the JSON files or bugs in this extension could cause serious damage to your database.
- Call the API method `Civiconfig.LoadJson`, and pass the full path to your resources directory in the *`path`* parameter. You can do this via the CiviCRM API Explorer - or make an API call using Drush or WP-CLI.
- The configuration items will then be added or updated from the JSON files in this directory. See below for the items types that are supported. The API call will return an array with the loader status for each file - if everything went well you should see `SUCCESS` on every line.
### Configuration support
The extension can work with the following types of items:
- activity types
- contact types
- custom groups with custom fields
- event types
- groups
- membership types
- option groups with option values
- relationship types
- tags
Refer to the example files (that contain config items for IIDA) in the
[resources_examples](resources_examples) directory as a start to create your own configuration files.
For some more explanation on how this works exactly, check
- ContactTypes
- MembershipTypes
- RelationshipTypes
- OptionGroups with OptionValues
- Groups
- Tags
- FinancialAccounts
- FinancialTypes
- EventTypes
- ActivityTypes
- LocationTypes
- CaseTypes
- CustomGroups with CustomFields
- CiviCRM Settings (system settings set through the Setting.API - be especially careful with that!)
You can use the example files in the *[resources_examples](resources_examples)*
directory as a start to create your own config files.
For some more explanation on how this extension works exactly, check
[this blog post on civicrm.org](https://civicrm.org/blog/erikhommel/extension-to-configure-civicrm-items).
### Tips and notes
- The extensions also removes 'old' custom fields from custom groups that are in the JSON files! So if I have a custom group 'test_erik' with custom fields 'test1' and 'test2' in my CiviCRM database and the JSON custom data file has a custom group named 'test_erik' with only the custom field 'test1', running the updater will remove the field 'test2'.
- The extensions also removes 'old' custom fields from custom groups that are in the JSON files! Example: if I have a custom group 'test_erik' with custom fields 'test1' and 'test2' in my database and the JSON file has a custom group named 'test_erik' that only contains custom field 'test1', the updater will remove the field 'test2' (and all data!).
- For backwards compatibility reasons, the API method doesn't require the *path* parameter. If you don't specify a path, the script will check if a */resources* folder exists in the extension's own directory.
- For backwards compatibility reasons, the API method doesn't require the *path* parameter. If you don't specify a path, the script will check if a */resources/* folder exists in the extension's own directory.
- Feeling adventurous? We've made it relatively easy to use a different file format instead of JSON, or fetch the configuration in a different way entirely (database, url, smoke signals, ...). If you want to do this, create your own class that extends CRM_Civiconfig_ParamsProvider to provide config item data, then call the CRM_Civiconfig_Config->updateConfiguration method passing an instance of your custom class.
- Developers: feeling adventurous? We've made it relatively easy to add support for other file formats than JSON, or fetch the configuration in a different way entirely (database, url, smoke signals, ...). To do this, create your own class that extends `CRM_Civiconfig_ParamsProvider` to provide config item data, then call the `CRM_Civiconfig_Config->updateConfiguration` method passing an instance of your custom class.
### Ideas for future improvements
- Add support for more CiviCRM entity types.
For one, support for ProfileGroups/ProfileFields could probably be borrowed from [this extension](https://github.com/catorghans/net.trinfinity.orgis.mi.dataquality).
- Add support to set certain CiviCRM core settings automatically (using the Setting.create API).
- Add support for more CiviCRM entity types. Maybe...
+ ProfileGroup/ProfileField (may be borrowed from [this extension](https://github.com/catorghans/net.trinfinity.orgis.mi.dataquality))
+ Mapping / MappingField
+ UFGroup / UFField
+ MembershipStatus
+ ParticipantStatusType
+ MessageTemplate!
+ ...?
- Add functionality to export config items from an existing CiviCRM installation, for the types of data we support?
- Add functionality to easily export config items from an existing CiviCRM installation, for the types of data we support? (Using the API's JSON output as a starting point and slightly modifying/filtering that)
- Make it possible to use this extension from an admin page (configuring resource path and running the loader) instead of having to call the API?
- Make an admin page that allows setting the resource directory and running a quick and easy import / export function, so users won't have to call the API anymore?
- ...and more? Code contributions and/or funding to improve this extension are of course most welcome!
- ...anything else? Code contributions and/or funding to improve this extension are of course most welcome!
......@@ -46,7 +46,7 @@ function _civicrm_api3_civiconfig_load_json_spec(&$params) {
'path' => [
'required' => 0,
'name' => 'path',
'title' => 'Resource Directory Path (default: org.civicoop.configitems/resources)',
'title' => 'JSON Resources Path',
'type' => \CRM_Utils_Type::T_STRING,
],
];
......
......@@ -14,8 +14,8 @@
<url desc="Support">https://www.civicoop.org</url>
<url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url>
</urls>
<releaseDate>2016-08-12</releaseDate>
<version>1.2.0</version>
<releaseDate>2016-08-15</releaseDate>
<version>1.2.1</version>
<develStage>beta</develStage>
<compatibility>
<ver>4.4</ver>
......
[
{
"id": "1",
"name": "job",
"title": "Job",
"description": "A job, project or service handled through this platform",
"is_active": "1",
"weight": "1",
"definition": {
"activityTypes": [
{
"name": "Assignment Conditions"
}
],
"activitySets": [
{
"name": "standard_timeline",
"label": "Standard Timeline",
"timeline": "1",
"activityTypes": [
{
"name": "Open Case",
"status": "Completed"
}
]
}
],
"caseRoles": [
{
"name": "Job: Person Responsible",
"creator": "1",
"manager": "1"
},
{
"name": "Job: Freelancer",
"manager": "0"
}
]
},
"is_forkable": "1",
"is_forked": ""
}
]
\ No newline at end of file
{
"dateformatDatetime": "%e %B %Y %H:%M",
"dateformatFull": "%e %B %Y",
"dateformatPartial": "%m-%Y",
"dateformatTime": "%H:%M",
"dateformatYear": "%Y",
"dateformatFinancialBatch": "%d-%m-%Y",
"dateInputFormat": "dd-mm-yy",
"timeInputFormat": "2",
"weekBegins": "1"
}
\ No newline at end of file
[
{
"name": "Donation",
"contact_id": "1",
"financial_account_type_id": "Revenue",
"accounting_code": "4200",
"account_type_code": "INC",
"description": "Default account for donations",
"is_deductible": "1",
"is_tax": "0",
"is_reserved": "0",
"is_active": "0",
"is_default": "1"
},
{
"name": "Member Dues",
"contact_id": "1",
"financial_account_type_id": "Revenue",
"accounting_code": "4100",
"account_type_code": "INC",
"description": "Default account for membership sales",
"is_deductible": "1",
"is_tax": "0",
"is_reserved": "0",
"is_active": "1",
"is_default": "0"
},
{
"name": "Campaign Contribution",
"contact_id": "1",
"financial_account_type_id": "Revenue",
"accounting_code": "4400",
"account_type_code": "INC",
"description": "Sample account for recording payments to a campaign",
"is_deductible": "0",
"is_tax": "0",
"is_reserved": "0",
"is_active": "0",
"is_default": "0"
},
{
"name": "Event Fee",
"contact_id": "1",
"financial_account_type_id": "Revenue",
"accounting_code": "4300",
"account_type_code": "INC",
"description": "Default account for event ticket sales",
"is_deductible": "0",
"is_tax": "0",
"is_reserved": "0",
"is_active": "1",
"is_default": "0"
},
{
"name": "Banking Fees",
"contact_id": "1",
"financial_account_type_id": "Expenses",
"accounting_code": "5200",
"account_type_code": "EXP",
"description": "Payment processor fees and manually recorded banking fees",
"is_deductible": "0",
"is_tax": "0",
"is_reserved": "0",
"is_active": "1",
"is_default": "1"
},
{
"name": "Deposit Bank Account",
"contact_id": "1",
"financial_account_type_id": "Asset",
"accounting_code": "1100",
"account_type_code": "BANK",
"description": "All manually recorded cash and cheques go to this account",
"is_deductible": "0",
"is_tax": "0",
"is_reserved": "0",
"is_active": "1",
"is_default": "1"
},
{
"name": "Accounts Receivable",
"contact_id": "1",
"financial_account_type_id": "Asset",
"accounting_code": "1300",
"account_type_code": "AR",
"description": "Amounts to be received later (eg pay later event revenues)",
"is_deductible": "0",