summaryrefslogtreecommitdiff
path: root/www/crm/wp-content/plugins/civicrm/civicrm/ext
diff options
context:
space:
mode:
Diffstat (limited to 'www/crm/wp-content/plugins/civicrm/civicrm/ext')
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/.gitignore74
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Page/AJAX.php66
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Page/Api4Explorer.php24
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Upgrader.php158
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Upgrader/Base.php375
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ACL.php19
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Address/Create.php43
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Address/Update.php43
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contact/Create.php22
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contact/GetFields.php19
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contribution/Create.php18
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Create.php11
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Delete.php11
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Get.php11
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/GetActions.php11
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/GetFields.php34
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Replace.php11
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Update.php11
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Entity/Get.php102
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Entity/GetLinks.php51
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GetActions.php101
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GroupContact/Create.php38
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GroupContact/Update.php38
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Navigation/Get.php19
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Participant/Get.php18
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ActionSchedule.php18
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Activity.php20
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ActivityContact.php16
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Address.php32
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Contact.php39
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ContactType.php18
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Contribution.php19
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomField.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomGroup.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomValue.php79
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Email.php16
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Entity.php48
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Events.php25
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/GetSpecEvent.php35
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/PostSelectQueryEvent.php64
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/SchemaMapBuildEvent.php39
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/AbstractPrepareSubscriber.php24
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivityPreCreationSubscriber.php40
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivitySchemaMapSubscriber.php40
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php54
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomFieldPreCreationSubscriber.php91
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomGroupPreCreationSubscriber.php29
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/OptionValuePreCreationSubscriber.php50
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PermissionCheckSubscriber.php65
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php369
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PreCreationSubscriber.php50
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php97
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractAction.php391
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractBatchAction.php64
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractCreateAction.php56
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractEntity.php88
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractGetAction.php23
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractQueryAction.php105
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractUpdateAction.php45
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicBatchAction.php72
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicCreateAction.php64
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicGetAction.php144
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicGetFieldsAction.php120
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicReplaceAction.php81
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicUpdateAction.php67
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOCreateAction.php58
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAODeleteAction.php73
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOEntity.php52
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOGetAction.php21
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOGetFieldsAction.php53
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOUpdateAction.php31
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Result.php105
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php197
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/CustomValueActionTrait.php83
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/DAOActionTrait.php229
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Group.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/GroupContact.php29
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/IM.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Navigation.php19
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Note.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OpenID.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OptionGroup.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OptionValue.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Participant.php19
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Phone.php16
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Provider/ActionObjectProvider.php155
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Query/Api4SelectQuery.php535
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Relationship.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/RelationshipType.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/ActivityToActivityContactAssigneesJoinable.php40
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/BridgeJoinable.php23
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/CustomGroupJoinable.php71
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/Joinable.php277
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/OptionValueJoinable.php61
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joiner.php98
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/SchemaMap.php140
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/SchemaMapBuilder.php217
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Table.php128
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/CustomFieldSpec.php118
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/FieldSpec.php320
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ActionScheduleCreationSpecProvider.php27
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ActivityCreationSpecProvider.php27
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/AddressCreationSpecProvider.php29
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContactCreationSpecProvider.php32
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContactTypeCreationSpecProvider.php29
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContributionCreationSpecProvider.php23
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/CustomGroupCreationSpecProvider.php22
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/CustomValueSpecProvider.php34
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/EmailCreationSpecProvider.php25
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/EventCreationSpecProvider.php22
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/GroupCreationSpecProvider.php22
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/NavigationCreationSpecProvider.php22
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/NoteCreationSpecProvider.php28
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/OptionValueCreationSpecProvider.php23
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/PhoneCreationSpecProvider.php24
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/SpecProviderInterface.php23
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/RequestSpec.php110
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/SpecFormatter.php117
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/SpecGatherer.php131
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/UFGroup.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/UFJoin.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ActionUtil.php27
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ArrayInsertionUtil.php73
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/CoreUtil.php42
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/FormattingUtil.php106
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ReflectionUtils.php119
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Website.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/LICENSE.txt667
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4.ang.php12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4.js4
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4/crmApi4.js37
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer.ang.php18
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer.js4
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Chain.html4
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Explorer.html137
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Explorer.js789
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/WhereClause.html39
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/api4.civix.php460
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/api4.php193
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/css/explorer.css191
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/images/ApiExplorer.pngbin0 -> 207956 bytes
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/info.xml29
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/js/api4.js43
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/js/load-bootstrap.js7
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/js-yaml.js3919
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/shoreditch/bootstrap.css1
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/shoreditch/dropdown.js165
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/phpunit.xml.dist29
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/readme.md120
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/services.xml122
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/templates/CRM/Api4/Page/Api4Explorer.tpl0
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BaseCustomValueTest.php31
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicActionsTest.php154
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicCustomFieldTest.php187
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ChainTest.php53
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ComplexQueryTest.php87
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ContactApiKeyTest.php170
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateCustomValueTest.php64
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateWithOptionGroupTest.php190
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValuePerformanceTest.php95
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValueTest.php174
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/DateTest.php46
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/EvaluateConditionTest.php38
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ExtendFromIndividualTest.php54
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/FkJoinTest.php75
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetExtraFieldsTest.php26
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetFromArrayTest.php163
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/IndexTest.php48
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/NullValueTest.php55
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ReplaceTest.php171
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateContactTest.php34
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateCustomValueTest.php56
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/ConformanceTest.json28
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/DefaultDataSet.json45
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/MultiContactMultiEmail.json42
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/SingleContact.json81
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ConformanceTest.php226
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ContactJoinTest.php103
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/EntityTest.php35
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ParticipantTest.php200
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/Action/MockArrayEntity/Get.php53
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockArrayEntity.php21
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockBasicEntity.php94
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockEntityDataStorage.php31
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionBase.php22
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionChild.php16
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionGrandchild.php17
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryComplexJoinTest.php85
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryTest.php91
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OneToOneJoinTest.php46
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OptionValueJoinTest.php46
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/SelectQueryMultiJoinTest.php74
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapRealTableTest.php22
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapperTest.php90
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/TestCreationParameterProvider.php148
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/RequestSpecTest.php42
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecFormatterTest.php90
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecGathererTest.php96
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/OptionCleanupTrait.php24
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/QueryCounterTrait.php43
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TableDropperTrait.php23
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TestDataLoaderTrait.php69
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/UnitTestCase.php235
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ArrayInsertionServiceTest.php67
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ReflectionUtilsTest.php46
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/bootstrap.php56
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/services.xml10
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/xml/Menu/api4.xml14
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php486
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php334
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceSWIPE.php76
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceUKDD.php341
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerLink.php233
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerUpdateBillingInfo.php52
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSOneTimeCharge.php253
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IatsSettings.php173
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.mgd.php26
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.php986
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.mgd.php26
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.php116
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.mgd.php26
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.php483
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.mgd.php26
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.php137
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/IATSCustomerLink.php55
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/iATSAdmin.php104
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/json.php67
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Upgrader.php213
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Upgrader/Base.php320
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/iATSService.php887
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/README.md125
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/GetJournal.php115
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/Journal.php76
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/Verifylog.php68
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.mgd.php28
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php467
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.mgd.php23
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php211
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.mgd.php28
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php212
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/composer.json5
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/css/iatspayments_civicrm.css15
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iATS_4.4.14.diff49
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iATS_4.5.8.diff37
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iats.civix.php278
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iats.php1530
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/info.xml29
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/contribute_form_search.js25
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_acheft.js12
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_cad.js58
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_uk.js150
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dpm.js100
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/recur_start.js27
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/subscription.js17
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/subscription_view.js10
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/swipe.js45
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/phpunit.xml.dist18
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.0.md38
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.1.md25
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.2.md7
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/install.sql111
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/uninstall.sql7
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_3_001.sql15
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_5_003.sql3
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_001.sql2
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_002.sql31
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_004.sql1
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDPM.tpl14
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_CAD.tpl36
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_GBP.tpl94
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_Other.tpl6
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_USD.tpl24
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockRecurringExtra.tpl10
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockSwipe.tpl22
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/CDN_cheque_500x.jpgbin0 -> 55291 bytes
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeConfirmExtra_UKDD.tpl41
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeThankYouExtra_UKDD.tpl38
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributionRecur.tpl9
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSCustomerLink.tpl17
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSOneTimeCharge.tpl22
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IatsSettings.tpl18
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/ACHEFTVerify.tpl2
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Journal.tpl2
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Recur.tpl2
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/GBP_cheque_500x.jpgbin0 -> 57165 bytes
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Page/IATSCustomerLink.tpl15
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Page/iATSAdmin.tpl58
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Subscription.tpl28
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/USD_check_500x.jpgbin0 -> 58859 bytes
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/bacs.pngbin0 -> 2616 bytes
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/credit_card_reader.jpgbin0 -> 20631 bytes
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/direct-debit.jpgbin0 -> 2372 bytes
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/iats.jpgbin0 -> 2753 bytes
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/usb_reader.jpgbin0 -> 14396 bytes
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/BaseTestClass.php196
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/ContributionIATSTest.php278
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php51
-rw-r--r--www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/xml/Menu/iats.xml38
299 files changed, 30329 insertions, 0 deletions
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/.gitignore b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/.gitignore
new file mode 100644
index 00000000..0d74f047
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/.gitignore
@@ -0,0 +1,74 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Example user template template
+### Example user template
+
+# IntelliJ project files
+.idea
+*.iml
+out
+gen### Linux template
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+### macOS template
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+### Windows template
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Page/AJAX.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Page/AJAX.php
new file mode 100644
index 00000000..6a1bc2de
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Page/AJAX.php
@@ -0,0 +1,66 @@
+<?php
+
+class CRM_Api4_Page_AJAX extends CRM_Core_Page {
+
+ /**
+ * Handler for api4 ajax requests
+ */
+ public function run() {
+ try {
+ // Call multiple
+ if (empty($this->urlPath[3])) {
+ $calls = CRM_Utils_Request::retrieve('calls', 'String', CRM_Core_DAO::$_nullObject, TRUE, NULL, 'POST', TRUE);
+ $calls = json_decode($calls, TRUE);
+ $response = [];
+ foreach ($calls as $index => $call) {
+ $response[$index] = call_user_func_array([$this, 'execute'], $call);
+ }
+ }
+ // Call single
+ else {
+ $entity = $this->urlPath[3];
+ $action = $this->urlPath[4];
+ $params = CRM_Utils_Request::retrieve('params', 'String');
+ $params = $params ? json_decode($params, TRUE) : [];
+ $index = CRM_Utils_Request::retrieve('index', 'String');
+ $response = $this->execute($entity, $action, $params, $index);
+ }
+ }
+ catch (Exception $e) {
+ http_response_code(500);
+ $response = [
+ 'error_code' => $e->getCode(),
+ ];
+ if (CRM_Core_Permission::check('view debug output')) {
+ $response['error_message'] = $e->getMessage();
+ if (CRM_Core_BAO_Setting::getItem(NULL, 'backtrace')) {
+ $response['backtrace'] = $e->getTrace();
+ }
+ }
+ }
+ CRM_Utils_System::setHttpHeader('Content-Type', 'application/json');
+ echo json_encode($response);
+ CRM_Utils_System::civiExit();
+ }
+
+ /**
+ * Run api call & prepare result for json encoding
+ *
+ * @param string $entity
+ * @param string $action
+ * @param array $params
+ * @param string $index
+ * @return array
+ */
+ protected function execute($entity, $action, $params = [], $index = NULL) {
+ $params['checkPermissions'] = TRUE;
+ $result = civicrm_api4($entity, $action, $params, $index);
+ // Convert arrayObject into something more suitable for json
+ $vals = ['values' => (array) $result];
+ foreach (get_class_vars(get_class($result)) as $key => $val) {
+ $vals[$key] = $result->$key;
+ }
+ return $vals;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Page/Api4Explorer.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Page/Api4Explorer.php
new file mode 100644
index 00000000..50aa0c3d
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Page/Api4Explorer.php
@@ -0,0 +1,24 @@
+<?php
+
+class CRM_Api4_Page_Api4Explorer extends CRM_Core_Page {
+
+ public function run() {
+ $vars = [
+ 'operators' => \CRM_Core_DAO::acceptedSQLOperators(),
+ 'basePath' => Civi::resources()->getUrl('org.civicrm.api4'),
+ ];
+ Civi::resources()
+ ->addVars('api4', $vars)
+ ->addScriptFile('org.civicrm.api4', 'js/load-bootstrap.js');
+
+ $loader = new Civi\Angular\AngularLoader();
+ $loader->setModules(['api4Explorer']);
+ $loader->setPageName('civicrm/api4');
+ $loader->useApp([
+ 'defaultRoute' => '/explorer',
+ ]);
+ $loader->load();
+ parent::run();
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Upgrader.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Upgrader.php
new file mode 100644
index 00000000..10ec9da5
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Upgrader.php
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * Collection of upgrade steps.
+ */
+class CRM_Api4_Upgrader extends CRM_Api4_Upgrader_Base {
+
+ // By convention, functions that look like "function upgrade_NNNN()" are
+ // upgrade tasks. They are executed in order (like Drupal's hook_update_N).
+
+ /**
+ * Install script
+ */
+ public function install() {
+ // Add menu item for api explorer; rename v3 explorer menu item.
+ try {
+ $v3Item = civicrm_api3('Navigation', 'get', [
+ 'name' => 'API Explorer',
+ 'return' => ['id', 'parent_id'],
+ 'sequential' => 1,
+ 'api.Navigation.create' => ['label' => ts("Api Explorer v3")],
+ ]);
+ civicrm_api3('Navigation', 'create', [
+ 'parent_id' => $v3Item['values'][0]['parent_id'],
+ 'label' => ts("Api Explorer v4"),
+ 'weight' => 2,
+ 'name' => "Api Explorer v4",
+ 'permission' => "administer CiviCRM",
+ 'url' => "civicrm/api4#/explorer",
+ 'is_active' => 1,
+ ]);
+ }
+ catch (Exception $e) {
+ // Couldn't create menu item.
+ }
+ }
+
+ /**
+ * Example: Work with entities usually not available during the install step.
+ *
+ * This method can be used for any post-install tasks. For example, if a step
+ * of your installation depends on accessing an entity that is itself
+ * created during the installation (e.g., a setting or a managed entity), do
+ * so here to avoid order of operation problems.
+ *
+ public function postInstall() {
+ $customFieldId = civicrm_api3('CustomField', 'getvalue', array(
+ 'return' => array("id"),
+ 'name' => "customFieldCreatedViaManagedHook",
+ ));
+ civicrm_api3('Setting', 'create', array(
+ 'myWeirdFieldSetting' => array('id' => $customFieldId, 'weirdness' => 1),
+ ));
+ }
+
+ /**
+ * Uninstall script
+ */
+ public function uninstall() {
+ // Remove Api4 Explorer navigation menu item
+ civicrm_api3('Navigation', 'get', [
+ 'name' => 'Api Explorer v4',
+ 'return' => ['id'],
+ 'api.Navigation.delete' => [],
+ ]);
+ }
+
+ /**
+ * Example: Run a simple query when a module is enabled.
+ *
+ public function enable() {
+ CRM_Core_DAO::executeQuery('UPDATE foo SET is_active = 1 WHERE bar = "whiz"');
+ }
+
+ /**
+ * Example: Run a simple query when a module is disabled.
+ *
+ public function disable() {
+ CRM_Core_DAO::executeQuery('UPDATE foo SET is_active = 0 WHERE bar = "whiz"');
+ }
+
+ /**
+ * Example: Run a couple simple queries.
+ *
+ * @return TRUE on success
+ * @throws Exception
+ */
+ public function upgrade_1000() {
+ $this->ctx->log->info('Applying update 1000');
+ $domain = CRM_Core_Config::domainID();
+ CRM_Core_DAO::executeQuery('UPDATE civicrm_navigation SET url = "civicrm/api4#/explorer" WHERE url = "civicrm/a/#/api4" AND domain_id = ' . $domain);
+ return TRUE;
+ }
+
+
+ /**
+ * Example: Run an external SQL script.
+ *
+ * @return TRUE on success
+ * @throws Exception
+ public function upgrade_4201() {
+ $this->ctx->log->info('Applying update 4201');
+ // this path is relative to the extension base dir
+ $this->executeSqlFile('sql/upgrade_4201.sql');
+ return TRUE;
+ } // */
+
+
+ /**
+ * Example: Run a slow upgrade process by breaking it up into smaller chunk.
+ *
+ * @return TRUE on success
+ * @throws Exception
+ public function upgrade_4202() {
+ $this->ctx->log->info('Planning update 4202'); // PEAR Log interface
+
+ $this->addTask(ts('Process first step'), 'processPart1', $arg1, $arg2);
+ $this->addTask(ts('Process second step'), 'processPart2', $arg3, $arg4);
+ $this->addTask(ts('Process second step'), 'processPart3', $arg5);
+ return TRUE;
+ }
+ public function processPart1($arg1, $arg2) { sleep(10); return TRUE; }
+ public function processPart2($arg3, $arg4) { sleep(10); return TRUE; }
+ public function processPart3($arg5) { sleep(10); return TRUE; }
+ // */
+
+
+ /**
+ * Example: Run an upgrade with a query that touches many (potentially
+ * millions) of records by breaking it up into smaller chunks.
+ *
+ * @return TRUE on success
+ * @throws Exception
+ public function upgrade_4203() {
+ $this->ctx->log->info('Planning update 4203'); // PEAR Log interface
+
+ $minId = CRM_Core_DAO::singleValueQuery('SELECT coalesce(min(id),0) FROM civicrm_contribution');
+ $maxId = CRM_Core_DAO::singleValueQuery('SELECT coalesce(max(id),0) FROM civicrm_contribution');
+ for ($startId = $minId; $startId <= $maxId; $startId += self::BATCH_SIZE) {
+ $endId = $startId + self::BATCH_SIZE - 1;
+ $title = ts('Upgrade Batch (%1 => %2)', array(
+ 1 => $startId,
+ 2 => $endId,
+ ));
+ $sql = '
+ UPDATE civicrm_contribution SET foobar = whiz(wonky()+wanker)
+ WHERE id BETWEEN %1 and %2
+ ';
+ $params = array(
+ 1 => array($startId, 'Integer'),
+ 2 => array($endId, 'Integer'),
+ );
+ $this->addTask($title, 'executeSql', $sql, $params);
+ }
+ return TRUE;
+ } // */
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Upgrader/Base.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Upgrader/Base.php
new file mode 100644
index 00000000..bfea5fef
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/CRM/Api4/Upgrader/Base.php
@@ -0,0 +1,375 @@
+<?php
+
+// AUTO-GENERATED FILE -- Civix may overwrite any changes made to this file
+
+/**
+ * Base class which provides helpers to execute upgrade logic
+ */
+class CRM_Api4_Upgrader_Base {
+
+ /**
+ * @var varies, subclass of ttis
+ */
+ static $instance;
+
+ /**
+ * @var CRM_Queue_TaskContext
+ */
+ protected $ctx;
+
+ /**
+ * @var string, eg 'com.example.myextension'
+ */
+ protected $extensionName;
+
+ /**
+ * @var string, full path to the extension's source tree
+ */
+ protected $extensionDir;
+
+ /**
+ * @var array(revisionNumber) sorted numerically
+ */
+ private $revisions;
+
+ /**
+ * @var boolean
+ * Flag to clean up extension revision data in civicrm_setting
+ */
+ private $revisionStorageIsDeprecated = FALSE;
+
+ /**
+ * Obtain a reference to the active upgrade handler.
+ */
+ static public function instance() {
+ if (!self::$instance) {
+ // FIXME auto-generate
+ self::$instance = new CRM_Api4_Upgrader(
+ 'org.civicrm.api4',
+ realpath(__DIR__ . '/../../../')
+ );
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Adapter that lets you add normal (non-static) member functions to the queue.
+ *
+ * Note: Each upgrader instance should only be associated with one
+ * task-context; otherwise, this will be non-reentrant.
+ *
+ * @code
+ * CRM_Api4_Upgrader_Base::_queueAdapter($ctx, 'methodName', 'arg1', 'arg2');
+ * @endcode
+ */
+ static public function _queueAdapter() {
+ $instance = self::instance();
+ $args = func_get_args();
+ $instance->ctx = array_shift($args);
+ $instance->queue = $instance->ctx->queue;
+ $method = array_shift($args);
+ return call_user_func_array([$instance, $method], $args);
+ }
+
+ public function __construct($extensionName, $extensionDir) {
+ $this->extensionName = $extensionName;
+ $this->extensionDir = $extensionDir;
+ }
+
+ // ******** Task helpers ********
+
+ /**
+ * Run a CustomData file.
+ *
+ * @param string $relativePath the CustomData XML file path (relative to this extension's dir)
+ * @return bool
+ */
+ public function executeCustomDataFile($relativePath) {
+ $xml_file = $this->extensionDir . '/' . $relativePath;
+ return $this->executeCustomDataFileByAbsPath($xml_file);
+ }
+
+ /**
+ * Run a CustomData file
+ *
+ * @param string $xml_file the CustomData XML file path (absolute path)
+ *
+ * @return bool
+ */
+ protected static function executeCustomDataFileByAbsPath($xml_file) {
+ $import = new CRM_Utils_Migrate_Import();
+ $import->run($xml_file);
+ return TRUE;
+ }
+
+ /**
+ * Run a SQL file.
+ *
+ * @param string $relativePath the SQL file path (relative to this extension's dir)
+ *
+ * @return bool
+ */
+ public function executeSqlFile($relativePath) {
+ CRM_Utils_File::sourceSQLFile(
+ CIVICRM_DSN,
+ $this->extensionDir . DIRECTORY_SEPARATOR . $relativePath
+ );
+ return TRUE;
+ }
+
+ /**
+ * @param string $tplFile
+ * The SQL file path (relative to this extension's dir).
+ * Ex: "sql/mydata.mysql.tpl".
+ * @return bool
+ */
+ public function executeSqlTemplate($tplFile) {
+ // Assign multilingual variable to Smarty.
+ $upgrade = new CRM_Upgrade_Form();
+
+ $tplFile = CRM_Utils_File::isAbsolute($tplFile) ? $tplFile : $this->extensionDir . DIRECTORY_SEPARATOR . $tplFile;
+ $smarty = CRM_Core_Smarty::singleton();
+ $smarty->assign('domainID', CRM_Core_Config::domainID());
+ CRM_Utils_File::sourceSQLFile(
+ CIVICRM_DSN, $smarty->fetch($tplFile), NULL, TRUE
+ );
+ return TRUE;
+ }
+
+ /**
+ * Run one SQL query.
+ *
+ * This is just a wrapper for CRM_Core_DAO::executeSql, but it
+ * provides syntatic sugar for queueing several tasks that
+ * run different queries
+ */
+ public function executeSql($query, $params = []) {
+ // FIXME verify that we raise an exception on error
+ CRM_Core_DAO::executeQuery($query, $params);
+ return TRUE;
+ }
+
+ /**
+ * Syntatic sugar for enqueuing a task which calls a function in this class.
+ *
+ * The task is weighted so that it is processed
+ * as part of the currently-pending revision.
+ *
+ * After passing the $funcName, you can also pass parameters that will go to
+ * the function. Note that all params must be serializable.
+ */
+ public function addTask($title) {
+ $args = func_get_args();
+ $title = array_shift($args);
+ $task = new CRM_Queue_Task(
+ [get_class($this), '_queueAdapter'],
+ $args,
+ $title
+ );
+ return $this->queue->createItem($task, ['weight' => -1]);
+ }
+
+ // ******** Revision-tracking helpers ********
+
+ /**
+ * Determine if there are any pending revisions.
+ *
+ * @return bool
+ */
+ public function hasPendingRevisions() {
+ $revisions = $this->getRevisions();
+ $currentRevision = $this->getCurrentRevision();
+
+ if (empty($revisions)) {
+ return FALSE;
+ }
+ if (empty($currentRevision)) {
+ return TRUE;
+ }
+
+ return ($currentRevision < max($revisions));
+ }
+
+ /**
+ * Add any pending revisions to the queue.
+ */
+ public function enqueuePendingRevisions(CRM_Queue_Queue $queue) {
+ $this->queue = $queue;
+
+ $currentRevision = $this->getCurrentRevision();
+ foreach ($this->getRevisions() as $revision) {
+ if ($revision > $currentRevision) {
+ $title = ts('Upgrade %1 to revision %2', [
+ 1 => $this->extensionName,
+ 2 => $revision,
+ ]);
+
+ // note: don't use addTask() because it sets weight=-1
+
+ $task = new CRM_Queue_Task(
+ [get_class($this), '_queueAdapter'],
+ ['upgrade_' . $revision],
+ $title
+ );
+ $this->queue->createItem($task);
+
+ $task = new CRM_Queue_Task(
+ [get_class($this), '_queueAdapter'],
+ ['setCurrentRevision', $revision],
+ $title
+ );
+ $this->queue->createItem($task);
+ }
+ }
+ }
+
+ /**
+ * Get a list of revisions.
+ *
+ * @return array(revisionNumbers) sorted numerically
+ */
+ public function getRevisions() {
+ if (!is_array($this->revisions)) {
+ $this->revisions = [];
+
+ $clazz = new ReflectionClass(get_class($this));
+ $methods = $clazz->getMethods();
+ foreach ($methods as $method) {
+ if (preg_match('/^upgrade_(.*)/', $method->name, $matches)) {
+ $this->revisions[] = $matches[1];
+ }
+ }
+ sort($this->revisions, SORT_NUMERIC);
+ }
+
+ return $this->revisions;
+ }
+
+ public function getCurrentRevision() {
+ $revision = CRM_Core_BAO_Extension::getSchemaVersion($this->extensionName);
+ if (!$revision) {
+ $revision = $this->getCurrentRevisionDeprecated();
+ }
+ return $revision;
+ }
+
+ private function getCurrentRevisionDeprecated() {
+ $key = $this->extensionName . ':version';
+ if ($revision = CRM_Core_BAO_Setting::getItem('Extension', $key)) {
+ $this->revisionStorageIsDeprecated = TRUE;
+ }
+ return $revision;
+ }
+
+ public function setCurrentRevision($revision) {
+ CRM_Core_BAO_Extension::setSchemaVersion($this->extensionName, $revision);
+ // clean up legacy schema version store (CRM-19252)
+ $this->deleteDeprecatedRevision();
+ return TRUE;
+ }
+
+ private function deleteDeprecatedRevision() {
+ if ($this->revisionStorageIsDeprecated) {
+ $setting = new CRM_Core_BAO_Setting();
+ $setting->name = $this->extensionName . ':version';
+ $setting->delete();
+ CRM_Core_Error::debug_log_message("Migrated extension schema revision ID for {$this->extensionName} from civicrm_setting (deprecated) to civicrm_extension.\n");
+ }
+ }
+
+ // ******** Hook delegates ********
+
+ /**
+ * @see https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_install
+ */
+ public function onInstall() {
+ $files = glob($this->extensionDir . '/sql/*_install.sql');
+ if (is_array($files)) {
+ foreach ($files as $file) {
+ CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
+ }
+ }
+ $files = glob($this->extensionDir . '/sql/*_install.mysql.tpl');
+ if (is_array($files)) {
+ foreach ($files as $file) {
+ $this->executeSqlTemplate($file);
+ }
+ }
+ $files = glob($this->extensionDir . '/xml/*_install.xml');
+ if (is_array($files)) {
+ foreach ($files as $file) {
+ $this->executeCustomDataFileByAbsPath($file);
+ }
+ }
+ if (is_callable([$this, 'install'])) {
+ $this->install();
+ }
+ }
+
+ /**
+ * @see https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_postInstall
+ */
+ public function onPostInstall() {
+ $revisions = $this->getRevisions();
+ if (!empty($revisions)) {
+ $this->setCurrentRevision(max($revisions));
+ }
+ if (is_callable([$this, 'postInstall'])) {
+ $this->postInstall();
+ }
+ }
+
+ /**
+ * @see https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_uninstall
+ */
+ public function onUninstall() {
+ $files = glob($this->extensionDir . '/sql/*_uninstall.mysql.tpl');
+ if (is_array($files)) {
+ foreach ($files as $file) {
+ $this->executeSqlTemplate($file);
+ }
+ }
+ if (is_callable([$this, 'uninstall'])) {
+ $this->uninstall();
+ }
+ $files = glob($this->extensionDir . '/sql/*_uninstall.sql');
+ if (is_array($files)) {
+ foreach ($files as $file) {
+ CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
+ }
+ }
+ }
+
+ /**
+ * @see https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_enable
+ */
+ public function onEnable() {
+ // stub for possible future use
+ if (is_callable([$this, 'enable'])) {
+ $this->enable();
+ }
+ }
+
+ /**
+ * @see https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_disable
+ */
+ public function onDisable() {
+ // stub for possible future use
+ if (is_callable([$this, 'disable'])) {
+ $this->disable();
+ }
+ }
+
+ public function onUpgrade($op, CRM_Queue_Queue $queue = NULL) {
+ switch ($op) {
+ case 'check':
+ return [$this->hasPendingRevisions()];
+
+ case 'enqueue':
+ return $this->enqueuePendingRevisions($queue);
+
+ default:
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ACL.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ACL.php
new file mode 100644
index 00000000..754a0499
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ACL.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * ACL Entity.
+ *
+ * This entity holds the ACL informatiom. With this entity you add/update/delete an ACL permission which consists of
+ * an Operation (e.g. 'View' or 'Edit'), a set of Data that the operation can be performed on (e.g. a group of contacts),
+ * and a Role that has permission to do this operation. For more info refer to
+ * https://docs.civicrm.org/user/en/latest/initial-set-up/permissions-and-access-control for more info.
+ *
+ * Creating a new ACL requires at minimum a entity table, entity ID and object_table
+ *
+ * @package Civi\Api4
+ */
+class ACL extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Address/Create.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Address/Create.php
new file mode 100644
index 00000000..641b76f6
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Address/Create.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Civi\Api4\Action\Address;
+
+use Civi\Api4\Generic\Result;
+
+/**
+ * @inheritDoc
+ */
+class Create extends \Civi\Api4\Generic\DAOCreateAction {
+
+ /**
+ * Optional param to indicate you want the street_address field parsed into individual params
+ *
+ * @var bool
+ */
+ protected $streetParsing = TRUE;
+
+ /**
+ * Optional param to indicate you want to skip geocoding (useful when importing a lot of addresses at once, the job Geocode and Parse Addresses can execute this task after the import)
+ *
+ * @var bool
+ */
+ protected $skipGeocode = FALSE;
+
+ /**
+ * When true, apply various fixes to the address before insert.
+ *
+ * @var bool
+ */
+ protected $fixAddress = TRUE;
+
+ /**
+ * @inheritDoc
+ */
+ public function _run(Result $result) {
+ $this->values['street_parsing'] = $this->streetParsing;
+ $this->values['skip_geocode'] = $this->skipGeocode;
+ $this->values['fix_address'] = $this->fixAddress;
+ parent::_run($result);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Address/Update.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Address/Update.php
new file mode 100644
index 00000000..862a8b9e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Address/Update.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Civi\Api4\Action\Address;
+
+use Civi\Api4\Generic\Result;
+
+/**
+ * @inheritDoc
+ */
+class Update extends \Civi\Api4\Generic\DAOUpdateAction {
+
+ /**
+ * Optional param to indicate you want the street_address field parsed into individual params
+ *
+ * @var bool
+ */
+ protected $streetParsing = TRUE;
+
+ /**
+ * Optional param to indicate you want to skip geocoding (useful when importing a lot of addresses at once, the job Geocode and Parse Addresses can execute this task after the import)
+ *
+ * @var bool
+ */
+ protected $skipGeocode = FALSE;
+
+ /**
+ * When true, apply various fixes to the address before insert.
+ *
+ * @var bool
+ */
+ protected $fixAddress = TRUE;
+
+ /**
+ * @inheritDoc
+ */
+ public function _run(Result $result) {
+ $this->values['street_parsing'] = $this->streetParsing;
+ $this->values['skip_geocode'] = $this->skipGeocode;
+ $this->values['fix_address'] = $this->fixAddress;
+ parent::_run($result);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contact/Create.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contact/Create.php
new file mode 100644
index 00000000..1bd0bcec
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contact/Create.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Civi\Api4\Action\Contact;
+
+/**
+ * @inheritDoc
+ */
+class Create extends \Civi\Api4\Generic\DAOCreateAction {
+
+ protected function fillDefaults(&$params) {
+ // Guess which type of contact is being created
+ if (empty($params['contact_type']) && !empty($params['organization_name'])) {
+ $params['contact_type'] = 'Organization';
+ }
+ if (empty($params['contact_type']) && !empty($params['household_name'])) {
+ $params['contact_type'] = 'Household';
+ }
+ // Will default to Individual per fieldSpec
+ parent::fillDefaults($params);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contact/GetFields.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contact/GetFields.php
new file mode 100644
index 00000000..1d6ddf66
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contact/GetFields.php
@@ -0,0 +1,19 @@
+<?php
+namespace Civi\Api4\Action\Contact;
+
+use Civi\Api4\Generic\DAOGetFieldsAction;
+
+class GetFields extends DAOGetFieldsAction {
+
+ protected function getRecords() {
+ $fields = parent::getRecords();
+
+ $apiKeyPerms = ['edit api keys', 'administer CiviCRM'];
+ if ($this->checkPermissions && !\CRM_Core_Permission::check([$apiKeyPerms])) {
+ unset($fields['api_key']);
+ }
+
+ return $fields;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contribution/Create.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contribution/Create.php
new file mode 100644
index 00000000..52ee63c8
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Contribution/Create.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Civi\Api4\Action\Contribution;
+
+use Civi\Api4\Generic\Result;
+
+/**
+ * @inheritDoc
+ */
+class Create extends \Civi\Api4\Generic\DAOCreateAction {
+
+ public function _run(Result $result) {
+ // Required by Contribution BAO
+ $this->values['skipCleanMoney'] = TRUE;
+ parent::_run($result);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Create.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Create.php
new file mode 100644
index 00000000..7d059b24
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Create.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Civi\Api4\Action\CustomValue;
+
+/**
+ * @inheritDoc
+ */
+class Create extends \Civi\Api4\Generic\DAOCreateAction {
+ use \Civi\Api4\Generic\Traits\CustomValueActionTrait;
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Delete.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Delete.php
new file mode 100644
index 00000000..7c521748
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Delete.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Civi\Api4\Action\CustomValue;
+
+/**
+ * Delete one or more items, based on criteria specified in Where param.
+ */
+class Delete extends \Civi\Api4\Generic\DAODeleteAction {
+ use \Civi\Api4\Generic\Traits\CustomValueActionTrait;
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Get.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Get.php
new file mode 100644
index 00000000..47f3f514
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Get.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Civi\Api4\Action\CustomValue;
+
+/**
+ * Get fields for a custom group.
+ */
+class Get extends \Civi\Api4\Generic\DAOGetAction {
+ use \Civi\Api4\Generic\Traits\CustomValueActionTrait;
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/GetActions.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/GetActions.php
new file mode 100644
index 00000000..8af9088d
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/GetActions.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Civi\Api4\Action\CustomValue;
+
+/**
+ * @inheritDoc
+ */
+class GetActions extends \Civi\Api4\Action\GetActions {
+ use \Civi\Api4\Generic\Traits\CustomValueActionTrait;
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/GetFields.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/GetFields.php
new file mode 100644
index 00000000..733776b7
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/GetFields.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Civi\Api4\Action\CustomValue;
+
+use Civi\Api4\Service\Spec\SpecGatherer;
+use Civi\Api4\Service\Spec\SpecFormatter;
+
+/**
+ * Get fields for a custom group.
+ */
+class GetFields extends \Civi\Api4\Generic\DAOGetFieldsAction {
+ use \Civi\Api4\Generic\Traits\CustomValueActionTrait;
+
+ protected function getRecords() {
+ $fields = $this->_itemsToGet('name');
+ /** @var SpecGatherer $gatherer */
+ $gatherer = \Civi::container()->get('spec_gatherer');
+ $spec = $gatherer->getSpec('Custom_' . $this->getCustomGroup(), $this->getAction(), $this->includeCustom);
+ return SpecFormatter::specToArray($spec->getFields($fields), (array) $this->select, $this->loadOptions);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getParamInfo($param = NULL) {
+ $info = parent::getParamInfo($param);
+ if (!$param) {
+ // This param is meaningless here.
+ unset($info['includeCustom']);
+ }
+ return $info;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Replace.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Replace.php
new file mode 100644
index 00000000..457be9ca
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Replace.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Civi\Api4\Action\CustomValue;
+
+/**
+ * Given a set of records, will appropriately update the database.
+ */
+class Replace extends \Civi\Api4\Generic\BasicReplaceAction {
+ use \Civi\Api4\Generic\Traits\CustomValueActionTrait;
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Update.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Update.php
new file mode 100644
index 00000000..14f66f29
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/CustomValue/Update.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Civi\Api4\Action\CustomValue;
+
+/**
+ * Update one or more records with new values. Use the where clause to select them.
+ */
+class Update extends \Civi\Api4\Generic\DAOUpdateAction {
+ use \Civi\Api4\Generic\Traits\CustomValueActionTrait;
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Entity/Get.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Entity/Get.php
new file mode 100644
index 00000000..020c6398
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Entity/Get.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Civi\Api4\Action\Entity;
+
+use Civi\Api4\CustomGroup;
+use Civi\Api4\Utils\ReflectionUtils;
+
+/**
+ * Get entities
+ *
+ * @method $this setIncludeCustom(bool $value)
+ * @method bool getIncludeCustom()
+ */
+class Get extends \Civi\Api4\Generic\BasicGetAction {
+
+ /**
+ * Include custom-field-based pseudo-entities?
+ *
+ * @var bool
+ */
+ protected $includeCustom = TRUE;
+
+ /**
+ * Scan all api directories to discover entities
+ */
+ protected function getRecords() {
+ $entities = [];
+ foreach (explode(PATH_SEPARATOR, get_include_path()) as $path) {
+ $dir = \CRM_Utils_File::addTrailingSlash($path) . 'Civi/Api4';
+ if (is_dir($dir)) {
+ foreach (glob("$dir/*.php") as $file) {
+ $matches = [];
+ preg_match('/(\w*).php/', $file, $matches);
+ $entity = ['name' => $matches[1]];
+ if ($this->_isFieldSelected('description') || $this->_isFieldSelected('comment')) {
+ $this->addDocs($entity);
+ }
+ $entities[$matches[1]] = $entity;
+ }
+ }
+ }
+ unset($entities['CustomValue']);
+
+ if ($this->includeCustom) {
+ $this->addCustomEntities($entities);
+ }
+
+ ksort($entities);
+ return $entities;
+ }
+
+ /**
+ * Add custom-field pseudo-entities
+ *
+ * @param $entities
+ * @throws \API_Exception
+ */
+ private function addCustomEntities(&$entities) {
+ $customEntities = CustomGroup::get()
+ ->addWhere('is_multiple', '=', 1)
+ ->addWhere('is_active', '=', 1)
+ ->setSelect(['name', 'title', 'help_pre', 'help_post', 'extends'])
+ ->setCheckPermissions(FALSE)
+ ->execute();
+ foreach ($customEntities as $customEntity) {
+ $fieldName = 'Custom_' . $customEntity['name'];
+ $entities[$fieldName] = [
+ 'name' => $fieldName,
+ 'description' => $customEntity['title'] . ' custom group - extends ' . $customEntity['extends'],
+ ];
+ if (!empty($customEntity['help_pre'])) {
+ $entities[$fieldName]['comment'] = $this->plainTextify($customEntity['help_pre']);
+ }
+ if (!empty($customEntity['help_post'])) {
+ $pre = empty($entities[$fieldName]['comment']) ? '' : $entities[$fieldName]['comment'] . "\n\n";
+ $entities[$fieldName]['comment'] = $pre . $this->plainTextify($customEntity['help_post']);
+ }
+ }
+ }
+
+ /**
+ * Convert html to plain text.
+ *
+ * @param $input
+ * @return mixed
+ */
+ private function plainTextify($input) {
+ return html_entity_decode(strip_tags($input), ENT_QUOTES | ENT_HTML5, 'UTF-8');
+ }
+
+ /**
+ * Add info from code docblock.
+ *
+ * @param $entity
+ */
+ private function addDocs(&$entity) {
+ $reflection = new \ReflectionClass("\\Civi\\Api4\\" . $entity['name']);
+ $entity += ReflectionUtils::getCodeDocs($reflection);
+ unset($entity['package'], $entity['method']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Entity/GetLinks.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Entity/GetLinks.php
new file mode 100644
index 00000000..ee274bf9
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Entity/GetLinks.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Civi\Api4\Action\Entity;
+
+use \CRM_Core_DAO_AllCoreTables as AllCoreTables;
+
+/**
+ * Get a list of FK links between entities
+ */
+class GetLinks extends \Civi\Api4\Generic\BasicGetAction {
+
+ public function getRecords() {
+ $result = [];
+ /** @var \Civi\Api4\Service\Schema\SchemaMap $schema */
+ $schema = \Civi::container()->get('schema_map');
+ foreach ($schema->getTables() as $table) {
+ $entity = AllCoreTables::getBriefName(AllCoreTables::getClassForTable($table->getName()));
+ // Since this is an api function, exclude tables that don't have an api
+ if (class_exists('\Civi\Api4\\' . $entity)) {
+ $item = [
+ 'entity' => $entity,
+ 'table' => $table->getName(),
+ 'links' => [],
+ ];
+ foreach ($table->getTableLinks() as $link) {
+ $link = $link->toArray();
+ $link['entity'] = AllCoreTables::getBriefName(AllCoreTables::getClassForTable($link['targetTable']));
+ $item['links'][] = $link;
+ }
+ $result[] = $item;
+ }
+ }
+ return $result;
+ }
+
+ public function fields() {
+ return [
+ [
+ 'name' => 'entity',
+ ],
+ [
+ 'name' => 'table',
+ ],
+ [
+ 'name' => 'links',
+ 'data_type' => 'Array',
+ ],
+ ];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GetActions.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GetActions.php
new file mode 100644
index 00000000..1dc4ebbb
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GetActions.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Civi\Api4\Action;
+
+use Civi\API\Exception\NotImplementedException;
+use Civi\Api4\Generic\BasicGetAction;
+use Civi\Api4\Utils\ActionUtil;
+use Civi\Api4\Utils\ReflectionUtils;
+
+/**
+ * Get actions for an entity with a list of accepted params
+ */
+class GetActions extends BasicGetAction {
+
+ private $_actions = [];
+
+ private $_actionsToGet;
+
+ protected function getRecords() {
+ $this->_actionsToGet = $this->_itemsToGet('name');
+
+ $entityReflection = new \ReflectionClass('\Civi\Api4\\' . $this->_entityName);
+ foreach ($entityReflection->getMethods(\ReflectionMethod::IS_STATIC | \ReflectionMethod::IS_PUBLIC) as $method) {
+ $actionName = $method->getName();
+ if ($actionName != 'permissions' && $actionName[0] != '_') {
+ $this->loadAction($actionName);
+ }
+ }
+ if (!$this->_actionsToGet || count($this->_actionsToGet) > count($this->_actions)) {
+ $includePaths = array_unique(explode(PATH_SEPARATOR, get_include_path()));
+ // Search entity-specific actions (including those provided by extensions)
+ foreach ($includePaths as $path) {
+ $dir = \CRM_Utils_File::addTrailingSlash($path) . 'Civi/Api4/Action/' . $this->_entityName;
+ $this->scanDir($dir);
+ }
+ }
+ ksort($this->_actions);
+ return $this->_actions;
+ }
+
+ /**
+ * @param $dir
+ */
+ private function scanDir($dir) {
+ if (is_dir($dir)) {
+ foreach (glob("$dir/*.php") as $file) {
+ $matches = [];
+ preg_match('/(\w*).php/', $file, $matches);
+ $actionName = array_pop($matches);
+ $this->loadAction(lcfirst($actionName));
+ }
+ }
+ }
+
+ /**
+ * @param $actionName
+ */
+ private function loadAction($actionName) {
+ try {
+ if (!isset($this->_actions[$actionName]) && (!$this->_actionsToGet || in_array($actionName, $this->_actionsToGet))) {
+ $action = ActionUtil::getAction($this->getEntityName(), $actionName);
+ if (is_object($action)) {
+ $this->_actions[$actionName] = ['name' => $actionName];
+ if ($this->_isFieldSelected('description') || $this->_isFieldSelected('comment')) {
+ $actionReflection = new \ReflectionClass($action);
+ $actionInfo = ReflectionUtils::getCodeDocs($actionReflection);
+ unset($actionInfo['method']);
+ $this->_actions[$actionName] += $actionInfo;
+ }
+ if ($this->_isFieldSelected('params')) {
+ $this->_actions[$actionName]['params'] = $action->getParamInfo();
+ }
+ }
+ }
+ }
+ catch (NotImplementedException $e) {
+ }
+ }
+
+ public function fields() {
+ return [
+ [
+ 'name' => 'name',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'description',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'comment',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'params',
+ 'data_type' => 'Array',
+ ],
+ ];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GroupContact/Create.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GroupContact/Create.php
new file mode 100644
index 00000000..44e61f2e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GroupContact/Create.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Civi\Api4\Action\GroupContact;
+
+use Civi\Api4\Generic\Result;
+
+/**
+ * @inheritDoc
+ *
+ * @method $this setMethod(string $method) Indicate who added/removed the group.
+ * @method $this setTracking(string $tracking) Specify ip address or other tracking info.
+ */
+class Create extends \Civi\Api4\Generic\DAOCreateAction {
+
+ /**
+ * String to indicate who added/removed the group.
+ *
+ * @var string
+ */
+ protected $method = 'API';
+
+ /**
+ * IP address or other tracking info about who performed this group subscription.
+ *
+ * @var string
+ */
+ protected $tracking = '';
+
+ /**
+ * @inheritDoc
+ */
+ public function _run(Result $result) {
+ $this->values['method'] = $this->method;
+ $this->values['tracking'] = $this->tracking;
+ parent::_run($result);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GroupContact/Update.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GroupContact/Update.php
new file mode 100644
index 00000000..edb8a902
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/GroupContact/Update.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Civi\Api4\Action\GroupContact;
+
+use Civi\Api4\Generic\Result;
+
+/**
+ * @inheritDoc
+ *
+ * @method $this setMethod(string $method) Indicate who added/removed the group.
+ * @method $this setTracking(string $tracking) Specify ip address or other tracking info.
+ */
+class Update extends \Civi\Api4\Generic\DAOUpdateAction {
+
+ /**
+ * String to indicate who added/removed the group.
+ *
+ * @var string
+ */
+ protected $method = 'API';
+
+ /**
+ * IP address or other tracking info about who performed this group subscription.
+ *
+ * @var string
+ */
+ protected $tracking = '';
+
+ /**
+ * @inheritDoc
+ */
+ public function _run(Result $result) {
+ $this->values['method'] = $this->method;
+ $this->values['tracking'] = $this->tracking;
+ parent::_run($result);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Navigation/Get.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Navigation/Get.php
new file mode 100644
index 00000000..dcecc06a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Navigation/Get.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Civi\Api4\Action\Navigation;
+
+/**
+ * @inheritDoc
+ *
+ * Fetch items from the navigation menu. By default this will fetch items from the current domain.
+ */
+class Get extends \Civi\Api4\Generic\DAOGetAction {
+
+ /**
+ * @inheritDoc
+ */
+ protected $where = [
+ ['domain_id', '=', 'current_domain'],
+ ];
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Participant/Get.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Participant/Get.php
new file mode 100644
index 00000000..c5efec93
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Action/Participant/Get.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Civi\Api4\Action\Participant;
+
+/**
+ * @inheritDoc
+ */
+class Get extends \Civi\Api4\Generic\DAOGetAction {
+
+ /**
+ * @inheritDoc
+ * $example->addWhere('contact_id.contact_type', 'IN', array('Individual', 'Household'))
+ */
+ protected $where = [
+ ['is_test', '=', 0],
+ ];
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ActionSchedule.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ActionSchedule.php
new file mode 100644
index 00000000..a8235f64
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ActionSchedule.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * ActionSchedule Entity.
+ *
+ * This entity exposes CiviCRM schedule reminders, which allows us to send messages (through email or SMS)
+ * to contacts when certain criteria are met. Using this API you can create schedule reminder for
+ * supported entities like Contact, Activity, Event, Membership or Contribution.
+ *
+ * Creating a new ActionSchedule requires at minimum a title, mapping_id and entity_value.
+ *
+ * @package Civi\Api4
+ */
+class ActionSchedule extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Activity.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Activity.php
new file mode 100644
index 00000000..23ee4c15
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Activity.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Activity entity.
+ *
+ * This entity adds record of any scheduled or completed interaction with one or more contacts.
+ * Each activity record is tightly linked to other CiviCRM constituents. With this API you can manually
+ * create an activity of desired type for your organisation or any other contact.
+ *
+ * Creating a new Activity requires at minimum a activity_type_id, entity ID and object_table
+ *
+ * An activity is a record of some type of interaction with one or more contacts.
+ *
+ * @package Civi\Api4
+ */
+class Activity extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ActivityContact.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ActivityContact.php
new file mode 100644
index 00000000..7ef438a5
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ActivityContact.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * ActivityContact Entity.
+ *
+ * This entity adds a record which relate a contact to activity.
+ *
+ * Creating a new ActivityContact requires at minimum a contact_id and activity_id.
+ *
+ * @package Civi\Api4
+ */
+class ActivityContact extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Address.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Address.php
new file mode 100644
index 00000000..e52b6283
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Address.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Address Entity.
+ *
+ * This entity holds the address informatiom of a contact. Each contact may hold
+ * one or more addresses but must have different location types respectively.
+ *
+ * Creating a new address requires at minimum a contact's ID and location type ID
+ * and other attributes (although optional) like street address, city, country etc.
+ *
+ * @package Civi\Api4
+ */
+class Address extends Generic\DAOEntity {
+
+ /**
+ * @return \Civi\Api4\Action\Address\Create
+ */
+ public static function create() {
+ return new \Civi\Api4\Action\Address\Create(__CLASS__, __FUNCTION__);
+ }
+
+ /**
+ * @return \Civi\Api4\Action\Address\Update
+ */
+ public static function update() {
+ return new \Civi\Api4\Action\Address\Update(__CLASS__, __FUNCTION__);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Contact.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Contact.php
new file mode 100644
index 00000000..cca8c335
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Contact.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Contacts - Individuals, Organizations, Households.
+ *
+ * This is the central entity in the CiviCRM database, and links to
+ * many other entities (Email, Phone, Participant, etc.).
+ *
+ * Creating a new contact requires at minimum a name or email address.
+ *
+ * @package Civi\Api4
+ */
+class Contact extends Generic\DAOEntity {
+
+ /**
+ * @return Action\Contact\Create
+ */
+ public static function create() {
+ return new Action\Contact\Create(__CLASS__, __FUNCTION__);
+ }
+
+ /**
+ * @return \Civi\Api4\Generic\DAOUpdateAction
+ */
+ public static function update() {
+ // For some reason the contact bao requires this for updating
+ return new Generic\DAOUpdateAction(__CLASS__, __FUNCTION__, ['id', 'contact_type']);
+ }
+
+ /**
+ * @return \Civi\Api4\Action\Contact\GetFields
+ */
+ public static function getFields() {
+ return new Action\Contact\GetFields(__CLASS__, __FUNCTION__);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ContactType.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ContactType.php
new file mode 100644
index 00000000..8ce6c8dd
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/ContactType.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * ContactType entity.
+ *
+ * With this entity you can create or update any new or existing Contact type or a sub type
+ * In case of updating existing ContactType, id of that particular ContactType must
+ * be in $params array.
+ *
+ * Creating a new contact type requires at minimum a label and parent_id.
+ *
+ * @package Civi\Api4
+ */
+class ContactType extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Contribution.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Contribution.php
new file mode 100644
index 00000000..903c4753
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Contribution.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Contribution entity.
+ *
+ * @package Civi\Api4
+ */
+class Contribution extends Generic\DAOEntity {
+
+ /**
+ * @return \Civi\Api4\Action\Contribution\Create
+ */
+ public static function create() {
+ return new \Civi\Api4\Action\Contribution\Create(__CLASS__, __FUNCTION__);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomField.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomField.php
new file mode 100644
index 00000000..245c9f4c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomField.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * CustomField entity.
+ *
+ * @package Civi\Api4
+ */
+class CustomField extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomGroup.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomGroup.php
new file mode 100644
index 00000000..780ccd2f
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomGroup.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * CustomGroup entity.
+ *
+ * @package Civi\Api4
+ */
+class CustomGroup extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomValue.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomValue.php
new file mode 100644
index 00000000..9cf4da43
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/CustomValue.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * CustomGroup entity.
+ *
+ * @package Civi\Api4
+ */
+class CustomValue extends Generic\AbstractEntity {
+
+ /**
+ * @param string $customGroup
+ * @return Action\CustomValue\Get
+ */
+ public static function get($customGroup) {
+ return new Action\CustomValue\Get($customGroup, __FUNCTION__);
+ }
+
+ /**
+ * @param string $customGroup
+ * @return Action\CustomValue\GetFields
+ */
+ public static function getFields($customGroup = NULL) {
+ return new Action\CustomValue\GetFields($customGroup, __FUNCTION__);
+ }
+
+ /**
+ * @param string $customGroup
+ * @return Action\CustomValue\Create
+ */
+ public static function create($customGroup) {
+ return new Action\CustomValue\Create($customGroup, __FUNCTION__);
+ }
+
+ /**
+ * @param string $customGroup
+ * @return Action\CustomValue\Update
+ */
+ public static function update($customGroup) {
+ return new Action\CustomValue\Update($customGroup, __FUNCTION__);
+ }
+
+ /**
+ * @param string $customGroup
+ * @return Action\CustomValue\Delete
+ */
+ public static function delete($customGroup) {
+ return new Action\CustomValue\Delete($customGroup, __FUNCTION__);
+ }
+
+ /**
+ * @param string $customGroup
+ * @return Action\CustomValue\Replace
+ */
+ public static function replace($customGroup) {
+ return new Action\CustomValue\Replace($customGroup, __FUNCTION__);
+ }
+
+ /**
+ * @param string $customGroup
+ * @return Action\CustomValue\GetActions
+ */
+ public static function getActions($customGroup = NULL) {
+ return new Action\CustomValue\GetActions($customGroup, __FUNCTION__);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function permissions() {
+ $entity = 'contact';
+ $permissions = \CRM_Core_Permission::getEntityActionPermissions();
+
+ // Merge permissions for this entity with the defaults
+ return \CRM_Utils_Array::value($entity, $permissions, []) + $permissions['default'];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Email.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Email.php
new file mode 100644
index 00000000..cb743e3a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Email.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Email entity.
+ *
+ * This entity allows user to add, update, retrieve or delete emails address(es) of a contact.
+ *
+ * Creating a new email address requires at minimum a contact's ID and email
+ *
+ * @package Civi\Api4
+ */
+class Email extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Entity.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Entity.php
new file mode 100644
index 00000000..bc759d05
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Entity.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Retrieves information about all Api4 entities.
+ *
+ * @package Civi\Api4
+ */
+class Entity extends Generic\AbstractEntity {
+
+ /**
+ * @return Action\Entity\Get
+ */
+ public static function get() {
+ return new Action\Entity\Get('Entity', __FUNCTION__);
+ }
+
+ /**
+ * @return \Civi\Api4\Generic\BasicGetFieldsAction
+ */
+ public static function getFields() {
+ return new \Civi\Api4\Generic\BasicGetFieldsAction('Entity', __FUNCTION__, function() {
+ return [
+ ['name' => 'name'],
+ ['name' => 'description'],
+ ['name' => 'comment'],
+ ];
+ });
+ }
+
+ /**
+ * @return Action\Entity\GetLinks
+ */
+ public static function getLinks() {
+ return new Action\Entity\GetLinks('Entity', __FUNCTION__);
+ }
+
+ /**
+ * @return array
+ */
+ public static function permissions() {
+ return [
+ 'default' => ['access CiviCRM']
+ ];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event.php
new file mode 100644
index 00000000..1a07cc3d
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Event entity.
+ *
+ * @package Civi\Api4
+ */
+class Event extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Events.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Events.php
new file mode 100644
index 00000000..0bf4a993
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Events.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Civi\Api4\Event;
+
+class Events {
+
+ /**
+ * Prepare the specification for a request. Fired from within a request to
+ * get fields.
+ *
+ * @see GetSpecEvent
+ */
+ const GET_SPEC = 'civi.api.get_spec';
+
+ /**
+ * Build the database schema, allow adding of custom joins and tables.
+ */
+ const SCHEMA_MAP_BUILD = 'api.schema_map.build';
+
+ /**
+ * Alter query results of APIv4 select query
+ */
+ const POST_SELECT_QUERY = 'api.select_query.post';
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/GetSpecEvent.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/GetSpecEvent.php
new file mode 100644
index 00000000..cc247853
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/GetSpecEvent.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Civi\Api4\Event;
+
+use Civi\Api4\Generic\AbstractAction;
+use Symfony\Component\EventDispatcher\Event as BaseEvent;
+
+class GetSpecEvent extends BaseEvent {
+ /**
+ * @var AbstractAction
+ */
+ protected $request;
+
+ /**
+ * @param AbstractAction $request
+ */
+ public function __construct(AbstractAction $request) {
+ $this->request = $request;
+ }
+
+ /**
+ * @return AbstractAction
+ */
+ public function getRequest() {
+ return $this->request;
+ }
+
+ /**
+ * @param $request
+ */
+ public function setRequest(AbstractAction $request) {
+ $this->request = $request;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/PostSelectQueryEvent.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/PostSelectQueryEvent.php
new file mode 100644
index 00000000..4489033b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/PostSelectQueryEvent.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Civi\Api4\Event;
+
+use Civi\Api4\Query\Api4SelectQuery;
+use Symfony\Component\EventDispatcher\Event;
+
+class PostSelectQueryEvent extends Event {
+
+ /**
+ * @var array
+ */
+ protected $results;
+
+ /**
+ * @var Api4SelectQuery
+ */
+ protected $query;
+
+ /**
+ * PostSelectQueryEvent constructor.
+ * @param array $results
+ * @param Api4SelectQuery $query
+ */
+ public function __construct(array $results, Api4SelectQuery $query) {
+ $this->results = $results;
+ $this->query = $query;
+ }
+
+ /**
+ * @return array
+ */
+ public function getResults() {
+ return $this->results;
+ }
+
+ /**
+ * @param array $results
+ * @return $this
+ */
+ public function setResults($results) {
+ $this->results = $results;
+
+ return $this;
+ }
+
+ /**
+ * @return Api4SelectQuery
+ */
+ public function getQuery() {
+ return $this->query;
+ }
+
+ /**
+ * @param Api4SelectQuery $query
+ * @return $this
+ */
+ public function setQuery($query) {
+ $this->query = $query;
+
+ return $this;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/SchemaMapBuildEvent.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/SchemaMapBuildEvent.php
new file mode 100644
index 00000000..f79f6b4b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/SchemaMapBuildEvent.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Civi\Api4\Event;
+
+use Civi\Api4\Service\Schema\SchemaMap;
+use Symfony\Component\EventDispatcher\Event as BaseEvent;
+
+class SchemaMapBuildEvent extends BaseEvent {
+ /**
+ * @var SchemaMap
+ */
+ protected $schemaMap;
+
+ /**
+ * @param SchemaMap $schemaMap
+ */
+ public function __construct(SchemaMap $schemaMap) {
+ $this->schemaMap = $schemaMap;
+ }
+
+ /**
+ * @return SchemaMap
+ */
+ public function getSchemaMap() {
+ return $this->schemaMap;
+ }
+
+ /**
+ * @param SchemaMap $schemaMap
+ *
+ * @return $this
+ */
+ public function setSchemaMap($schemaMap) {
+ $this->schemaMap = $schemaMap;
+
+ return $this;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/AbstractPrepareSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/AbstractPrepareSubscriber.php
new file mode 100644
index 00000000..d4725e0d
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/AbstractPrepareSubscriber.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\API\Event\PrepareEvent;
+use Civi\API\Events;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+abstract class AbstractPrepareSubscriber implements EventSubscriberInterface {
+ /**
+ * @return array
+ */
+ public static function getSubscribedEvents() {
+ return [
+ Events::PREPARE => 'onApiPrepare',
+ ];
+ }
+
+ /**
+ * @param PrepareEvent $event
+ */
+ abstract public function onApiPrepare(PrepareEvent $event);
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivityPreCreationSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivityPreCreationSubscriber.php
new file mode 100644
index 00000000..1938ce0b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivityPreCreationSubscriber.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\Api4\Generic\DAOCreateAction;
+use Civi\Api4\OptionValue;
+
+class ActivityPreCreationSubscriber extends PreCreationSubscriber {
+ /**
+ * @param DAOCreateAction $request
+ * @throws \API_Exception
+ * @throws \Exception
+ */
+ protected function modify(DAOCreateAction $request) {
+ $activityType = $request->getValue('activity_type');
+ if ($activityType) {
+ $result = OptionValue::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('name', '=', $activityType)
+ ->addWhere('option_group.name', '=', 'activity_type')
+ ->execute();
+
+ if ($result->count() !== 1) {
+ throw new \Exception('Activity type must match a *single* type');
+ }
+
+ $request->addValue('activity_type_id', $result->first()['value']);
+ }
+ }
+
+ /**
+ * @param DAOCreateAction $request
+ *
+ * @return bool
+ */
+ protected function applies(DAOCreateAction $request) {
+ return $request->getEntityName() === 'Activity';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivitySchemaMapSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivitySchemaMapSubscriber.php
new file mode 100644
index 00000000..52d58397
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ActivitySchemaMapSubscriber.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\Api4\Event\Events;
+use Civi\Api4\Event\SchemaMapBuildEvent;
+use Civi\Api4\Service\Schema\Joinable\ActivityToActivityContactAssigneesJoinable;
+use Civi\Api4\Service\Schema\Joinable\BridgeJoinable;
+use Civi\Api4\Service\Schema\Joinable\Joinable;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use \CRM_Utils_String as StringHelper;
+
+class ActivitySchemaMapSubscriber implements EventSubscriberInterface {
+ /**
+ * @return array
+ */
+ public static function getSubscribedEvents() {
+ return [
+ Events::SCHEMA_MAP_BUILD => 'onSchemaBuild',
+ ];
+ }
+
+ /**
+ * @param SchemaMapBuildEvent $event
+ */
+ public function onSchemaBuild(SchemaMapBuildEvent $event) {
+ $schema = $event->getSchemaMap();
+ $table = $schema->getTableByName('civicrm_activity');
+
+ $middleAlias = StringHelper::createRandom(10, implode(range('a', 'z')));
+ $middleLink = new ActivityToActivityContactAssigneesJoinable($middleAlias);
+
+ $bridge = new BridgeJoinable('civicrm_contact', 'id', 'assignees', $middleLink);
+ $bridge->setBaseTable('civicrm_activity_contact');
+ $bridge->setJoinType(Joinable::JOIN_TYPE_ONE_TO_MANY);
+
+ $table->addTableLink('contact_id', $bridge);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php
new file mode 100644
index 00000000..edea3de6
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\Api4\Event\Events;
+use Civi\Api4\Event\SchemaMapBuildEvent;
+use Civi\Api4\Service\Schema\Joinable\Joinable;
+use Civi\Api4\Service\Schema\Table;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class ContactSchemaMapSubscriber implements EventSubscriberInterface {
+ /**
+ * @return array
+ */
+ public static function getSubscribedEvents() {
+ return [
+ Events::SCHEMA_MAP_BUILD => 'onSchemaBuild',
+ ];
+ }
+
+ /**
+ * @param SchemaMapBuildEvent $event
+ */
+ public function onSchemaBuild(SchemaMapBuildEvent $event) {
+ $schema = $event->getSchemaMap();
+ $table = $schema->getTableByName('civicrm_contact');
+ $this->addCreatedActivitiesLink($table);
+ $this->fixPreferredLanguageAlias($table);
+ }
+
+ /**
+ * @param Table $table
+ */
+ private function addCreatedActivitiesLink($table) {
+ $alias = 'created_activities';
+ $joinable = new Joinable('civicrm_activity_contact', 'contact_id', $alias);
+ $joinable->addCondition($alias . '.record_type_id = 1');
+ $joinable->setJoinType($joinable::JOIN_TYPE_ONE_TO_MANY);
+ $table->addTableLink('id', $joinable);
+ }
+
+ /**
+ * @param Table $table
+ */
+ private function fixPreferredLanguageAlias($table) {
+ foreach ($table->getExternalLinks() as $link) {
+ if ($link->getAlias() === 'languages') {
+ $link->setAlias('preferred_language');
+ return;
+ }
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomFieldPreCreationSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomFieldPreCreationSubscriber.php
new file mode 100644
index 00000000..289e1057
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomFieldPreCreationSubscriber.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\Api4\Generic\DAOCreateAction;
+
+class CustomFieldPreCreationSubscriber extends PreCreationSubscriber {
+
+ const OPTION_TYPE_NEW = 1;
+ const OPTION_STATUS_ACTIVE = 1;
+
+ /**
+ * @param DAOCreateAction $request
+ */
+ public function modify(DAOCreateAction $request) {
+ $this->formatOptionParams($request);
+ $this->setDefaults($request);
+ }
+
+ /**
+ * @param DAOCreateAction $request
+ *
+ * @return bool
+ */
+ protected function applies(DAOCreateAction $request) {
+ return $request->getEntityName() === 'CustomField';
+ }
+
+ /**
+ * Sets defaults required for option group and value creation
+ * @see CRM_Core_BAO_CustomField::create()
+ *
+ * @param DAOCreateAction $request
+ */
+ protected function formatOptionParams(DAOCreateAction $request) {
+ $options = $request->getValue('options');
+
+ if (!is_array($options)) {
+ return;
+ }
+
+ $dataTypeKey = 'data_type';
+ $optionLabelKey = 'option_label';
+ $optionWeightKey = 'option_weight';
+ $optionStatusKey = 'option_status';
+ $optionValueKey = 'option_value';
+ $optionTypeKey = 'option_type';
+
+ $dataType = $request->getValue($dataTypeKey);
+ $optionLabel = $request->getValue($optionLabelKey);
+ $optionWeight = $request->getValue($optionWeightKey);
+ $optionStatus = $request->getValue($optionStatusKey);
+ $optionValue = $request->getValue($optionValueKey);
+ $optionType = $request->getValue($optionTypeKey);
+
+ if (!$optionType) {
+ $request->addValue($optionTypeKey, self::OPTION_TYPE_NEW);
+ }
+
+ if (!$dataType) {
+ $request->addValue($dataTypeKey, 'String');
+ }
+
+ if (!$optionLabel) {
+ $request->addValue($optionLabelKey, array_values($options));
+ }
+
+ if (!$optionValue) {
+ $request->addValue($optionValueKey, array_keys($options));
+ }
+
+ if (!$optionStatus) {
+ $statuses = array_fill(0, count($options), self::OPTION_STATUS_ACTIVE);
+ $request->addValue($optionStatusKey, $statuses);
+ }
+
+ if (!$optionWeight) {
+ $request->addValue($optionWeightKey, range(1, count($options)));
+ }
+ }
+
+ /**
+ * @param DAOCreateAction $request
+ */
+ private function setDefaults(DAOCreateAction $request) {
+ if (!$request->getValue('option_type')) {
+ $request->addValue('option_type', NULL);
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomGroupPreCreationSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomGroupPreCreationSubscriber.php
new file mode 100644
index 00000000..70f6e426
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/CustomGroupPreCreationSubscriber.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\Api4\Generic\DAOCreateAction;
+
+class CustomGroupPreCreationSubscriber extends PreCreationSubscriber {
+ /**
+ * @param DAOCreateAction $request
+ */
+ protected function modify(DAOCreateAction $request) {
+ $extends = $request->getValue('extends');
+ $title = $request->getValue('title');
+ $name = $request->getValue('name');
+
+ if (is_string($extends)) {
+ $request->addValue('extends', [$extends]);
+ }
+
+ if (NULL === $title && $name) {
+ $request->addValue('title', $name);
+ }
+ }
+
+ protected function applies(DAOCreateAction $request) {
+ return $request->getEntityName() === 'CustomGroup';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/OptionValuePreCreationSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/OptionValuePreCreationSubscriber.php
new file mode 100644
index 00000000..3e671d61
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/OptionValuePreCreationSubscriber.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\Api4\Generic\DAOCreateAction;
+use Civi\Api4\OptionGroup;
+
+class OptionValuePreCreationSubscriber extends PreCreationSubscriber {
+
+ /**
+ * @param DAOCreateAction $request
+ */
+ protected function modify(DAOCreateAction $request) {
+ $this->setOptionGroupId($request);
+ }
+
+ /**
+ * @param DAOCreateAction $request
+ *
+ * @return bool
+ */
+ protected function applies(DAOCreateAction $request) {
+ return $request->getEntityName() === 'OptionValue';
+ }
+
+ /**
+ * @param DAOCreateAction $request
+ * @throws \API_Exception
+ * @throws \Exception
+ */
+ private function setOptionGroupId(DAOCreateAction $request) {
+ $optionGroupName = $request->getValue('option_group');
+ if (!$optionGroupName || $request->getValue('option_group_id')) {
+ return;
+ }
+
+ $optionGroup = OptionGroup::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('id')
+ ->addWhere('name', '=', $optionGroupName)
+ ->execute();
+
+ if ($optionGroup->count() !== 1) {
+ throw new \Exception('Option group name must match only a single group');
+ }
+
+ $request->addValue('option_group_id', $optionGroup->first()['id']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PermissionCheckSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PermissionCheckSubscriber.php
new file mode 100644
index 00000000..62d542d0
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PermissionCheckSubscriber.php
@@ -0,0 +1,65 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2017 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\API\Events;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * For any API requests that correspond to a Doctrine entity
+ * ($apiRequest['doctrineClass']), check permissions specified in
+ * Civi\API\Annotation\Permission.
+ */
+class PermissionCheckSubscriber implements EventSubscriberInterface {
+ /**
+ * @return array
+ */
+ public static function getSubscribedEvents() {
+ return [
+ Events::AUTHORIZE => [
+ ['onApiAuthorize', Events::W_LATE],
+ ],
+ ];
+ }
+
+ /**
+ * @param \Civi\API\Event\AuthorizeEvent $event
+ * API authorization event.
+ */
+ public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) {
+ /* @var \Civi\Api4\Generic\AbstractAction $apiRequest */
+ $apiRequest = $event->getApiRequest();
+ if ($apiRequest['version'] == 4) {
+ if (!$apiRequest->getCheckPermissions() || $apiRequest->isAuthorized()) {
+ $event->authorize();
+ $event->stopPropagation();
+ }
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php
new file mode 100644
index 00000000..ff7e6d20
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php
@@ -0,0 +1,369 @@
+<?php
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\Api4\Event\Events;
+use Civi\Api4\Event\PostSelectQueryEvent;
+use Civi\Api4\Query\Api4SelectQuery;
+use Civi\Api4\Service\Schema\Joinable\Joinable;
+use Civi\Api4\Utils\ArrayInsertionUtil;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Changes the results of a select query, doing 1-n joins and unserializing data
+ */
+class PostSelectQuerySubscriber implements EventSubscriberInterface {
+
+ /**
+ * @inheritdoc
+ */
+ public static function getSubscribedEvents() {
+ return [
+ Events::POST_SELECT_QUERY => 'onPostQuery'
+ ];
+ }
+
+ /**
+ * @param PostSelectQueryEvent $event
+ */
+ public function onPostQuery(PostSelectQueryEvent $event) {
+ $results = $event->getResults();
+ $event->setResults($this->postRun($results, $event->getQuery()));
+ }
+
+ /**
+ * @param array $results
+ * @param Api4SelectQuery $query
+ *
+ * @return array
+ */
+ protected function postRun(array $results, Api4SelectQuery $query) {
+ if (empty($results)) {
+ return $results;
+ }
+
+ $fieldSpec = $query->getApiFieldSpec();
+ $this->unserializeFields($results, $query->getEntity(), $fieldSpec);
+
+ // Group the selects to avoid queries for each field
+ $groupedSelects = $this->getJoinedDotSelects($query);
+ foreach ($groupedSelects as $finalAlias => $selects) {
+ $joinPath = $this->getJoinPathInfo($selects[0], $query);
+ $selects = $this->formatSelects($finalAlias, $selects, $query);
+ $joinResults = $this->getJoinResults($query, $finalAlias, $selects);
+ $this->formatJoinResults($joinResults, $query, $finalAlias);
+
+ // Insert join results into original result
+ foreach ($results as &$primaryResult) {
+ $baseId = $primaryResult['id'];
+ $filtered = array_filter($joinResults, function ($res) use ($baseId) {
+ return ($res['_base_id'] === $baseId);
+ });
+ $filtered = array_values($filtered);
+ ArrayInsertionUtil::insert($primaryResult, $joinPath, $filtered);
+ }
+ }
+
+ return array_values($results);
+ }
+
+ /**
+ * @param array $joinResults
+ * @param Api4SelectQuery $query
+ * @param string $alias
+ */
+ private function formatJoinResults(&$joinResults, $query, $alias) {
+ $join = $query->getJoinedTable($alias);
+ $fields = [];
+ foreach ($join->getEntityFields() as $field) {
+ $name = explode('.', $field->getName());
+ $fields[array_pop($name)] = $field->toArray();
+ }
+ if ($fields) {
+ $this->unserializeFields($joinResults, NULL, $fields);
+ }
+ }
+
+ /**
+ * Unserialize values
+ *
+ * @param array $results
+ * @param string $entity
+ * @param array $fields
+ */
+ protected function unserializeFields(&$results, $entity, $fields = []) {
+ if (empty($fields)) {
+ $params = ['action' => 'get', 'includeCustom' => FALSE];
+ $fields = civicrm_api4($entity, 'getFields', $params)->indexBy('name');
+ }
+
+ foreach ($results as &$result) {
+ foreach ($result as $field => &$value) {
+ if (!empty($fields[$field]['serialize']) && is_string($value)) {
+ $serializationType = $fields[$field]['serialize'];
+ $value = \CRM_Core_DAO::unSerializeField($value, $serializationType);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param Api4SelectQuery $query
+ *
+ * @return array
+ */
+ private function getJoinedDotSelects(Api4SelectQuery $query) {
+ // Remove selects that are not in a joined table
+ $fkAliases = $query->getFkSelectAliases();
+ $joinedDotSelects = array_filter(
+ $query->getSelect(),
+ function ($select) use ($fkAliases) {
+ return isset($fkAliases[$select]);
+ }
+ );
+
+ $selects = [];
+ // group related selects by alias so they can be executed in one query
+ foreach ($joinedDotSelects as $select) {
+ $parts = explode('.', $select);
+ $finalAlias = $parts[count($parts) - 2];
+ $selects[$finalAlias][] = $select;
+ }
+
+ // sort by depth, e.g. email selects should be done before email.location
+ uasort($selects, function ($a, $b) {
+ $aFirst = $a[0];
+ $bFirst = $b[0];
+ return substr_count($aFirst, '.') > substr_count($bFirst, '.');
+ });
+
+ return $selects;
+ }
+
+
+ /**
+ * @param array $selects
+ * @param $serializationType
+ * @param Api4SelectQuery $query
+ *
+ * @return array
+ */
+ private function getResultsForSerializedField(
+ array $selects,
+ $serializationType,
+ Api4SelectQuery $query
+ ) {
+ // Get the alias (Selects are grouped and all target the same table)
+ $sampleField = current($selects);
+ $alias = strstr($sampleField, '.', TRUE);
+
+ // Fetch the results with the serialized field
+ $selects['serialized'] = $query::MAIN_TABLE_ALIAS . '.' . $alias;
+ $serializedResults = $this->runWithNewSelects($selects, $query);
+ $newResults = [];
+
+ // Create a new results array, with a separate entry for each option value
+ foreach ($serializedResults as $result) {
+ $optionValues = \CRM_Core_DAO::unSerializeField(
+ $result['serialized'],
+ $serializationType
+ );
+ unset($result['serialized']);
+ foreach ($optionValues as $value) {
+ $newResults[] = array_merge($result, ['value' => $value]);
+ }
+ }
+
+ $optionValueValues = array_unique(array_column($newResults, 'value'));
+ $optionValues = $this->getOptionValuesFromValues(
+ $selects,
+ $query,
+ $optionValueValues
+ );
+ $valueField = $alias . '.value';
+
+ // Index by value
+ foreach ($optionValues as $key => $subResult) {
+ $optionValues[$subResult['value']] = $subResult;
+ unset($subResult[$key]);
+
+ // Exclude 'value' if not in original selects
+ if (!in_array($valueField, $selects)) {
+ unset($optionValues[$subResult['value']]['value']);
+ }
+ }
+
+ // Replace serialized with the sub-select results
+ foreach ($newResults as &$result) {
+ $result = array_merge($result, $optionValues[$result['value']]);
+ unset($result['value']);
+ }
+
+ return $newResults;
+ }
+
+
+ /**
+ * Prepares selects for the subquery to fetch join results
+ *
+ * @param string $alias
+ * @param array $selects
+ * @param Api4SelectQuery $query
+ *
+ * @return array
+ */
+ private function formatSelects($alias, $selects, Api4SelectQuery $query) {
+ $mainAlias = $query::MAIN_TABLE_ALIAS;
+ $selectFields = [];
+
+ foreach ($selects as $select) {
+ $selectAlias = $query->getFkSelectAliases()[$select];
+ $fieldAlias = substr($select, strrpos($select, '.') + 1);
+ $selectFields[$fieldAlias] = $selectAlias;
+ }
+
+ $firstSelect = $selects[0];
+ $pathParts = explode('.', $firstSelect);
+ $numParts = count($pathParts);
+ $parentAlias = $numParts > 2 ? $pathParts[$numParts - 3] : $mainAlias;
+
+ $selectFields['id'] = sprintf('%s.id', $alias);
+ $selectFields['_parent_id'] = $parentAlias . '.id';
+ $selectFields['_base_id'] = $mainAlias . '.id';
+
+ return $selectFields;
+ }
+
+ /**
+ * @param array $selects
+ * @param Api4SelectQuery $query
+ *
+ * @return array
+ */
+ private function runWithNewSelects(array $selects, Api4SelectQuery $query) {
+ $aliasedSelects = array_map(function ($field, $alias) {
+ return sprintf('%s as "%s"', $field, $alias);
+ }, $selects, array_keys($selects));
+
+ $newSelect = sprintf('SELECT DISTINCT %s', implode(", ", $aliasedSelects));
+ $sql = str_replace("\n", ' ', $query->getQuery()->toSQL());
+ $originalSelect = substr($sql, 0, strpos($sql, ' FROM'));
+ $sql = str_replace($originalSelect, $newSelect, $sql);
+
+ $relatedResults = [];
+ $resultDAO = \CRM_Core_DAO::executeQuery($sql);
+ while ($resultDAO->fetch()) {
+ $relatedResult = [];
+ foreach ($selects as $alias => $column) {
+ $returnName = $alias;
+ $alias = str_replace('.', '_', $alias);
+ if (property_exists($resultDAO, $alias)) {
+ $relatedResult[$returnName] = $resultDAO->$alias;
+ }
+ };
+ $relatedResults[] = $relatedResult;
+ }
+
+ return $relatedResults;
+ }
+
+ /**
+ * @param Api4SelectQuery $query
+ * @param $alias
+ * @param $selects
+ * @return array
+ */
+ protected function getJoinResults(Api4SelectQuery $query, $alias, $selects) {
+ $apiFieldSpec = $query->getApiFieldSpec();
+ if (!empty($apiFieldSpec[$alias]['serialize'])) {
+ $type = $apiFieldSpec[$alias]['serialize'];
+ $joinResults = $this->getResultsForSerializedField($selects, $type, $query);
+ }
+ else {
+ $joinResults = $this->runWithNewSelects($selects, $query);
+ }
+
+ // Remove results with no matching entries
+ $joinResults = array_filter($joinResults, function ($result) {
+ return !empty($result['id']);
+ });
+
+ return $joinResults;
+ }
+
+ /**
+ * Separates a string like 'emails.location_type.label' into an array, where
+ * each value in the array tells whether it is 1-1 or 1-n join type
+ *
+ * @param string $pathString
+ * Dot separated path to the field
+ * @param Api4SelectQuery $query
+ *
+ * @return array
+ * Index is table alias and value is boolean whether is 1-to-many join
+ */
+ private function getJoinPathInfo($pathString, $query) {
+ $pathParts = explode('.', $pathString);
+ array_pop($pathParts); // remove field
+ $path = [];
+ $isMultipleChecker = function($alias) use ($query) {
+ foreach ($query->getJoinedTables() as $table) {
+ if ($table->getAlias() === $alias) {
+ return $table->getJoinType() === Joinable::JOIN_TYPE_ONE_TO_MANY;
+ }
+ }
+ return FALSE;
+ };
+
+ foreach ($pathParts as $part) {
+ $path[$part] = $isMultipleChecker($part);
+ }
+
+ return $path;
+ }
+
+ /**
+ * Get all the option_value values required in the query
+ *
+ * @param array $selects
+ * @param Api4SelectQuery $query
+ * @param array $values
+ *
+ * @return array
+ */
+ private function getOptionValuesFromValues(
+ array $selects,
+ Api4SelectQuery $query,
+ array $values
+ ) {
+ $sampleField = current($selects);
+ $alias = strstr($sampleField, '.', TRUE);
+
+ // Get the option value table that was joined
+ $relatedTable = NULL;
+ foreach ($query->getJoinedTables() as $joinedTable) {
+ if ($joinedTable->getAlias() === $alias) {
+ $relatedTable = $joinedTable;
+ }
+ }
+
+ // We only want subselects related to the joined table
+ $subSelects = array_filter($selects, function ($select) use ($alias) {
+ return strpos($select, $alias) === 0;
+ });
+
+ // Fetch all related option_value entries
+ $valueField = $alias . '.value';
+ $subSelects[] = $valueField;
+ $tableName = $relatedTable->getTargetTable();
+ $conditions = $relatedTable->getExtraJoinConditions();
+ $conditions[] = $valueField . ' IN ("' . implode('", "', $values) . '")';
+ $subQuery = new \CRM_Utils_SQL_Select($tableName . ' ' . $alias);
+ $subQuery->where($conditions);
+ $subQuery->select($subSelects);
+ $subResults = $subQuery->execute()->fetchAll();
+
+ return $subResults;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PreCreationSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PreCreationSubscriber.php
new file mode 100644
index 00000000..6737a9f3
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/PreCreationSubscriber.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\API\Event\PrepareEvent;
+use Civi\Api4\Generic\DAOCreateAction;
+
+abstract class PreCreationSubscriber extends AbstractPrepareSubscriber {
+ /**
+ * @param PrepareEvent $event
+ */
+ public function onApiPrepare(PrepareEvent $event) {
+ $apiRequest = $event->getApiRequest();
+ if (!$apiRequest instanceof DAOCreateAction) {
+ return;
+ }
+
+ $this->addDefaultCreationValues($apiRequest);
+ if ($this->applies($apiRequest)) {
+ $this->modify($apiRequest);
+ }
+ }
+
+ /**
+ * Modify the request
+ *
+ * @param DAOCreateAction $request
+ *
+ * @return void
+ */
+ abstract protected function modify(DAOCreateAction $request);
+
+ /**
+ * Check if this subscriber should be applied to the request
+ *
+ * @param DAOCreateAction $request
+ *
+ * @return bool
+ */
+ abstract protected function applies(DAOCreateAction $request);
+
+ /**
+ * Sets default values common to all creation requests
+ *
+ * @param DAOCreateAction $request
+ */
+ protected function addDefaultCreationValues(DAOCreateAction $request) {
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php
new file mode 100644
index 00000000..5e2c6f33
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php
@@ -0,0 +1,97 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2017 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\API\Event\PrepareEvent;
+
+/**
+ * Validate field inputs based on annotations in the action class
+ */
+class ValidateFieldsSubscriber extends AbstractPrepareSubscriber {
+
+ /**
+ * @param PrepareEvent $event
+ * @throws \Exception
+ */
+ public function onApiPrepare(PrepareEvent $event) {
+ /** @var \Civi\Api4\Generic\AbstractAction $apiRequest */
+ $apiRequest = $event->getApiRequest();
+ if (is_a($apiRequest, 'Civi\Api4\Generic\AbstractAction')) {
+ $paramInfo = $apiRequest->getParamInfo();
+ foreach ($paramInfo as $param => $info) {
+ $getParam = 'get' . ucfirst($param);
+ $value = $apiRequest->$getParam();
+ // Required fields
+ if (!empty($info['required']) && (!$value && $value !== 0 && $value !== '0')) {
+ throw new \API_Exception('Parameter "' . $param . '" is required.');
+ }
+ if (!empty($info['type']) && !self::checkType($value, $info['type'])) {
+ throw new \API_Exception('Parameter "' . $param . '" is not of the correct type. Expecting ' . implode(' or ', $info['type']) . '.');
+ }
+ }
+ }
+ }
+
+ /**
+ * Validate variable type on input
+ *
+ * @param $value
+ * @param $types
+ * @return bool
+ * @throws \API_Exception
+ */
+ public static function checkType($value, $types) {
+ if ($value === NULL) {
+ return TRUE;
+ }
+ foreach ($types as $type) {
+ switch ($type) {
+ case 'array':
+ case 'bool':
+ case 'string':
+ case 'object':
+ $tester = 'is_' . $type;
+ if ($tester($value)) {
+ return TRUE;
+ }
+ break;
+
+ case 'int':
+ if (\CRM_Utils_Rule::integer($value)) {
+ return TRUE;
+ }
+ break;
+
+ default:
+ throw new \API_Exception('Unknown parameter type: ' . $type);
+ }
+ }
+ return FALSE;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractAction.php
new file mode 100644
index 00000000..1b0786ea
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractAction.php
@@ -0,0 +1,391 @@
+<?php
+namespace Civi\Api4\Generic;
+
+use Civi\API\Exception\UnauthorizedException;
+use Civi\API\Kernel;
+use Civi\Api4\Generic\Result;
+use Civi\Api4\Utils\ReflectionUtils;
+
+/**
+ * Base class for all api actions.
+ *
+ * @method $this setCheckPermissions(bool $value)
+ * @method bool getCheckPermissions()
+ * @method $this setChain(array $chain)
+ * @method array getChain()
+ */
+abstract class AbstractAction implements \ArrayAccess {
+
+ /**
+ * Api version number; cannot be changed.
+ *
+ * @var int
+ */
+ protected $version = 4;
+
+ /**
+ * Additional api requests - will be called once per result.
+ *
+ * Keys can be any string - this will be the name given to the output.
+ *
+ * You can reference other values in the api results in this call by prefixing them with $
+ *
+ * For example, you could create a contact and place them in a group by chaining the
+ * GroupContact api to the Contact api:
+ *
+ * Contact::create()
+ * ->setValue('first_name', 'Hello')
+ * ->addChain('add_to_a_group', GroupContact::create()->setValue('contact_id', '$id')->setValue('group_id', 123))
+ *
+ * This will substitute the id of the newly created contact with $id.
+ *
+ * @var array
+ */
+ protected $chain = [];
+
+ /**
+ * Whether to enforce acl permissions based on the current user.
+ *
+ * Setting to FALSE will disable permission checks and override ACLs.
+ * In REST/javascript this cannot be disabled.
+ *
+ * @var bool
+ */
+ protected $checkPermissions = TRUE;
+
+ /* @var string */
+ protected $_entityName;
+
+ /* @var string */
+ protected $_actionName;
+
+ /* @var \ReflectionClass */
+ private $thisReflection;
+
+ /* @var array */
+ private $thisParamInfo;
+
+ /* @var array */
+ private $entityFields;
+
+ /* @var array */
+ private $thisArrayStorage;
+
+ /**
+ * Action constructor.
+ *
+ * @param string $entityName
+ * @param string $actionName
+ * @throws \API_Exception
+ */
+ public function __construct($entityName, $actionName) {
+ // If a namespaced class name is passed in
+ if (strpos($entityName, '\\') !== FALSE) {
+ $entityName = substr($entityName, strrpos($entityName, '\\') + 1);
+ }
+ $this->_entityName = $entityName;
+ $this->_actionName = $actionName;
+ }
+
+ /**
+ * Strictly enforce api parameters
+ * @param $name
+ * @param $value
+ * @throws \Exception
+ */
+ public function __set($name, $value) {
+ throw new \API_Exception('Unknown api parameter');
+ }
+
+ /**
+ * @param int $val
+ * @return $this
+ * @throws \API_Exception
+ */
+ public function setVersion($val) {
+ if ($val != 4) {
+ throw new \API_Exception('Cannot modify api version');
+ }
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * Unique name for this chained request
+ * @param \Civi\Api4\Generic\AbstractAction $apiRequest
+ * @param string|int $index
+ * Either a string for how the results should be indexed e.g. 'name'
+ * or the index of a single result to return e.g. 0 for the first result.
+ * @return $this
+ */
+ public function addChain($name, AbstractAction $apiRequest, $index = NULL) {
+ $this->chain[$name] = [$apiRequest->getEntityName(), $apiRequest->getActionName(), $apiRequest->getParams(), $index];
+ return $this;
+ }
+
+ /**
+ * Magic function to provide addFoo, getFoo and setFoo for params.
+ *
+ * @param $name
+ * @param $arguments
+ * @return static|mixed
+ * @throws \API_Exception
+ */
+ public function __call($name, $arguments) {
+ $param = lcfirst(substr($name, 3));
+ if (!$param || $param[0] == '_') {
+ throw new \API_Exception('Unknown api parameter: ' . $name);
+ }
+ $mode = substr($name, 0, 3);
+ // Handle plural when adding to e.g. $values with "addValue" method.
+ if ($mode == 'add' && $this->paramExists($param . 's')) {
+ $param .= 's';
+ }
+ if ($this->paramExists($param)) {
+ switch ($mode) {
+ case 'get':
+ return $this->$param;
+
+ case 'set':
+ $this->$param = $arguments[0];
+ return $this;
+
+ case 'add':
+ if (!is_array($this->$param)) {
+ throw new \API_Exception('Cannot add to non-array param');
+ }
+ if (array_key_exists(1, $arguments)) {
+ $this->{$param}[$arguments[0]] = $arguments[1];
+ }
+ else {
+ $this->{$param}[] = $arguments[0];
+ }
+ return $this;
+ }
+ }
+ throw new \API_Exception('Unknown api parameter: ' . $name);
+ }
+
+ /**
+ * Invoke api call.
+ *
+ * At this point all the params have been sent in and we initiate the api call & return the result.
+ * This is basically the outer wrapper for api v4.
+ *
+ * @return Result|array
+ * @throws UnauthorizedException
+ */
+ final public function execute() {
+ /** @var Kernel $kernel */
+ $kernel = \Civi::service('civi_api_kernel');
+
+ return $kernel->runRequest($this);
+ }
+
+ /**
+ * @param \Civi\Api4\Generic\Result $result
+ */
+ abstract public function _run(Result $result);
+
+ /**
+ * Serialize this object's params into an array
+ * @return array
+ */
+ public function getParams() {
+ $params = [];
+ foreach ($this->getReflection()->getProperties(\ReflectionProperty::IS_PROTECTED) as $property) {
+ $name = $property->getName();
+ // Skip variables starting with an underscore
+ if ($name[0] != '_') {
+ $params[$name] = $this->$name;
+ }
+ }
+ return $params;
+ }
+
+ /**
+ * Get documentation for one or all params
+ *
+ * @param string $param
+ * @return array of arrays [description, type, default, (comment)]
+ */
+ public function getParamInfo($param = NULL) {
+ if (!isset($this->thisParamInfo)) {
+ $defaults = $this->getParamDefaults();
+ foreach ($this->getReflection()->getProperties(\ReflectionProperty::IS_PROTECTED) as $property) {
+ $name = $property->getName();
+ if ($name != 'version' && $name[0] != '_') {
+ $this->thisParamInfo[$name] = ReflectionUtils::getCodeDocs($property, 'Property');
+ $this->thisParamInfo[$name]['default'] = $defaults[$name];
+ }
+ }
+ }
+ return $param ? $this->thisParamInfo[$param] : $this->thisParamInfo;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEntityName() {
+ return $this->_entityName;
+ }
+
+ /**
+ *
+ * @return string
+ */
+ public function getActionName() {
+ return $this->_actionName;
+ }
+
+ /**
+ * @param string $param
+ * @return bool
+ */
+ protected function paramExists($param) {
+ return array_key_exists($param, $this->getParams());
+ }
+
+ /**
+ * @return array
+ */
+ protected function getParamDefaults() {
+ return array_intersect_key($this->getReflection()->getDefaultProperties(), $this->getParams());
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function offsetExists($offset) {
+ return in_array($offset, ['entity', 'action', 'params', 'version', 'check_permissions']) || isset($this->thisArrayStorage[$offset]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function &offsetGet($offset) {
+ $val = NULL;
+ if (in_array($offset, ['entity', 'action'])) {
+ $offset .= 'Name';
+ }
+ if (in_array($offset, ['entityName', 'actionName', 'params', 'version'])) {
+ $getter = 'get' . ucfirst($offset);
+ $val = $this->$getter();
+ return $val;
+ }
+ if ($offset == 'check_permissions') {
+ return $this->checkPermissions;
+ }
+ if (isset ($this->thisArrayStorage[$offset])) {
+ return $this->thisArrayStorage[$offset];
+ }
+ return $val;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function offsetSet($offset, $value) {
+ if (in_array($offset, ['entity', 'action', 'entityName', 'actionName', 'params', 'version'])) {
+ throw new \API_Exception('Cannot modify api4 state via array access');
+ }
+ if ($offset == 'check_permissions') {
+ $this->setCheckPermissions($value);
+ }
+ else {
+ $this->thisArrayStorage[$offset] = $value;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function offsetUnset($offset) {
+ if (in_array($offset, ['entity', 'action', 'entityName', 'actionName', 'params', 'check_permissions', 'version'])) {
+ throw new \API_Exception('Cannot modify api4 state via array access');
+ }
+ unset($this->thisArrayStorage[$offset]);
+ }
+
+ /**
+ * Is this api call permitted?
+ *
+ * This function is called if checkPermissions is set to true.
+ *
+ * @return bool
+ */
+ public function isAuthorized() {
+ $permissions = $this->getPermissions();
+ return \CRM_Core_Permission::check($permissions);
+ }
+
+ public function getPermissions() {
+ $permissions = call_user_func(["\\Civi\\Api4\\" . $this->_entityName, 'permissions']);
+ $permissions += [
+ // applies to getFields, getActions, etc.
+ 'meta' => ['access CiviCRM'],
+ // catch-all, applies to create, get, delete, etc.
+ 'default' => ['administer CiviCRM'],
+ ];
+ $action = $this->getActionName();
+ if (isset($permissions[$action])) {
+ return $permissions[$action];
+ }
+ elseif (in_array($action, ['getActions', 'getFields'])) {
+ return $permissions['meta'];
+ }
+ return $permissions['default'];
+ }
+
+ /**
+ * Returns schema fields for this entity & action.
+ *
+ * @return array
+ * @throws \API_Exception
+ */
+ protected function getEntityFields() {
+ if (!$this->entityFields) {
+ $params = [
+ 'action' => $this->getActionName(),
+ 'checkPermissions' => $this->checkPermissions,
+ ];
+ if (method_exists($this, 'getBaoName')) {
+ $params['includeCustom'] = FALSE;
+ }
+ $this->entityFields = (array) civicrm_api4($this->getEntityName(), 'getFields', $params, 'name');
+ }
+ return $this->entityFields;
+ }
+
+ /**
+ * @return \ReflectionClass
+ */
+ protected function getReflection() {
+ if (!$this->thisReflection) {
+ $this->thisReflection = new \ReflectionClass($this);
+ }
+ return $this->thisReflection;
+ }
+
+ /**
+ * This function is used internally for evaluating field annotations.
+ *
+ * It should never be passed raw user input.
+ *
+ * @param string $expr
+ * Conditional in php format e.g. $foo > $bar
+ * @param array $vars
+ * Variable name => value
+ * @return bool
+ * @throws \API_Exception
+ * @throws \Exception
+ */
+ protected function evaluateCondition($expr, $vars) {
+ if (strpos($expr, '}') !== FALSE || strpos($expr, '{') !== FALSE) {
+ throw new \API_Exception('Illegal character in expression');
+ }
+ $tpl = "{if $expr}1{else}0{/if}";
+ return (bool) trim(\CRM_Core_Smarty::singleton()->fetchWith('string:' . $tpl, $vars));
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractBatchAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractBatchAction.php
new file mode 100644
index 00000000..91c3b461
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractBatchAction.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+/**
+ * Base class for all batch actions (Update, Delete, Replace).
+ *
+ * This differs from the AbstractQuery class in that the "Where" clause is required.
+ *
+ * @package Civi\Api4\Generic
+ */
+abstract class AbstractBatchAction extends AbstractQueryAction {
+
+ /**
+ * Criteria for selecting items to process.
+ *
+ * @required
+ * @var array
+ */
+ protected $where = [];
+
+ /**
+ * @var array
+ */
+ private $select;
+
+ /**
+ * QueryAction constructor.
+ * @param string $entityName
+ * @param string $actionName
+ * @param string|array $select
+ * One or more fields to load for each item.
+ */
+ public function __construct($entityName, $actionName, $select = 'id') {
+ $this->select = (array) $select;
+ parent::__construct($entityName, $actionName);
+ }
+
+ /**
+ * @return array
+ */
+ protected function getBatchRecords() {
+ $params = [
+ 'checkPermissions' => $this->checkPermissions,
+ 'where' => $this->where,
+ 'orderBy' => $this->orderBy,
+ 'limit' => $this->limit,
+ 'offset' => $this->offset,
+ ];
+ if (empty($this->reload)) {
+ $params['select'] = $this->select;
+ }
+
+ return (array) civicrm_api4($this->getEntityName(), 'get', $params);
+ }
+
+ /**
+ * @return array
+ */
+ protected function getSelect() {
+ return $this->select;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractCreateAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractCreateAction.php
new file mode 100644
index 00000000..0cb55d10
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractCreateAction.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+/**
+ * Base class for all "Create" api actions.
+ *
+ * @method $this setValues(array $values) Set all field values from an array of key => value pairs.
+ * @method $this addValue($field, $value) Set field value.
+ * @method array getValues() Get field values.
+ *
+ * @package Civi\Api4\Generic
+ */
+abstract class AbstractCreateAction extends AbstractAction {
+
+ /**
+ * Field values to set
+ *
+ * @var array
+ */
+ protected $values = [];
+
+ /**
+ * @param string $key
+ *
+ * @return mixed|null
+ */
+ public function getValue($key) {
+ return isset($this->values[$key]) ? $this->values[$key] : NULL;
+ }
+
+ /**
+ * @throws \API_Exception
+ */
+ protected function validateValues() {
+ $unmatched = [];
+ $params = NULL;
+ foreach ($this->getEntityFields() as $fieldName => $fieldInfo) {
+ if (!$this->getValue($fieldName)) {
+ if (!empty($fieldInfo['required']) && !isset($fieldInfo['default_value'])) {
+ $unmatched[] = $fieldName;
+ }
+ elseif (!empty($fieldInfo['required_if'])) {
+ $params = $params ?: $this->getParams();
+ if ($this->evaluateCondition($fieldInfo['required_if'], $params)) {
+ $unmatched[] = $fieldName;
+ }
+ }
+ }
+ }
+ if ($unmatched) {
+ throw new \API_Exception("Mandatory values missing from Api4 {$this->getEntityName()}::{$this->getActionName()}: '" . implode("', '", $unmatched) . "'", "mandatory_missing", ["fields" => $unmatched]);
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractEntity.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractEntity.php
new file mode 100644
index 00000000..e774baca
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractEntity.php
@@ -0,0 +1,88 @@
+<?php
+namespace Civi\Api4\Generic;
+
+use Civi\API\Exception\NotImplementedException;
+
+/**
+ * Base class for all api entities.
+ *
+ * When adding your own api from an extension, extend this class only
+ * if your entity does not have an associated DAO. Otherwise extend DAOEntity.
+ *
+ * The recommended way to create a non-DAO-based api is to extend this class
+ * and then add a getFields function and any other actions you wish, e.g.
+ * - a get() function which returns BasicGetAction using your custom getter callback
+ * - a create() function which returns BasicCreateAction using your custom setter callback
+ * - an update() function which returns BasicUpdateAction using your custom setter callback
+ * - a delete() function which returns BasicBatchAction using your custom delete callback
+ * - a replace() function which returns BasicReplaceAction (no callback needed but
+ * depends on the existence of get, create, update & delete actions)
+ *
+ * Note that you can use the same setter callback function for update as create -
+ * that function can distinguish between new & existing records by checking if the
+ * unique identifier has been set (identifier field defaults to "id" but you can change
+ * that when constructing BasicUpdateAction)
+ */
+abstract class AbstractEntity {
+
+ /**
+ * @return \Civi\Api4\Action\GetActions
+ */
+ public static function getActions() {
+ return new \Civi\Api4\Action\GetActions(self::getEntityName(), __FUNCTION__);
+ }
+
+ /**
+ * Should return \Civi\Api4\Generic\BasicGetFieldsAction
+ * @todo make this function abstract when we require php 7.
+ * @throws \Civi\API\Exception\NotImplementedException
+ */
+ public static function getFields() {
+ throw new NotImplementedException(self::getEntityName() . ' should implement getFields action.');
+ }
+
+ /**
+ * Returns a list of permissions needed to access the various actions in this api.
+ *
+ * @return array
+ */
+ public static function permissions() {
+ $permissions = \CRM_Core_Permission::getEntityActionPermissions();
+
+ // For legacy reasons the permissions are keyed by lowercase entity name
+ $lcentity = _civicrm_api_get_entity_name_from_camel(self::getEntityName());
+ // Merge permissions for this entity with the defaults
+ return \CRM_Utils_Array::value($lcentity, $permissions, []) + $permissions['default'];
+ }
+
+ /**
+ * Get entity name from called class
+ *
+ * @return string
+ */
+ protected static function getEntityName() {
+ return substr(static::class, strrpos(static::class, '\\') + 1);
+ }
+
+ /**
+ * Magic method to return the action object for an api.
+ *
+ * @param string $action
+ * @param null $args
+ * @return AbstractAction
+ * @throws NotImplementedException
+ */
+ public static function __callStatic($action, $args) {
+ $entity = self::getEntityName();
+ // Find class for this action
+ $entityAction = "\\Civi\\Api4\\Action\\$entity\\" . ucfirst($action);
+ if (class_exists($entityAction)) {
+ $actionObject = new $entityAction($entity, $action);
+ }
+ else {
+ throw new NotImplementedException("Api $entity $action version 4 does not exist.");
+ }
+ return $actionObject;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractGetAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractGetAction.php
new file mode 100644
index 00000000..f8374cf4
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractGetAction.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+/**
+ * Base class for all "Get" api actions.
+ *
+ * @package Civi\Api4\Generic
+ *
+ * @method $this addSelect(string $select)
+ * @method $this setSelect(array $selects)
+ * @method array getSelect()
+ */
+abstract class AbstractGetAction extends AbstractQueryAction {
+
+ /**
+ * Fields to return. Defaults to all non-custom fields.
+ *
+ * @var array
+ */
+ protected $select = [];
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractQueryAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractQueryAction.php
new file mode 100644
index 00000000..993383dc
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractQueryAction.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+/**
+ * Base class for all actions that need to fetch records (Get, Update, Delete, etc)
+ *
+ * @package Civi\Api4\Generic
+ *
+ * @method $this setWhere(array $wheres)
+ * @method array getWhere()
+ * @method $this setOrderBy(array $order)
+ * @method array getOrderBy()
+ * @method $this setLimit(int $limit)
+ * @method int getLimit()
+ * @method $this setOffset(int $offset)
+ * @method int getOffset()
+ */
+abstract class AbstractQueryAction extends AbstractAction {
+
+ /**
+ * Criteria for selecting items.
+ *
+ * $example->addWhere('contact_type', 'IN', array('Individual', 'Household'))
+ *
+ * @var array
+ */
+ protected $where = [];
+
+ /**
+ * Array of field(s) to use in ordering the results
+ *
+ * Defaults to id ASC
+ *
+ * $example->addOrderBy('sort_name', 'ASC')
+ *
+ * @var array
+ */
+ protected $orderBy = [];
+
+ /**
+ * Maximum number of results to return.
+ *
+ * Defaults to unlimited.
+ *
+ * Note: the Api Explorer sets this to 25 by default to avoid timeouts.
+ * Change or remove this default for your application code.
+ *
+ * @var int
+ */
+ protected $limit = 0;
+
+ /**
+ * Zero-based index of first result to return.
+ *
+ * Defaults to "0" - first record.
+ *
+ * @var int
+ */
+ protected $offset = 0;
+
+ /**
+ * @param string $field
+ * @param string $op
+ * @param mixed $value
+ * @return $this
+ * @throws \API_Exception
+ */
+ public function addWhere($field, $op, $value = NULL) {
+ if (!in_array($op, \CRM_Core_DAO::acceptedSQLOperators())) {
+ throw new \API_Exception('Unsupported operator');
+ }
+ $this->where[] = [$field, $op, $value];
+ return $this;
+ }
+
+ /**
+ * Adds one or more AND/OR/NOT clause groups
+ *
+ * @param string $operator
+ * @param mixed $condition1 ... $conditionN
+ * Either a nested array of arguments, or a variable number of arguments passed to this function.
+ *
+ * @return $this
+ * @throws \API_Exception
+ */
+ public function addClause($operator, $condition1) {
+ if (!is_array($condition1[0])) {
+ $condition1 = array_slice(func_get_args(), 1);
+ }
+ $this->where[] = [$operator, $condition1];
+ return $this;
+ }
+
+ /**
+ * @param string $field
+ * @param string $direction
+ * @return $this
+ */
+ public function addOrderBy($field, $direction = 'ASC') {
+ $this->orderBy[$field] = $direction;
+ return $this;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractUpdateAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractUpdateAction.php
new file mode 100644
index 00000000..a1904970
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/AbstractUpdateAction.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+/**
+ * Base class for all "Update" api actions
+ *
+ * @method $this setValues(array $values) Set all field values from an array of key => value pairs.
+ * @method $this addValue($field, $value) Set field value.
+ * @method array getValues() Get field values.
+ * @method $this setReload(bool $reload) Specify whether complete objects will be returned after saving.
+ * @method bool getReload()
+ *
+ * @package Civi\Api4\Generic
+ */
+abstract class AbstractUpdateAction extends AbstractBatchAction {
+
+ /**
+ * Field values to update.
+ *
+ * @required
+ * @var array
+ */
+ protected $values = [];
+
+ /**
+ * Reload objects after saving.
+ *
+ * Setting to TRUE will load complete records and return them as the api result.
+ * If FALSE the api usually returns only the fields specified to be updated.
+ *
+ * @var bool
+ */
+ protected $reload = FALSE;
+
+ /**
+ * @param string $key
+ *
+ * @return mixed|null
+ */
+ public function getValue($key) {
+ return isset($this->values[$key]) ? $this->values[$key] : NULL;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicBatchAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicBatchAction.php
new file mode 100644
index 00000000..2f39cf23
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicBatchAction.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Civi\Api4\Generic;
+use Civi\API\Exception\NotImplementedException;
+
+/**
+ * Basic action for deleting or performing some other task with a set of records. Ex:
+ *
+ * $myAction = new BasicBatchAction('Entity', 'action', function($item) {
+ * // Do something with $item
+ * $return $item;
+ * });
+ *
+ * @package Civi\Api4\Generic
+ */
+class BasicBatchAction extends AbstractBatchAction {
+
+ /**
+ * @var callable
+ *
+ * Function(array $item, BasicBatchAction $thisAction) => array
+ */
+ private $doer;
+
+ /**
+ * BasicBatchAction constructor.
+ *
+ * @param string $entityName
+ * @param string $actionName
+ * @param string|array $select
+ * One or more fields to select from each matching item.
+ * @param callable $doer
+ * Function(array $item, BasicBatchAction $thisAction) => array
+ */
+ public function __construct($entityName, $actionName, $select = 'id', $doer = NULL) {
+ parent::__construct($entityName, $actionName, $select);
+ $this->doer = $doer;
+ }
+
+ /**
+ * We pass the doTask function an array representing one item to update.
+ * We expect to get the same format back.
+ *
+ * @param \Civi\Api4\Generic\Result $result
+ */
+ public function _run(Result $result) {
+ foreach ($this->getBatchRecords() as $item) {
+ $result[] = $this->doTask($item);
+ }
+ }
+
+ /**
+ * This Basic Batch class can be used in one of two ways:
+ *
+ * 1. Use this class directly by passing a callable ($doer) to the constructor.
+ * 2. Extend this class and override this function.
+ *
+ * Either way, this function should return an array with an output record
+ * for the item.
+ *
+ * @param array $item
+ * @return array
+ * @throws \Civi\API\Exception\NotImplementedException
+ */
+ protected function doTask($item) {
+ if (is_callable($this->doer)) {
+ return call_user_func($this->doer, $item, $this);
+ }
+ throw new NotImplementedException('Doer function not found for api4 ' . $this->getEntityName() . '::' . $this->getActionName());
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicCreateAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicCreateAction.php
new file mode 100644
index 00000000..ddd238f4
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicCreateAction.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+use Civi\API\Exception\NotImplementedException;
+
+/**
+ * Create a new object from supplied values.
+ *
+ * This function will create 1 new object. It cannot be used to update existing objects. Use the Update or Replace actions for that.
+ */
+class BasicCreateAction extends AbstractCreateAction {
+
+ /**
+ * @var callable
+ *
+ * Function(array $item, BasicCreateAction $thisAction) => array
+ */
+ private $setter;
+
+ /**
+ * Basic Create constructor.
+ *
+ * @param string $entityName
+ * @param string $actionName
+ * @param callable $setter
+ * Function(array $item, BasicCreateAction $thisAction) => array
+ */
+ public function __construct($entityName, $actionName, $setter = NULL) {
+ parent::__construct($entityName, $actionName);
+ $this->setter = $setter;
+ }
+
+ /**
+ * We pass the writeRecord function an array representing one item to write.
+ * We expect to get the same format back.
+ *
+ * @param \Civi\Api4\Generic\Result $result
+ */
+ public function _run(Result $result) {
+ $this->validateValues();
+ $result->exchangeArray([$this->writeRecord($this->values)]);
+ }
+
+ /**
+ * This Basic Create class can be used in one of two ways:
+ *
+ * 1. Use this class directly by passing a callable ($setter) to the constructor.
+ * 2. Extend this class and override this function.
+ *
+ * Either way, this function should return an array representing the one new object.
+ *
+ * @param array $item
+ * @return array
+ * @throws \Civi\API\Exception\NotImplementedException
+ */
+ protected function writeRecord($item) {
+ if (is_callable($this->setter)) {
+ return call_user_func($this->setter, $item, $this);
+ }
+ throw new NotImplementedException('Setter function not found for api4 ' . $this->getEntityName() . '::' . $this->getActionName());
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicGetAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicGetAction.php
new file mode 100644
index 00000000..23d47a13
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicGetAction.php
@@ -0,0 +1,144 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+use Civi\API\Exception\NotImplementedException;
+
+/**
+ * Retrieve items based on criteria specified in the 'where' param.
+ *
+ * Use the 'select' param to determine which fields are returned, defaults to *.
+ */
+class BasicGetAction extends AbstractGetAction {
+ use Traits\ArrayQueryActionTrait;
+
+ /**
+ * @var callable
+ *
+ * Function(BasicGetAction $thisAction) => array<array>
+ */
+ private $getter;
+
+ /**
+ * Basic Get constructor.
+ *
+ * @param string $entityName
+ * @param string $actionName
+ * @param callable $getter
+ */
+ public function __construct($entityName, $actionName, $getter = NULL) {
+ parent::__construct($entityName, $actionName);
+ $this->getter = $getter;
+ }
+
+ /**
+ * Fetch results from the getter then apply filter/sort/select/limit.
+ *
+ * @param \Civi\Api4\Generic\Result $result
+ */
+ public function _run(Result $result) {
+ $values = $this->getRecords();
+ $result->exchangeArray($this->queryArray($values));
+ }
+
+ /**
+ * This Basic Get class is a general-purpose api for non-DAO-based entities.
+ *
+ * Useful for fetching records from files or other places.
+ * You can specify any php function to retrieve the records, and this class will
+ * automatically filter, sort, select & limit the raw data from your callback.
+ *
+ * You can implement this action in one of two ways:
+ * 1. Use this class directly by passing a callable ($getter) to the constructor.
+ * 2. Extend this class and override this function.
+ *
+ * Either way, this function should return an array of arrays, each representing one retrieved object.
+ *
+ * The simplest thing for your getter function to do is return every full record
+ * and allow this class to automatically do the sorting and filtering.
+ *
+ * Sometimes however that may not be practical for performance reasons.
+ * To optimize your getter, it can use the following helpers from $this:
+ *
+ * Use this->_itemsToGet() to match records to field values in the WHERE clause.
+ * Note the WHERE clause can potentially be very complex and it is not recommended
+ * to parse $this->where yourself.
+ *
+ * Use $this->_isFieldSelected() to check if a field value is called for - useful
+ * if loading the field involves expensive calculations.
+ *
+ * Be careful not to make assumptions, e.g. if LIMIT 100 is specified and your getter "helpfully" truncates the list
+ * at 100 without accounting for WHERE, ORDER BY and LIMIT clauses, the final filtered result may be very incorrect.
+ *
+ * @return array
+ * @throws \Civi\API\Exception\NotImplementedException
+ */
+ protected function getRecords() {
+ if (is_callable($this->getter)) {
+ return call_user_func($this->getter, $this);
+ }
+ throw new NotImplementedException('Getter function not found for api4 ' . $this->getEntityName() . '::' . $this->getActionName());
+ }
+
+ /**
+ * Helper to parse the WHERE param for getRecords to perform simple pre-filtering.
+ *
+ * This is intended to optimize some common use-cases e.g. calling the api to get
+ * one or more records by name or id.
+ *
+ * Ex: If getRecords fetches a long list of items each with a unique name,
+ * but the user has specified a single record to retrieve, you can optimize the call
+ * by checking $this->_itemsToGet('name') and only fetching the item(s) with that name.
+ *
+ * @param string $field
+ * @return array|null
+ */
+ public function _itemsToGet($field) {
+ foreach ($this->where as $clause) {
+ if ($clause[0] == $field && in_array($clause[1], ['=', 'IN'])) {
+ return (array) $clause[2];
+ }
+ }
+ return NULL;
+ }
+
+ /**
+ * Helper to see if a field should be selected by the getRecords function.
+ *
+ * Checks the SELECT, WHERE and ORDER BY params to see what fields are needed.
+ *
+ * Note that if no SELECT clause has been set then all fields should be selected
+ * and this function will always return TRUE.
+ *
+ * @param string $field
+ * @return bool
+ */
+ public function _isFieldSelected($field) {
+ if (!$this->select || in_array($field, $this->select) || isset($this->orderBy[$field])) {
+ return TRUE;
+ }
+ return $this->_whereContains($field, $this->where);
+ }
+
+ /**
+ * Walk through the where clause and check if a field is in use.
+ *
+ * @param string $field
+ * @param array $clauses
+ * @return bool
+ */
+ private function _whereContains($field, $clauses) {
+ foreach ($clauses as $clause) {
+ if (is_array($clause) && is_string($clause[0])) {
+ if ($clause[0] == $field) {
+ return TRUE;
+ }
+ elseif (is_array($clause[1])) {
+ return $this->_whereContains($field, $clause[1]);
+ }
+ }
+ }
+ return FALSE;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicGetFieldsAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicGetFieldsAction.php
new file mode 100644
index 00000000..c9869d5e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicGetFieldsAction.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+use Civi\Api4\Utils\ActionUtil;
+
+/**
+ * Get fields for an entity.
+ *
+ * @method $this setLoadOptions(bool $value)
+ * @method bool getLoadOptions()
+ * @method $this setAction(string $value)
+ */
+class BasicGetFieldsAction extends BasicGetAction {
+
+ /**
+ * Fetch option lists for fields?
+ *
+ * @var bool
+ */
+ protected $loadOptions = FALSE;
+
+ /**
+ * @var string
+ */
+ protected $action = 'get';
+
+ /**
+ * To implement getFields for your own entity:
+ *
+ * 1. From your entity class add a static getFields method.
+ * 2. That method should construct and return this class.
+ * 3. The 3rd argument passed to this constructor should be a function that returns an
+ * array of fields for your entity's CRUD actions.
+ * 4. For non-crud actions that need a different set of fields, you can override the
+ * list from step 3 on a per-action basis by defining a fields() method in that action.
+ * See for example BasicGetFieldsAction::fields() or GetActions::fields().
+ *
+ * @param Result $result
+ * @throws \Civi\API\Exception\NotImplementedException
+ */
+ public function _run(Result $result) {
+ $actionClass = ActionUtil::getAction($this->getEntityName(), $this->action);
+ if (method_exists($actionClass, 'fields')) {
+ $values = $actionClass->fields();
+ }
+ else {
+ $values = $this->getRecords();
+ }
+ $this->padResults($values);
+ $result->exchangeArray($this->queryArray($values));
+ }
+
+ /**
+ * @param array $values
+ */
+ private function padResults(&$values) {
+ foreach ($values as &$field) {
+ $field += [
+ 'title' => ucwords(str_replace('_', ' ', $field['name'])),
+ 'entity' => $this->getEntityName(),
+ 'required' => FALSE,
+ 'options' => FALSE,
+ 'data_type' => 'String',
+ ];
+ if (!$this->loadOptions) {
+ $field['options'] = (bool) $field['options'];
+ }
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getAction() {
+ return $this->action;
+ }
+
+ public function fields() {
+ return [
+ [
+ 'name' => 'name',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'title',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'description',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'default_value',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'required',
+ 'data_type' => 'Boolean',
+ ],
+ [
+ 'name' => 'options',
+ 'data_type' => 'Array',
+ ],
+ [
+ 'name' => 'data_type',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'fk_entity',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'serialize',
+ 'data_type' => 'Integer',
+ ],
+ ];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicReplaceAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicReplaceAction.php
new file mode 100644
index 00000000..8e0dd22e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicReplaceAction.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+use Civi\Api4\Generic\Result;
+
+/**
+ * Given a set of records, will appropriately update the database.
+ *
+ * @method $this setRecords(array $records) Array of records.
+ * @method $this addRecord($record) Add a record to update.
+ * @method array getRecords()
+ * @method $this setReload(bool $reload) Specify whether complete objects will be returned after saving.
+ * @method bool getReload()
+ */
+class BasicReplaceAction extends AbstractBatchAction {
+
+ /**
+ * Array of records.
+ *
+ * Should be in the same format as returned by Get.
+ *
+ * @required
+ * @var array
+ */
+ protected $records = [];
+
+ /**
+ * Reload objects after saving.
+ *
+ * Setting to TRUE will load complete records and return them as the api result.
+ * If FALSE the api usually returns only the fields specified to be updated.
+ *
+ * @var bool
+ */
+ protected $reload = FALSE;
+
+ /**
+ * @inheritDoc
+ */
+ public function _run(Result $result) {
+ $items = $this->getBatchRecords();
+
+ // Copy params from where clause if the operator is =
+ $paramsFromWhere = [];
+ foreach ($this->where as $clause) {
+ if (is_array($clause) && $clause[1] === '=') {
+ $paramsFromWhere[$clause[0]] = $clause[2];
+ }
+ }
+
+ $idField = $this->getSelect()[0];
+ $toDelete = array_column($items, NULL, $idField);
+
+ foreach ($this->records as $record) {
+ $record += $paramsFromWhere;
+ if (!empty($record[$idField])) {
+ $id = $record[$idField];
+ unset($toDelete[$id], $record[$idField]);
+ $result[] = civicrm_api4($this->getEntityName(), 'update', [
+ 'reload' => $this->reload,
+ 'where' => [[$idField, '=', $id]],
+ 'values' => $record,
+ ])->first();
+ }
+ else {
+ $result[] = civicrm_api4($this->getEntityName(), 'create', [
+ 'values' => $record,
+ ])->first();
+ }
+ }
+
+ $result->deleted = [];
+ if ($toDelete) {
+ $result->deleted = (array) civicrm_api4($this->getEntityName(), 'delete', [
+ 'where' => [[$idField, 'IN', array_keys($toDelete)]],
+ ]);
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicUpdateAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicUpdateAction.php
new file mode 100644
index 00000000..40c93624
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/BasicUpdateAction.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+use Civi\API\Exception\NotImplementedException;
+
+/**
+ * Update one or more records with new values.
+ *
+ * Use the where clause (required) to select them.
+ */
+class BasicUpdateAction extends AbstractUpdateAction {
+
+ /**
+ * @var callable
+ *
+ * Function(array $item, BasicUpdateAction $thisAction) => array
+ */
+ private $setter;
+
+ /**
+ * BasicUpdateAction constructor.
+ *
+ * @param string $entityName
+ * @param string $actionName
+ * @param string|array $select
+ * One or more fields to select from each matching item.
+ * @param callable $setter
+ * Function(array $item, BasicUpdateAction $thisAction) => array
+ */
+ public function __construct($entityName, $actionName, $select = 'id', $setter = NULL) {
+ parent::__construct($entityName, $actionName, $select);
+ $this->setter = $setter;
+ }
+
+ /**
+ * We pass the writeRecord function an array representing one item to update.
+ * We expect to get the same format back.
+ *
+ * @param \Civi\Api4\Generic\Result $result
+ */
+ public function _run(Result $result) {
+ foreach ($this->getBatchRecords() as $item) {
+ $result[] = $this->writeRecord($this->values + $item);
+ }
+ }
+
+ /**
+ * This Basic Update class can be used in one of two ways:
+ *
+ * 1. Use this class directly by passing a callable ($setter) to the constructor.
+ * 2. Extend this class and override this function.
+ *
+ * Either way, this function should return an array representing the one modified object.
+ *
+ * @param array $item
+ * @return array
+ * @throws \Civi\API\Exception\NotImplementedException
+ */
+ protected function writeRecord($item) {
+ if (is_callable($this->setter)) {
+ return call_user_func($this->setter, $item, $this);
+ }
+ throw new NotImplementedException('Setter function not found for api4 ' . $this->getEntityName() . '::' . $this->getActionName());
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOCreateAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOCreateAction.php
new file mode 100644
index 00000000..d7a0e869
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOCreateAction.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+use Civi\Api4\Generic\Result;
+
+/**
+ * Create a new object from supplied values.
+ *
+ * This function will create 1 new object. It cannot be used to update existing objects. Use the Update or Replace actions for that.
+ */
+class DAOCreateAction extends AbstractCreateAction {
+ use Traits\DAOActionTrait;
+
+ /**
+ * @inheritDoc
+ */
+ public function _run(Result $result) {
+ $this->validateValues();
+ $params = $this->values;
+ $this->fillDefaults($params);
+
+ $resultArray = $this->writeObjects([$params]);
+
+ $result->exchangeArray($resultArray);
+ }
+
+ /**
+ * @throws \API_Exception
+ */
+ protected function validateValues() {
+ if (!empty($this->values['id'])) {
+ throw new \API_Exception('Cannot pass id to Create action. Use Update action instead.');
+ }
+ parent::validateValues();
+ }
+
+ /**
+ * Fill field defaults which were declared by the api.
+ *
+ * Note: default values from core are ignored because the BAO or database layer will supply them.
+ *
+ * @param array $params
+ */
+ protected function fillDefaults(&$params) {
+ $fields = $this->getEntityFields();
+ $bao = $this->getBaoName();
+ $coreFields = array_column($bao::fields(), NULL, 'name');
+
+ foreach ($fields as $name => $field) {
+ // If a default value is set in the api but not in core, the api should supply it.
+ if (!isset($params[$name]) && !empty($field['default_value']) && empty($coreFields[$name]['default'])) {
+ $params[$name] = $field['default_value'];
+ }
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAODeleteAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAODeleteAction.php
new file mode 100644
index 00000000..f61af3f1
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAODeleteAction.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+use Civi\Api4\Generic\Result;
+
+/**
+ * Delete one or more items, based on criteria specified in Where param (required).
+ */
+class DAODeleteAction extends AbstractBatchAction {
+ use Traits\DAOActionTrait;
+
+ /**
+ * Batch delete function
+ */
+ public function _run(Result $result) {
+ $defaults = $this->getParamDefaults();
+ if ($defaults['where'] && !array_diff_key($this->where, $defaults['where'])) {
+ throw new \API_Exception('Cannot delete with no "where" parameter specified');
+ }
+
+ $items = $this->getObjects();
+
+ $ids = $this->deleteObjects($items);
+
+ $result->exchangeArray($ids);
+ }
+
+ /**
+ * @param $items
+ * @return array
+ * @throws \API_Exception
+ */
+ protected function deleteObjects($items) {
+ $ids = [];
+ $baoName = $this->getBaoName();
+
+ if ($this->getCheckPermissions()) {
+ foreach ($items as $item) {
+ $this->checkContactPermissions($baoName, $item);
+ }
+ }
+
+ if ($this->getEntityName() !== 'EntityTag' && method_exists($baoName, 'del')) {
+ foreach ($items as $item) {
+ $args = [$item['id']];
+ $bao = call_user_func_array([$baoName, 'del'], $args);
+ if ($bao !== FALSE) {
+ $ids[] = $item['id'];
+ }
+ else {
+ throw new \API_Exception("Could not delete {$this->getEntityName()} id {$item['id']}");
+ }
+ }
+ }
+ else {
+ foreach ($items as $item) {
+ $bao = new $baoName();
+ $bao->id = $item['id'];
+ // delete it
+ $action_result = $bao->delete();
+ if ($action_result) {
+ $ids[] = $item['id'];
+ }
+ else {
+ throw new \API_Exception("Could not delete {$this->getEntityName()} id {$item['id']}");
+ }
+ }
+ }
+ return $ids;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOEntity.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOEntity.php
new file mode 100644
index 00000000..1ad175da
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOEntity.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+/**
+ * Base class for DAO-based entities.
+ */
+abstract class DAOEntity extends AbstractEntity {
+
+ /**
+ * @return DAOGetAction
+ */
+ public static function get() {
+ return new DAOGetAction(static::class, __FUNCTION__);
+ }
+
+ /**
+ * @return DAOGetFieldsAction
+ */
+ public static function getFields() {
+ return new DAOGetFieldsAction(static::class, __FUNCTION__);
+ }
+
+ /**
+ * @return DAOCreateAction
+ */
+ public static function create() {
+ return new DAOCreateAction(static::class, __FUNCTION__);
+ }
+
+ /**
+ * @return DAOUpdateAction
+ */
+ public static function update() {
+ return new DAOUpdateAction(static::class, __FUNCTION__);
+ }
+
+ /**
+ * @return DAODeleteAction
+ */
+ public static function delete() {
+ return new DAODeleteAction(static::class, __FUNCTION__);
+ }
+
+ /**
+ * @return BasicReplaceAction
+ */
+ public static function replace() {
+ return new BasicReplaceAction(static::class, __FUNCTION__);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOGetAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOGetAction.php
new file mode 100644
index 00000000..0216f0da
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOGetAction.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+use Civi\Api4\Generic\Result;
+
+/**
+ * Retrieve items based on criteria specified in the 'where' param.
+ *
+ * Use the 'select' param to determine which fields are returned, defaults to *.
+ *
+ * Perform joins on other related entities using a dot notation.
+ */
+class DAOGetAction extends AbstractGetAction {
+ use Traits\DAOActionTrait;
+
+ public function _run(Result $result) {
+ $result->exchangeArray($this->getObjects());
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOGetFieldsAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOGetFieldsAction.php
new file mode 100644
index 00000000..e86d99bc
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOGetFieldsAction.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+use Civi\Api4\Service\Spec\SpecGatherer;
+use Civi\Api4\Service\Spec\SpecFormatter;
+
+/**
+ * Get fields for a DAO-based entity.
+ *
+ * @method $this setIncludeCustom(bool $value)
+ * @method bool getIncludeCustom()
+ */
+class DAOGetFieldsAction extends BasicGetFieldsAction {
+
+ /**
+ * Include custom fields for this entity, or only core fields?
+ *
+ * @var bool
+ */
+ protected $includeCustom = TRUE;
+
+ /**
+ * Get fields for a DAO-based entity
+ *
+ * @return array
+ */
+ protected function getRecords() {
+ $fields = $this->_itemsToGet('name');
+ /** @var SpecGatherer $gatherer */
+ $gatherer = \Civi::container()->get('spec_gatherer');
+ // Any fields name with a dot in it is custom
+ if ($fields) {
+ $this->includeCustom = strpos(implode('', $fields), '.') !== FALSE;
+ }
+ $spec = $gatherer->getSpec($this->getEntityName(), $this->action, $this->includeCustom);
+ return SpecFormatter::specToArray($spec->getFields($fields), (array) $this->select, $this->loadOptions);
+ }
+
+ public function fields() {
+ $fields = parent::fields();
+ $fields[] = [
+ 'name' => 'custom_field_id',
+ 'data_type' => 'Integer',
+ ];
+ $fields[] = [
+ 'name' => 'custom_group_id',
+ 'data_type' => 'Integer',
+ ];
+ return $fields;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOUpdateAction.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOUpdateAction.php
new file mode 100644
index 00000000..62da8796
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/DAOUpdateAction.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Civi\Api4\Generic;
+
+use Civi\Api4\Generic\Result;
+
+/**
+ * Update one or more records with new values.
+ *
+ * Use the where clause (required) to select them.
+ */
+class DAOUpdateAction extends AbstractUpdateAction {
+ use Traits\DAOActionTrait;
+
+ /**
+ * @inheritDoc
+ */
+ public function _run(Result $result) {
+ if (!empty($this->values['id'])) {
+ throw new \Exception("Cannot update the id of an existing " . $this->getEntityName() . '.');
+ }
+
+ $items = $this->getObjects();
+ foreach ($items as &$item) {
+ $item = $this->values + $item;
+ }
+
+ $result->exchangeArray($this->writeObjects($items));
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Result.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Result.php
new file mode 100644
index 00000000..35fb6fb0
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Result.php
@@ -0,0 +1,105 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2015 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Generic;
+
+/**
+ * Container for api results.
+ */
+class Result extends \ArrayObject {
+ /**
+ * @var string
+ */
+ public $entity;
+ /**
+ * @var string
+ */
+ public $action;
+ /**
+ * Api version
+ * @var int
+ */
+ public $version = 4;
+
+ /**
+ * Return first result.
+ * @return array|null
+ */
+ public function first() {
+ foreach ($this as $values) {
+ return $values;
+ }
+ return NULL;
+ }
+
+ /**
+ * Return last result.
+ * @return array|null
+ */
+ public function last() {
+ $items = $this->getArrayCopy();
+ return array_pop($items);
+ }
+
+ /**
+ * @param int $index
+ * @return array|null
+ */
+ public function itemAt($index) {
+ $length = $index < 0 ? 0 - $index : $index + 1;
+ if ($length > count($this)) {
+ return NULL;
+ }
+ return array_slice(array_values($this->getArrayCopy()), $index, 1)[0];
+ }
+
+ /**
+ * Re-index the results array (which by default is non-associative)
+ *
+ * Drops any item from the results that does not contain the specified key
+ *
+ * @param string $key
+ * @return $this
+ * @throws \API_Exception
+ */
+ public function indexBy($key) {
+ if (count($this)) {
+ $newResults = [];
+ foreach ($this as $values) {
+ if (isset($values[$key])) {
+ $newResults[$values[$key]] = $values;
+ }
+ }
+ if (!$newResults) {
+ throw new \API_Exception("Key $key not found in api results");
+ }
+ $this->exchangeArray($newResults);
+ }
+ return $this;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php
new file mode 100644
index 00000000..1d223f1b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php
@@ -0,0 +1,197 @@
+<?php
+
+namespace Civi\Api4\Generic\Traits;
+use Civi\API\Exception\NotImplementedException;
+
+/**
+ * Helper functions for performing api queries on arrays of data.
+ *
+ * @package Civi\Api4\Generic
+ */
+trait ArrayQueryActionTrait {
+
+ /**
+ * @param array $values
+ * List of all rows
+ * @return array
+ * Filtered list of rows
+ */
+ protected function queryArray($values) {
+ $values = $this->filterArray($values);
+ $values = $this->sortArray($values);
+ $values = $this->selectArray($values);
+ $values = $this->limitArray($values);
+ return $values;
+ }
+
+ /**
+ * @param array $values
+ * @return array
+ */
+ protected function filterArray($values) {
+ if ($this->getWhere()) {
+ $values = array_filter($values, [$this, 'evaluateFilters']);
+ }
+ return array_values($values);
+ }
+
+ /**
+ * @param array $row
+ * @return bool
+ */
+ private function evaluateFilters($row) {
+ $where = $this->getWhere();
+ $allConditions = in_array($where[0], ['AND', 'OR', 'NOT']) ? $where : ['AND', $where];
+ return $this->walkFilters($row, $allConditions);
+ }
+
+ /**
+ * @param array $row
+ * @param array $filters
+ * @return bool
+ * @throws \Civi\API\Exception\NotImplementedException
+ */
+ private function walkFilters($row, $filters) {
+ switch ($filters[0]) {
+ case 'AND':
+ case 'NOT':
+ $result = TRUE;
+ foreach ($filters[1] as $filter) {
+ if (!$this->walkFilters($row, $filter)) {
+ $result = FALSE;
+ break;
+ }
+ }
+ return $result == ($filters[0] == 'AND');
+
+ case 'OR':
+ $result = !count($filters[1]);
+ foreach ($filters[1] as $filter) {
+ if ($this->walkFilters($row, $filter)) {
+ return TRUE;
+ }
+ }
+ return $result;
+
+ default:
+ return $this->filterCompare($row, $filters);
+ }
+ }
+
+ /**
+ * @param array $row
+ * @param array $condition
+ * @return bool
+ * @throws \Civi\API\Exception\NotImplementedException
+ */
+ private function filterCompare($row, $condition) {
+ if (!is_array($condition)) {
+ throw new NotImplementedException('Unexpected where syntax; expecting array.');
+ }
+ $value = isset($row[$condition[0]]) ? $row[$condition[0]] : NULL;
+ $operator = $condition[1];
+ $expected = isset($condition[2]) ? $condition[2] : NULL;
+ switch ($operator) {
+ case '=':
+ case '!=':
+ case '<>':
+ $equal = $value == $expected;
+ // PHP is too imprecise about comparing the number 0
+ if ($expected === 0 || $expected === '0') {
+ $equal = ($value === 0 || $value === '0');
+ }
+ // PHP is too imprecise about comparing empty strings
+ if ($expected === '') {
+ $equal = ($value === '');
+ }
+ return $equal == ($operator == '=');
+
+ case 'IS NULL':
+ case 'IS NOT NULL':
+ return is_null($value) == ($operator == 'IS NULL');
+
+ case '>':
+ return $value > $expected;
+
+ case '>=':
+ return $value >= $expected;
+
+ case '<':
+ return $value < $expected;
+
+ case '<=':
+ return $value <= $expected;
+
+ case 'BETWEEN':
+ case 'NOT BETWEEN':
+ $between = ($value >= $expected[0] && $value <= $expected[1]);
+ return $between == ($operator == 'BETWEEN');
+
+ case 'LIKE':
+ case 'NOT LIKE':
+ $pattern = '/^' . str_replace('%', '.*', preg_quote($expected, '/')) . '$/i';
+ return !preg_match($pattern, $value) == ($operator != 'LIKE');
+
+ case 'IN':
+ return in_array($value, $expected);
+
+ case 'NOT IN':
+ return !in_array($value, $expected);
+
+ default:
+ throw new NotImplementedException("Unsupported operator: '$operator' cannot be used with array data");
+ }
+ }
+
+ /**
+ * @param $values
+ * @return array
+ */
+ protected function sortArray($values) {
+ if ($this->getOrderBy()) {
+ usort($values, [$this, 'sortCompare']);
+ }
+ return $values;
+ }
+
+ private function sortCompare($a, $b) {
+ foreach ($this->getOrderBy() as $field => $dir) {
+ $modifier = $dir == 'ASC' ? 1 : -1;
+ if (isset($a[$field]) && isset($b[$field])) {
+ if ($a[$field] == $b[$field]) {
+ continue;
+ }
+ return (strnatcasecmp($a[$field], $b[$field]) * $modifier);
+ }
+ elseif (isset($a[$field]) || isset($b[$field])) {
+ return ((isset($a[$field]) ? 1 : -1) * $modifier);
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * @param $values
+ * @return array
+ */
+ protected function selectArray($values) {
+ if ($this->getSelect()) {
+ foreach ($values as &$value) {
+ $value = array_intersect_key($value, array_flip($this->getSelect()));
+ }
+ }
+ return $values;
+ }
+
+ /**
+ * @param $values
+ * @return array
+ */
+ protected function limitArray($values) {
+ if ($this->getOffset() || $this->getLimit()) {
+ $values = array_slice($values, $this->getOffset() ?: 0, $this->getLimit() ?: NULL);
+ }
+ return $values;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/CustomValueActionTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/CustomValueActionTrait.php
new file mode 100644
index 00000000..6a765b40
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/CustomValueActionTrait.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Civi\Api4\Generic\Traits;
+
+use Civi\Api4\Utils\FormattingUtil;
+use Civi\Api4\Utils\CoreUtil;
+
+/**
+ * Helper functions for working with custom values
+ *
+ * @package Civi\Api4\Generic
+ */
+trait CustomValueActionTrait {
+
+ function __construct($customGroup, $actionName) {
+ $this->customGroup = $customGroup;
+ parent::__construct('CustomValue', $actionName, ['id', 'entity_id']);
+ }
+
+ /**
+ * Custom Group name if this is a CustomValue pseudo-entity.
+ *
+ * @var string
+ */
+ private $customGroup;
+
+ /**
+ * @inheritDoc
+ */
+ public function getEntityName() {
+ return 'Custom_' . $this->getCustomGroup();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function writeObjects($items) {
+ $result = [];
+ foreach ($items as $item) {
+ FormattingUtil::formatWriteParams($item, $this->getEntityName(), $this->getEntityFields());
+
+ $result[] = \CRM_Core_BAO_CustomValueTable::setValues($item);
+ }
+ return $result;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function deleteObjects($items) {
+ $customTable = CoreUtil::getCustomTableByName($this->getCustomGroup());
+ $ids = [];
+ foreach ($items as $item) {
+ \CRM_Utils_Hook::pre('delete', $this->getEntityName(), $item['id'], \CRM_Core_DAO::$_nullArray);
+ \CRM_Utils_SQL_Delete::from($customTable)
+ ->where('id = #value')
+ ->param('#value', $item['id'])
+ ->execute();
+ \CRM_Utils_Hook::post('delete', $this->getEntityName(), $item['id'], \CRM_Core_DAO::$_nullArray);
+ $ids[] = $item['id'];
+ }
+ return $ids;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function fillDefaults(&$params) {
+ foreach ($this->getEntityFields() as $name => $field) {
+ if (!isset($params[$name]) && isset($field['default_value'])) {
+ $params[$name] = $field['default_value'];
+ }
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getCustomGroup() {
+ return $this->customGroup;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/DAOActionTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/DAOActionTrait.php
new file mode 100644
index 00000000..1c92906b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/DAOActionTrait.php
@@ -0,0 +1,229 @@
+<?php
+namespace Civi\Api4\Generic\Traits;
+
+use CRM_Utils_Array as UtilsArray;
+use Civi\Api4\Utils\FormattingUtil;
+use Civi\Api4\Query\Api4SelectQuery;
+
+trait DAOActionTrait {
+
+ /**
+ * @return \CRM_Core_DAO|string
+ */
+ protected function getBaoName() {
+ require_once 'api/v3/utils.php';
+ return \_civicrm_api3_get_BAO($this->getEntityName());
+ }
+
+ /**
+ * Extract the true fields from a BAO
+ *
+ * (Used by create and update actions)
+ * @param object $bao
+ * @return array
+ */
+ public static function baoToArray($bao) {
+ $fields = $bao->fields();
+ $values = [];
+ foreach ($fields as $key => $field) {
+ $name = $field['name'];
+ if (property_exists($bao, $name)) {
+ $values[$name] = $bao->$name;
+ }
+ }
+ return $values;
+ }
+
+ /**
+ * @return array|int
+ */
+ protected function getObjects() {
+ $query = new Api4SelectQuery($this->getEntityName(), $this->getCheckPermissions());
+ $query->select = $this->getSelect();
+ $query->where = $this->getWhere();
+ $query->orderBy = $this->getOrderBy();
+ $query->limit = $this->getLimit();
+ $query->offset = $this->getOffset();
+ return $query->run();
+ }
+
+ /**
+ * Write a bao object as part of a create/update action.
+ *
+ * @param array $items
+ * The record to write to the DB.
+ * @return array
+ * The record after being written to the DB (e.g. including newly assigned "id").
+ * @throws \API_Exception
+ */
+ protected function writeObjects($items) {
+ $baoName = $this->getBaoName();
+
+ // Some BAOs are weird and don't support a straightforward "create" method.
+ $oddballs = [
+ 'Address' => 'add',
+ 'GroupContact' => 'add',
+ 'Website' => 'add',
+ ];
+ $method = UtilsArray::value($this->getEntityName(), $oddballs, 'create');
+ if (!method_exists($baoName, $method)) {
+ $method = 'add';
+ }
+
+ $result = [];
+
+ foreach ($items as $item) {
+ $entityId = UtilsArray::value('id', $item);
+ FormattingUtil::formatWriteParams($item, $this->getEntityName(), $this->getEntityFields());
+ $this->formatCustomParams($item, $entityId);
+ $item['check_permissions'] = $this->getCheckPermissions();
+
+ $apiKeyPermission = $this->getEntityName() != 'Contact' || !$this->getCheckPermissions() || array_key_exists('api_key', $this->getEntityFields())
+ || ($entityId && \CRM_Core_Permission::check('edit own api keys') && \CRM_Core_Session::getLoggedInContactID() == $entityId);
+
+ if (!$apiKeyPermission && array_key_exists('api_key', $item)) {
+ throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify api key');
+ }
+
+ // For some reason the contact bao requires this
+ if ($entityId && $this->getEntityName() == 'Contact') {
+ $item['contact_id'] = $entityId;
+ }
+
+ if ($this->getCheckPermissions() && $entityId) {
+ $this->checkContactPermissions($baoName, $item);
+ }
+
+ if (method_exists($baoName, $method)) {
+ $createResult = $baoName::$method($item);
+ }
+ else {
+ $createResult = $this->genericCreateMethod($item);
+ }
+
+ if (!$createResult) {
+ $errMessage = sprintf('%s write operation failed', $this->getEntityName());
+ throw new \API_Exception($errMessage);
+ }
+
+ if (!empty($this->reload) && is_a($createResult, 'CRM_Core_DAO')) {
+ $createResult->find(TRUE);
+ }
+
+ // trim back the junk and just get the array:
+ $resultArray = $this->baoToArray($createResult);
+
+ if (!$apiKeyPermission && array_key_exists('api_key', $resultArray)) {
+ unset($resultArray['api_key']);
+ }
+
+ $result[] = $resultArray;
+ }
+ return $result;
+ }
+
+ /**
+ * Fallback when a BAO does not contain create or add functions
+ *
+ * @param $params
+ * @return mixed
+ */
+ private function genericCreateMethod($params) {
+ $baoName = $this->getBaoName();
+ $hook = empty($params['id']) ? 'create' : 'edit';
+
+ \CRM_Utils_Hook::pre($hook, $this->getEntityName(), UtilsArray::value('id', $params), $params);
+ /** @var \CRM_Core_DAO $instance */
+ $instance = new $baoName();
+ $instance->copyValues($params, TRUE);
+ $instance->save();
+ \CRM_Utils_Hook::post($hook, $this->getEntityName(), $instance->id, $instance);
+
+ return $instance;
+ }
+
+ /**
+ * @param array $params
+ * @param int $entityId
+ * @return mixed
+ */
+ private function formatCustomParams(&$params, $entityId) {
+ $customParams = [];
+
+ // $customValueID is the ID of the custom value in the custom table for this
+ // entity (i guess this assumes it's not a multi value entity)
+ foreach ($params as $name => $value) {
+ if (strpos($name, '.') === FALSE) {
+ continue;
+ }
+
+ list($customGroup, $customField) = explode('.', $name);
+
+ $customFieldId = \CRM_Core_BAO_CustomField::getFieldValue(
+ \CRM_Core_DAO_CustomField::class,
+ $customField,
+ 'id',
+ 'name'
+ );
+ $customFieldType = \CRM_Core_BAO_CustomField::getFieldValue(
+ \CRM_Core_DAO_CustomField::class,
+ $customField,
+ 'html_type',
+ 'name'
+ );
+ $customFieldExtends = \CRM_Core_BAO_CustomGroup::getFieldValue(
+ \CRM_Core_DAO_CustomGroup::class,
+ $customGroup,
+ 'extends',
+ 'name'
+ );
+
+ // todo are we sure we don't want to allow setting to NULL? need to test
+ if ($customFieldId && NULL !== $value) {
+
+ if ($customFieldType == 'CheckBox') {
+ // this function should be part of a class
+ formatCheckBoxField($value, 'custom_' . $customFieldId, $this->getEntityName());
+ }
+
+ \CRM_Core_BAO_CustomField::formatCustomField(
+ $customFieldId,
+ $customParams,
+ $value,
+ $customFieldExtends,
+ NULL, // todo check when this is needed
+ $entityId,
+ FALSE,
+ FALSE,
+ TRUE
+ );
+ }
+ }
+
+ if ($customParams) {
+ $params['custom'] = $customParams;
+ }
+ }
+
+ /**
+ * Check edit/delete permissions for contacts and related entities.
+ *
+ * @param $baoName
+ * @param $item
+ * @throws \Civi\API\Exception\UnauthorizedException
+ */
+ protected function checkContactPermissions($baoName, $item) {
+ if ($baoName == 'CRM_Contact_BAO_Contact') {
+ $permission = $this->getActionName() == 'delete' ? \CRM_Core_Permission::DELETE : \CRM_Core_Permission::EDIT;
+ if (!\CRM_Contact_BAO_Contact_Permission::allow($item['id'], $permission)) {
+ throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify contact record');
+ }
+ }
+ else {
+ // Fixme: decouple from v3
+ require_once 'api/v3/utils.php';
+ _civicrm_api3_check_edit_permissions($baoName, ['check_permissions' => 1] + $item);
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Group.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Group.php
new file mode 100644
index 00000000..b82fa982
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Group.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Group entity.
+ *
+ * @package Civi\Api4
+ */
+class Group extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/GroupContact.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/GroupContact.php
new file mode 100644
index 00000000..8901cd3b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/GroupContact.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * GroupContact entity - link between groups and contacts.
+ *
+ * A contact can either be "Added" "Removed" or "Pending" in a group.
+ * CiviCRM only considers them to be "in" a group if their status is "Added".
+ *
+ * @package Civi\Api4
+ */
+class GroupContact extends Generic\DAOEntity {
+
+ /**
+ * @return Action\GroupContact\Create
+ */
+ public static function create() {
+ return new Action\GroupContact\Create(__CLASS__, __FUNCTION__);
+ }
+
+ /**
+ * @return Action\GroupContact\Update
+ */
+ public static function update() {
+ return new Action\GroupContact\Update(__CLASS__, __FUNCTION__);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/IM.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/IM.php
new file mode 100644
index 00000000..514f39a8
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/IM.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * IM entity.
+ *
+ * @package Civi\Api4
+ */
+class IM extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Navigation.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Navigation.php
new file mode 100644
index 00000000..31f2a911
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Navigation.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Navigation entity.
+ *
+ * @package Civi\Api4
+ */
+class Navigation extends Generic\DAOEntity {
+
+ /**
+ * @return Action\Navigation\Get
+ */
+ public static function get() {
+ return new Action\Navigation\Get(__CLASS__, __FUNCTION__);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Note.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Note.php
new file mode 100644
index 00000000..55f6b7e6
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Note.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Note entity.
+ *
+ * @package Civi\Api4
+ */
+class Note extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OpenID.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OpenID.php
new file mode 100644
index 00000000..a0c146aa
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OpenID.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * OpenID entity.
+ *
+ * @package Civi\Api4
+ */
+class OpenID extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OptionGroup.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OptionGroup.php
new file mode 100644
index 00000000..4821348a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OptionGroup.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * OptionGroup entity.
+ *
+ * @package Civi\Api4
+ */
+class OptionGroup extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OptionValue.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OptionValue.php
new file mode 100644
index 00000000..16e9706c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/OptionValue.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * OptionValue entity.
+ *
+ * @package Civi\Api4
+ */
+class OptionValue extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Participant.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Participant.php
new file mode 100644
index 00000000..0e09c797
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Participant.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Participant entity.
+ *
+ * @package Civi\Api4
+ */
+class Participant extends Generic\DAOEntity {
+
+ /**
+ * @return Action\Participant\Get
+ */
+ public static function get() {
+ return new Action\Participant\Get(__CLASS__, __FUNCTION__);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Phone.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Phone.php
new file mode 100644
index 00000000..a02cd7cd
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Phone.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Phone entity.
+ *
+ * This entity allows user to add, update, retrieve or delete phone number(s) of a contact.
+ *
+ * Creating a new phone of a contact, requires at minimum a contact's ID and phone number
+ *
+ * @package Civi\Api4
+ */
+class Phone extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Provider/ActionObjectProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Provider/ActionObjectProvider.php
new file mode 100644
index 00000000..60596d92
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Provider/ActionObjectProvider.php
@@ -0,0 +1,155 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2015 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Provider;
+
+use Civi\API\Event\ResolveEvent;
+use Civi\API\Provider\ProviderInterface;
+use Civi\Api4\Generic\AbstractAction;
+use Civi\API\Events;
+use Civi\Api4\Generic\Result;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Accept $apiRequests based on \Civi\API\Action
+ */
+class ActionObjectProvider implements EventSubscriberInterface, ProviderInterface {
+
+ /**
+ * @return array
+ */
+ public static function getSubscribedEvents() {
+ // Using a high priority allows adhoc implementations
+ // to override standard implementations -- which is
+ // handy for testing/mocking.
+ return [
+ Events::RESOLVE => [
+ ['onApiResolve', Events::W_EARLY],
+ ],
+ ];
+ }
+ /**
+ * @param ResolveEvent $event
+ * API resolution event.
+ */
+ public function onApiResolve(ResolveEvent $event) {
+ $apiRequest = $event->getApiRequest();
+ if ($apiRequest instanceof AbstractAction) {
+ $event->setApiRequest($apiRequest);
+ $event->setApiProvider($this);
+ $event->stopPropagation();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param AbstractAction $action
+ *
+ * @return array|mixed
+ */
+ public function invoke($action) {
+ $result = new Result();
+ $result->action = $action->getActionName();
+ $result->entity = $action->getEntityName();
+ $action->_run($result);
+ $this->handleChains($action, $result);
+ return $result;
+ }
+
+ /**
+ * Run each chained action once per row
+ *
+ * @param AbstractAction $action
+ * @param Result $result
+ */
+ protected function handleChains($action, $result) {
+ foreach ($action->getChain() as $name => $request) {
+ $request += [NULL, NULL, [], NULL];
+ $request[2]['checkPermissions'] = $action->getCheckPermissions();
+ foreach ($result as &$row) {
+ $row[$name] = $this->runChain($request, $row);
+ }
+ }
+ }
+
+ /**
+ * Run a chained action
+ *
+ * @param $request
+ * @param $row
+ * @return array|\Civi\Api4\Generic\Result|null
+ * @throws \API_Exception
+ */
+ protected function runChain($request, $row) {
+ list($entity, $action, $params, $index) = $request;
+ // Swap out variables in $entity, $action & $params
+ $this->resolveChainLinks($entity, $row);
+ $this->resolveChainLinks($action, $row);
+ $this->resolveChainLinks($params, $row);
+ return (array) civicrm_api4($entity, $action, $params, $index);
+ }
+
+ /**
+ * Swap out variable names
+ *
+ * @param mixed $val
+ * @param array $result
+ */
+ protected function resolveChainLinks(&$val, $result) {
+ if (is_array($val)) {
+ foreach ($val as &$v) {
+ $this->resolveChainLinks($v, $result);
+ }
+ }
+ elseif (is_string($val) && strlen($val) > 1 && substr($val, 0, 1) === '$') {
+ $val = \CRM_Utils_Array::pathGet($result, explode('.', substr($val, 1)));
+ }
+ }
+
+ /**
+ * @inheritDoc
+ * @param int $version
+ * @return array
+ */
+ public function getEntityNames($version) {
+ /** FIXME */
+ return [];
+ }
+
+ /**
+ * @inheritDoc
+ * @param int $version
+ * @param string $entity
+ * @return array
+ */
+ public function getActionNames($version, $entity) {
+ /** FIXME Civi\API\V4\Action\GetActions */
+ return [];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Query/Api4SelectQuery.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Query/Api4SelectQuery.php
new file mode 100644
index 00000000..a7a912da
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Query/Api4SelectQuery.php
@@ -0,0 +1,535 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2015 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Query;
+
+use Civi\API\SelectQuery;
+use Civi\Api4\Event\Events;
+use Civi\Api4\Event\PostSelectQueryEvent;
+use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable;
+use Civi\Api4\Service\Schema\Joinable\Joinable;
+use Civi\Api4\Utils\FormattingUtil;
+use Civi\Api4\Utils\CoreUtil;
+use CRM_Core_DAO_AllCoreTables as TableHelper;
+use CRM_Core_DAO_CustomField as CustomFieldDAO;
+use CRM_Utils_Array as UtilsArray;
+
+/**
+ * A query `node` may be in one of three formats:
+ *
+ * * leaf: [$fieldName, $operator, $criteria]
+ * * negated: ['NOT', $node]
+ * * branch: ['OR|NOT', [$node, $node, ...]]
+ *
+ * Leaf operators are one of:
+ *
+ * * '=', '<=', '>=', '>', '<', 'LIKE', "<>", "!=",
+ * * "NOT LIKE", 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN',
+ * * 'IS NOT NULL', or 'IS NULL'.
+ */
+class Api4SelectQuery extends SelectQuery {
+
+ /**
+ * @var int
+ */
+ protected $apiVersion = 4;
+
+ /**
+ * @var array
+ * Maps select fields to [<table_alias>, <column_alias>]
+ */
+ protected $fkSelectAliases = [];
+
+ /**
+ * @var Joinable[]
+ * The joinable tables that have been joined so far
+ */
+ protected $joinedTables = [];
+
+ /**
+ * @param string $entity
+ * @param bool $checkPermissions
+ */
+ public function __construct($entity, $checkPermissions) {
+ require_once 'api/v3/utils.php';
+ $this->entity = $entity;
+ $this->checkPermissions = $checkPermissions;
+
+ $baoName = CoreUtil::getDAOFromApiName($entity);
+ $bao = new $baoName();
+
+ $this->entityFieldNames = _civicrm_api3_field_names(_civicrm_api3_build_fields_array($bao));
+ $this->apiFieldSpec = $this->getFields();
+
+ \CRM_Utils_SQL_Select::from($this->getTableName($baoName) . ' ' . self::MAIN_TABLE_ALIAS);
+
+ // Add ACLs first to avoid redundant subclauses
+ $this->query->where($this->getAclClause(self::MAIN_TABLE_ALIAS, $baoName));
+ }
+
+ /**
+ * Why walk when you can
+ *
+ * @return array|int
+ */
+ public function run() {
+ $this->preRun();
+ $baseResults = parent::run();
+ $event = new PostSelectQueryEvent($baseResults, $this);
+ \Civi::dispatcher()->dispatch(Events::POST_SELECT_QUERY, $event);
+
+ return $event->getResults();
+ }
+
+ /**
+ * Gets all FK fields and does the required joins
+ */
+ protected function preRun() {
+ $allFields = array_merge($this->select, array_keys($this->orderBy));
+ $recurse = function($clauses) use (&$allFields, &$recurse) {
+ foreach ($clauses as $clause) {
+ if ($clause[0] === 'NOT' && is_string($clause[1][0])) {
+ $recurse($clause[1][1]);
+ }
+ elseif (in_array($clause[0], ['AND', 'OR', 'NOT'])) {
+ $recurse($clause[1]);
+ }
+ elseif (is_array($clause[0])) {
+ array_walk($clause, $recurse);
+ }
+ else {
+ $allFields[] = $clause[0];
+ }
+ }
+ };
+ $recurse($this->where);
+ $dotFields = array_unique(array_filter($allFields, function ($field) {
+ return strpos($field, '.') !== FALSE;
+ }));
+
+ foreach ($dotFields as $dotField) {
+ $this->joinFK($dotField);
+ }
+ }
+
+ /**
+ * Populate $this->selectFields
+ *
+ * @throws \Civi\API\Exception\UnauthorizedException
+ */
+ protected function buildSelectFields() {
+ $return_all_fields = (empty($this->select) || !is_array($this->select));
+ $return = $return_all_fields ? $this->entityFieldNames : $this->select;
+ if ($return_all_fields || in_array('custom', $this->select)) {
+ foreach (array_keys($this->apiFieldSpec) as $fieldName) {
+ if (strpos($fieldName, 'custom_') === 0) {
+ $return[] = $fieldName;
+ }
+ }
+ }
+
+ // Always select the ID if the table has one.
+ if (array_key_exists('id', $this->apiFieldSpec) || strstr($this->entity, 'Custom_')) {
+ $this->selectFields[self::MAIN_TABLE_ALIAS . ".id"] = "id";
+ }
+
+ // core return fields
+ foreach ($return as $fieldName) {
+ $field = $this->getField($fieldName);
+ if ($field && in_array($field['name'], $this->entityFieldNames)) {
+ $this->selectFields[self::MAIN_TABLE_ALIAS . "." . UtilsArray::value('column_name', $field, $field['name'])] = $field['name'];
+ }
+ elseif (strpos($fieldName, '.')) {
+ $fkField = $this->addFkField($fieldName, 'LEFT');
+ if ($fkField) {
+ $this->selectFields[implode('.', $fkField)] = $fieldName;
+ }
+ }
+ elseif ($field && strpos($fieldName, 'custom_') === 0) {
+ list($table_name, $column_name) = $this->addCustomField($field, 'LEFT');
+
+ if ($field['data_type'] != 'ContactReference') {
+ // 'ordinary' custom field. We will select the value as custom_XX.
+ $this->selectFields["$table_name.$column_name"] = $fieldName;
+ }
+ else {
+ // contact reference custom field. The ID will be stored in custom_XX_id.
+ // custom_XX will contain the sort name of the contact.
+ $this->query->join("c_$fieldName", "LEFT JOIN civicrm_contact c_$fieldName ON c_$fieldName.id = `$table_name`.`$column_name`");
+ $this->selectFields["$table_name.$column_name"] = $fieldName . "_id";
+ // We will call the contact table for the join c_XX.
+ $this->selectFields["c_$fieldName.sort_name"] = $fieldName;
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function buildWhereClause() {
+ foreach ($this->where as $clause) {
+ $sql_clause = $this->treeWalkWhereClause($clause);
+ $this->query->where($sql_clause);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function buildOrderBy() {
+ foreach ($this->orderBy as $field => $dir) {
+ if ($dir !== 'ASC' && $dir !== 'DESC') {
+ throw new \API_Exception("Invalid sort direction. Cannot order by $field $dir");
+ }
+ if ($this->getField($field)) {
+ $this->query->orderBy(self::MAIN_TABLE_ALIAS . '.' . $field . " $dir");
+ }
+ // TODO: Handle joined fields, custom fields, etc.
+ else {
+ throw new \API_Exception("Invalid sort field. Cannot order by $field $dir");
+ }
+ }
+ }
+
+ /**
+ * Recursively validate and transform a branch or leaf clause array to SQL.
+ *
+ * @param array $clause
+ * @return string SQL where clause
+ *
+ * @uses validateClauseAndComposeSql() to generate the SQL etc.
+ * @todo if an 'and' is nested within and 'and' (or or-in-or) then should
+ * flatten that to be a single list of clauses.
+ */
+ protected function treeWalkWhereClause($clause) {
+ switch ($clause[0]) {
+ case 'OR':
+ case 'AND':
+ // handle branches
+ if (count($clause[1]) === 1) {
+ // a single set so AND|OR is immaterial
+ return $this->treeWalkWhereClause($clause[1][0]);
+ }
+ else {
+ $sql_subclauses = [];
+ foreach ($clause[1] as $subclause) {
+ $sql_subclauses[] = $this->treeWalkWhereClause($subclause);
+ }
+ return '(' . implode("\n" . $clause[0], $sql_subclauses) . ')';
+ }
+
+ case 'NOT':
+ // If we get a group of clauses with no operator, assume AND
+ if (!is_string($clause[1][0])) {
+ $clause[1] = ['AND', $clause[1]];
+ }
+ return 'NOT (' . $this->treeWalkWhereClause($clause[1]) . ')';
+
+ default:
+ return $this->validateClauseAndComposeSql($clause);
+ }
+ }
+
+ /**
+ * Validate and transform a leaf clause array to SQL.
+ * @param array $clause [$fieldName, $operator, $criteria]
+ * @return string SQL
+ * @throws \API_Exception
+ * @throws \Exception
+ */
+ protected function validateClauseAndComposeSql($clause) {
+ // Pad array for unary operators
+ list($key, $operator, $value) = array_pad($clause, 3, NULL);
+ $fieldSpec = $this->getField($key);
+ // derive table and column:
+ $table_name = NULL;
+ $column_name = NULL;
+ if (in_array($key, $this->entityFieldNames)) {
+ $table_name = self::MAIN_TABLE_ALIAS;
+ $column_name = $key;
+ }
+ elseif (strpos($key, '.') && isset($this->fkSelectAliases[$key])) {
+ list($table_name, $column_name) = explode('.', $this->fkSelectAliases[$key]);
+ }
+
+ if (!$table_name || !$column_name) {
+ throw new \API_Exception("Invalid field '$key' in where clause.");
+ }
+
+ FormattingUtil::formatValue($value, $fieldSpec, $this->getEntity());
+
+ $sql_clause = \CRM_Core_DAO::createSQLFilter("`$table_name`.`$column_name`", [$operator => $value]);
+ if ($sql_clause === NULL) {
+ throw new \API_Exception("Invalid value in where clause for field '$key'");
+ }
+ return $sql_clause;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getFields() {
+ $fields = civicrm_api4($this->entity, 'getFields', ['action' => 'get', 'checkPermissions' => $this->checkPermissions, 'includeCustom' => FALSE])->indexBy('name');
+ return (array) $fields;
+ }
+
+ /**
+ * Fetch a field from the getFields list
+ *
+ * @param string $fieldName
+ *
+ * @return string|null
+ */
+ protected function getField($fieldName) {
+ if ($fieldName) {
+ $fieldPath = explode('.', $fieldName);
+ if (count($fieldPath) > 1) {
+ $fieldName = implode('.', array_slice($fieldPath, -2));
+ }
+ return UtilsArray::value($fieldName, $this->apiFieldSpec);
+ }
+ return NULL;
+ }
+
+ /**
+ * @param $key
+ * @throws \API_Exception
+ */
+ protected function joinFK($key) {
+ $pathArray = explode('.', $key);
+
+ if (count($pathArray) < 2) {
+ return;
+ }
+
+ /** @var \Civi\Api4\Service\Schema\Joiner $joiner */
+ $joiner = \Civi::container()->get('joiner');
+ $field = array_pop($pathArray);
+ $pathString = implode('.', $pathArray);
+
+ if (!$joiner->canJoin($this, $pathString)) {
+ return;
+ }
+
+ $joinPath = $joiner->join($this, $pathString);
+ /** @var Joinable $lastLink */
+ $lastLink = array_pop($joinPath);
+
+ // Cache field info for retrieval by $this->getField()
+ $prefix = array_pop($pathArray) . '.';
+ if (!isset($this->apiFieldSpec[$prefix . $field])) {
+ $joinEntity = $lastLink->getEntity();
+ // Custom fields are already prefixed
+ if ($lastLink instanceof CustomGroupJoinable) {
+ $prefix = '';
+ }
+ foreach ($lastLink->getEntityFields() as $fieldObject) {
+ $this->apiFieldSpec[$prefix . $fieldObject->getName()] = $fieldObject->toArray() + ['entity' => $joinEntity];
+ }
+ }
+
+ if (!$lastLink->getField($field)) {
+ throw new \API_Exception('Invalid join');
+ }
+
+ // custom groups use aliases for field names
+ if ($lastLink instanceof CustomGroupJoinable) {
+ $field = $lastLink->getSqlColumn($field);
+ }
+
+ $this->fkSelectAliases[$key] = sprintf('%s.%s', $lastLink->getAlias(), $field);
+ }
+
+ /**
+ * @param Joinable $joinable
+ *
+ * @return $this
+ */
+ public function addJoinedTable(Joinable $joinable) {
+ $this->joinedTables[] = $joinable;
+
+ return $this;
+ }
+
+ /**
+ * @return FALSE|string
+ */
+ public function getFrom() {
+ return TableHelper::getTableForClass(TableHelper::getFullName($this->entity));
+ }
+
+ /**
+ * @return string
+ */
+ public function getEntity() {
+ return $this->entity;
+ }
+
+ /**
+ * @return array
+ */
+ public function getSelect() {
+ return $this->select;
+ }
+
+ /**
+ * @return array
+ */
+ public function getWhere() {
+ return $this->where;
+ }
+
+ /**
+ * @return array
+ */
+ public function getOrderBy() {
+ return $this->orderBy;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getLimit() {
+ return $this->limit;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getOffset() {
+ return $this->offset;
+ }
+
+ /**
+ * @return array
+ */
+ public function getSelectFields() {
+ return $this->selectFields;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isFillUniqueFields() {
+ return $this->isFillUniqueFields;
+ }
+
+ /**
+ * @return \CRM_Utils_SQL_Select
+ */
+ public function getQuery() {
+ return $this->query;
+ }
+
+ /**
+ * @return array
+ */
+ public function getJoins() {
+ return $this->joins;
+ }
+
+ /**
+ * @return array
+ */
+ public function getApiFieldSpec() {
+ return $this->apiFieldSpec;
+ }
+
+ /**
+ * @return array
+ */
+ public function getEntityFieldNames() {
+ return $this->entityFieldNames;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAclFields() {
+ return $this->aclFields;
+ }
+
+ /**
+ * @return bool|string
+ */
+ public function getCheckPermissions() {
+ return $this->checkPermissions;
+ }
+
+ /**
+ * @return int
+ */
+ public function getApiVersion() {
+ return $this->apiVersion;
+ }
+
+ /**
+ * @return array
+ */
+ public function getFkSelectAliases() {
+ return $this->fkSelectAliases;
+ }
+
+ /**
+ * @return Joinable[]
+ */
+ public function getJoinedTables() {
+ return $this->joinedTables;
+ }
+
+ /**
+ * @return Joinable
+ */
+ public function getJoinedTable($alias) {
+ foreach ($this->joinedTables as $join) {
+ if ($join->getAlias() == $alias) {
+ return $join;
+ }
+ }
+ }
+
+ /**
+ * Get table name on basis of entity
+ *
+ * @param string $baoName
+ *
+ * @return void
+ */
+ public function getTableName($baoName) {
+ if (strstr($this->entity, 'Custom_')) {
+ $this->query = \CRM_Utils_SQL_Select::from(CoreUtil::getCustomTableByName(str_replace('Custom_', '', $this->entity)) . ' ' . self::MAIN_TABLE_ALIAS);
+ $this->entityFieldNames = array_keys($this->apiFieldSpec);
+ }
+ else {
+ $bao = new $baoName();
+ $this->query = \CRM_Utils_SQL_Select::from($bao->tableName() . ' ' . self::MAIN_TABLE_ALIAS);
+ $bao->free();
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Relationship.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Relationship.php
new file mode 100644
index 00000000..5161f05f
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Relationship.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Relationship entity.
+ *
+ * @package Civi\Api4
+ */
+class Relationship extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/RelationshipType.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/RelationshipType.php
new file mode 100644
index 00000000..1cd335cd
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/RelationshipType.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * RelationshipType entity.
+ *
+ * @package Civi\Api4
+ */
+class RelationshipType extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/ActivityToActivityContactAssigneesJoinable.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/ActivityToActivityContactAssigneesJoinable.php
new file mode 100644
index 00000000..191f4389
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/ActivityToActivityContactAssigneesJoinable.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Civi\Api4\Service\Schema\Joinable;
+
+class ActivityToActivityContactAssigneesJoinable extends Joinable {
+ /**
+ * @var string
+ */
+ protected $baseTable = 'civicrm_activity';
+
+ /**
+ * @var string
+ */
+ protected $baseColumn = 'id';
+
+ /**
+ * @param $alias
+ */
+ public function __construct($alias) {
+ $optionValueTable = 'civicrm_option_value';
+ $optionGroupTable = 'civicrm_option_group';
+
+ $subSubSelect = sprintf(
+ 'SELECT id FROM %s WHERE name = "%s"',
+ $optionGroupTable,
+ 'activity_contacts'
+ );
+
+ $subSelect = sprintf(
+ 'SELECT value FROM %s WHERE name = "%s" AND option_group_id = (%s)',
+ $optionValueTable,
+ 'Activity Assignees',
+ $subSubSelect
+ );
+
+ $this->addCondition(sprintf('%s.record_type_id = (%s)', $alias, $subSelect));
+ parent::__construct('civicrm_activity_contact', 'activity_id', $alias);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/BridgeJoinable.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/BridgeJoinable.php
new file mode 100644
index 00000000..370c5898
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/BridgeJoinable.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Civi\Api4\Service\Schema\Joinable;
+
+class BridgeJoinable extends Joinable {
+ /**
+ * @var Joinable
+ */
+ protected $middleLink;
+
+ public function __construct($targetTable, $targetColumn, $alias, Joinable $middleLink) {
+ parent::__construct($targetTable, $targetColumn, $alias);
+ $this->middleLink = $middleLink;
+ }
+
+ /**
+ * @return Joinable
+ */
+ public function getMiddleLink() {
+ return $this->middleLink;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/CustomGroupJoinable.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/CustomGroupJoinable.php
new file mode 100644
index 00000000..a1dd1a1d
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/CustomGroupJoinable.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Civi\Api4\Service\Schema\Joinable;
+
+use Civi\Api4\CustomField;
+
+class CustomGroupJoinable extends Joinable {
+
+ /**
+ * @var string
+ */
+ protected $joinSide = self::JOIN_SIDE_LEFT;
+
+ /**
+ * @var string
+ *
+ * Name of the custom field column.
+ */
+ protected $columns;
+
+ /**
+ * @param $targetTable
+ * @param $alias
+ * @param bool $isMultiRecord
+ * @param null $entity
+ */
+ public function __construct($targetTable, $alias, $isMultiRecord, $entity, $columns) {
+ $this->entity = $entity;
+ $this->columns = $columns;
+ parent::__construct($targetTable, 'entity_id', $alias);
+ $this->joinType = $isMultiRecord ?
+ self::JOIN_TYPE_ONE_TO_MANY : self::JOIN_TYPE_ONE_TO_ONE;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getEntityFields() {
+ if (!$this->entityFields) {
+ $fields = CustomField::get()
+ ->setSelect(['custom_group.name', 'custom_group_id', 'name', 'label', 'data_type', 'html_type', 'is_required', 'is_searchable', 'is_search_range', 'weight', 'is_active', 'is_view', 'option_group_id', 'default_value'])
+ ->addWhere('custom_group.table_name', '=', $this->getTargetTable())
+ ->execute();
+ foreach ($fields as $field) {
+ $this->entityFields[] = \Civi\Api4\Service\Spec\SpecFormatter::arrayToField($field, $this->getEntity());
+ }
+ }
+ return $this->entityFields;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getField($fieldName) {
+ foreach ($this->getEntityFields() as $field) {
+ $name = $field->getName();
+ if ($name === $fieldName || strrpos($name, '.' . $fieldName) === strlen($name) - strlen($fieldName) - 1) {
+ return $field;
+ }
+ }
+ return NULL;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSqlColumn($fieldName) {
+ return $this->columns[$fieldName];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/Joinable.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/Joinable.php
new file mode 100644
index 00000000..0e92e3ab
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/Joinable.php
@@ -0,0 +1,277 @@
+<?php
+
+namespace Civi\Api4\Service\Schema\Joinable;
+
+use Civi\Api4\Service\Spec\FieldSpec;
+use CRM_Core_DAO_AllCoreTables as Tables;
+
+class Joinable {
+
+ const JOIN_SIDE_LEFT = 'LEFT';
+ const JOIN_SIDE_INNER = 'INNER';
+
+ const JOIN_TYPE_ONE_TO_ONE = '1_to_1';
+ const JOIN_TYPE_MANY_TO_ONE = 'n_to_1';
+ const JOIN_TYPE_ONE_TO_MANY = '1_to_n';
+
+ /**
+ * @var string
+ */
+ protected $baseTable;
+
+ /**
+ * @var string
+ */
+ protected $baseColumn;
+
+ /**
+ * @var string
+ */
+ protected $targetTable;
+
+ /**
+ * @var string
+ *
+ * Name (or alias) of the target column)
+ */
+ protected $targetColumn;
+
+ /**
+ * @var string
+ */
+ protected $alias;
+
+ /**
+ * @var array
+ */
+ protected $conditions = [];
+
+ /**
+ * @var string
+ */
+ protected $joinSide = self::JOIN_SIDE_LEFT;
+
+ /**
+ * @var int
+ */
+ protected $joinType = self::JOIN_TYPE_ONE_TO_ONE;
+
+ /**
+ * @var string
+ */
+ protected $entity;
+
+ /**
+ * @var array
+ */
+ protected $entityFields;
+
+ /**
+ * @param $targetTable
+ * @param $targetColumn
+ * @param string|null $alias
+ */
+ public function __construct($targetTable, $targetColumn, $alias = NULL) {
+ $this->targetTable = $targetTable;
+ $this->targetColumn = $targetColumn;
+ if (!$this->entity) {
+ $this->entity = Tables::getBriefName(Tables::getClassForTable($targetTable));
+ }
+ $this->alias = $alias ?: str_replace('civicrm_', '', $targetTable);
+ }
+
+ /**
+ * Gets conditions required when joining to a base table
+ *
+ * @param string|null $baseTableAlias
+ * Name of the base table, if aliased.
+ *
+ * @return array
+ */
+ public function getConditionsForJoin($baseTableAlias = NULL) {
+ $baseCondition = sprintf(
+ '%s.%s = %s.%s',
+ $baseTableAlias ?: $this->baseTable,
+ $this->baseColumn,
+ $this->getAlias(),
+ $this->targetColumn
+ );
+
+ return array_merge([$baseCondition], $this->conditions);
+ }
+
+ /**
+ * @return string
+ */
+ public function getBaseTable() {
+ return $this->baseTable;
+ }
+
+ /**
+ * @param string $baseTable
+ *
+ * @return $this
+ */
+ public function setBaseTable($baseTable) {
+ $this->baseTable = $baseTable;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBaseColumn() {
+ return $this->baseColumn;
+ }
+
+ /**
+ * @param string $baseColumn
+ *
+ * @return $this
+ */
+ public function setBaseColumn($baseColumn) {
+ $this->baseColumn = $baseColumn;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAlias() {
+ return $this->alias;
+ }
+
+ /**
+ * @param string $alias
+ *
+ * @return $this
+ */
+ public function setAlias($alias) {
+ $this->alias = $alias;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTargetTable() {
+ return $this->targetTable;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTargetColumn() {
+ return $this->targetColumn;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEntity() {
+ return $this->entity;
+ }
+
+ /**
+ * @param $condition
+ *
+ * @return $this
+ */
+ public function addCondition($condition) {
+ $this->conditions[] = $condition;
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExtraJoinConditions() {
+ return $this->conditions;
+ }
+
+ /**
+ * @param array $conditions
+ *
+ * @return $this
+ */
+ public function setConditions($conditions) {
+ $this->conditions = $conditions;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getJoinSide() {
+ return $this->joinSide;
+ }
+
+ /**
+ * @param string $joinSide
+ *
+ * @return $this
+ */
+ public function setJoinSide($joinSide) {
+ $this->joinSide = $joinSide;
+
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getJoinType() {
+ return $this->joinType;
+ }
+
+ /**
+ * @param int $joinType
+ *
+ * @return $this
+ */
+ public function setJoinType($joinType) {
+ $this->joinType = $joinType;
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray() {
+ return get_object_vars($this);
+ }
+
+ /**
+ * @return \Civi\Api4\Service\Spec\FieldSpec[]
+ */
+ public function getEntityFields() {
+ if (!$this->entityFields) {
+ $bao = Tables::getClassForTable($this->getTargetTable());
+ if ($bao) {
+ foreach ($bao::fields() as $field) {
+ $this->entityFields[] = \Civi\Api4\Service\Spec\SpecFormatter::arrayToField($field, $this->getEntity());
+ }
+ }
+ }
+ return $this->entityFields;
+ }
+
+ /**
+ * @return \Civi\Api4\Service\Spec\FieldSpec|NULL
+ */
+ public function getField($fieldName) {
+ foreach ($this->getEntityFields() as $field) {
+ if ($field->getName() === $fieldName) {
+ return $field;
+ }
+ }
+ return NULL;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/OptionValueJoinable.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/OptionValueJoinable.php
new file mode 100644
index 00000000..96f65488
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/OptionValueJoinable.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Civi\Api4\Service\Schema\Joinable;
+
+class OptionValueJoinable extends Joinable {
+ /**
+ * @var string
+ */
+ protected $optionGroupName;
+
+ /**
+ * @param string $optionGroup
+ * Can be either the option group name or ID
+ * @param string|null $alias
+ * The join alias
+ * @param string $keyColumn
+ * Which column to use to join, defaults to "value"
+ */
+ public function __construct($optionGroup, $alias = NULL, $keyColumn = 'value') {
+ $this->optionGroupName = $optionGroup;
+ $optionValueTable = 'civicrm_option_value';
+
+ // default join alias to option group name, e.g. activity_type
+ if (!$alias && !is_numeric($optionGroup)) {
+ $alias = $optionGroup;
+ }
+
+ parent::__construct($optionValueTable, $keyColumn, $alias);
+
+ if (!is_numeric($optionGroup)) {
+ $subSelect = 'SELECT id FROM civicrm_option_group WHERE name = "%s"';
+ $subQuery = sprintf($subSelect, $optionGroup);
+ $condition = sprintf('%s.option_group_id = (%s)', $alias, $subQuery);
+ }
+ else {
+ $condition = sprintf('%s.option_group_id = %d', $alias, $optionGroup);
+ }
+
+ $this->addCondition($condition);
+ }
+
+ /**
+ * The existing condition must also be re-aliased
+ *
+ * @param string $alias
+ *
+ * @return $this
+ */
+ public function setAlias($alias) {
+ foreach ($this->conditions as $index => $condition) {
+ $search = $this->alias . '.';
+ $replace = $alias . '.';
+ $this->conditions[$index] = str_replace($search, $replace, $condition);
+ }
+
+ parent::setAlias($alias);
+
+ return $this;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joiner.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joiner.php
new file mode 100644
index 00000000..cb30ab57
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Joiner.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Civi\Api4\Service\Schema;
+
+use Civi\Api4\Query\Api4SelectQuery;
+use Civi\Api4\Service\Schema\Joinable\Joinable;
+
+class Joiner {
+ /**
+ * @var SchemaMap
+ */
+ protected $schemaMap;
+
+ /**
+ * @var Joinable[][]
+ */
+ protected $cache = [];
+
+ /**
+ * @param SchemaMap $schemaMap
+ */
+ public function __construct(SchemaMap $schemaMap) {
+ $this->schemaMap = $schemaMap;
+ }
+
+ /**
+ * @param Api4SelectQuery $query
+ * The query object to do the joins on
+ * @param string $joinPath
+ * A path of aliases in dot notation, e.g. contact.phone
+ * @param string $side
+ * Can be LEFT or INNER
+ *
+ * @throws \Exception
+ * @return Joinable[]
+ * The path used to make the join
+ */
+ public function join(Api4SelectQuery $query, $joinPath, $side = 'LEFT') {
+ $fullPath = $this->getPath($query->getFrom(), $joinPath);
+ $baseTable = $query::MAIN_TABLE_ALIAS;
+
+ foreach ($fullPath as $link) {
+ $target = $link->getTargetTable();
+ $alias = $link->getAlias();
+ $conditions = $link->getConditionsForJoin($baseTable);
+
+ $query->join($side, $target, $alias, $conditions);
+ $query->addJoinedTable($link);
+
+ $baseTable = $link->getAlias();
+ }
+
+ return $fullPath;
+ }
+
+ /**
+ * @param Api4SelectQuery $query
+ * @param $joinPath
+ *
+ * @return bool
+ */
+ public function canJoin(Api4SelectQuery $query, $joinPath) {
+ return !empty($this->getPath($query->getFrom(), $joinPath));
+ }
+
+ /**
+ * @param string $baseTable
+ * @param string $joinPath
+ *
+ * @return array
+ * @throws \Exception
+ */
+ protected function getPath($baseTable, $joinPath) {
+ $cacheKey = sprintf('%s.%s', $baseTable, $joinPath);
+ if (!isset($this->cache[$cacheKey])) {
+ $stack = explode('.', $joinPath);
+ $fullPath = [];
+
+ foreach ($stack as $key => $targetAlias) {
+ $links = $this->schemaMap->getPath($baseTable, $targetAlias);
+
+ if (empty($links)) {
+ throw new \Exception(sprintf('Cannot join %s to %s', $baseTable, $targetAlias));
+ }
+ else {
+ $fullPath = array_merge($fullPath, $links);
+ $lastLink = end($links);
+ $baseTable = $lastLink->getTargetTable();
+ }
+ }
+
+ $this->cache[$cacheKey] = $fullPath;
+ }
+
+ return $this->cache[$cacheKey];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/SchemaMap.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/SchemaMap.php
new file mode 100644
index 00000000..3989afeb
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/SchemaMap.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Civi\Api4\Service\Schema;
+
+use Civi\Api4\Service\Schema\Joinable\BridgeJoinable;
+use Civi\Api4\Service\Schema\Joinable\Joinable;
+
+class SchemaMap {
+
+ const MAX_JOIN_DEPTH = 3;
+
+ /**
+ * @var Table[]
+ */
+ protected $tables = [];
+
+ /**
+ * @param $baseTableName
+ * @param $targetTableAlias
+ *
+ * @return Joinable[]
+ * Array of links to the target table, empty if no path found
+ */
+ public function getPath($baseTableName, $targetTableAlias) {
+ $table = $this->getTableByName($baseTableName);
+ $path = [];
+
+ if (!$table) {
+ return $path;
+ }
+
+ $this->findPaths($table, $targetTableAlias, 1, $path);
+
+ foreach ($path as $index => $pathLink) {
+ if ($pathLink instanceof BridgeJoinable) {
+ $start = array_slice($path, 0, $index);
+ $middle = [$pathLink->getMiddleLink()];
+ $end = array_slice($path, $index, count($path) - $index);
+ $path = array_merge($start, $middle, $end);
+ }
+ }
+
+ return $path;
+ }
+
+ /**
+ * @return Table[]
+ */
+ public function getTables() {
+ return $this->tables;
+ }
+
+ /**
+ * @param $name
+ *
+ * @return Table|null
+ */
+ public function getTableByName($name) {
+ foreach ($this->tables as $table) {
+ if ($table->getName() === $name) {
+ return $table;
+ }
+ }
+
+ return NULL;
+ }
+
+ /**
+ * Adds a table to the schema map if it has not already been added
+ *
+ * @param Table $table
+ *
+ * @return $this
+ */
+ public function addTable(Table $table) {
+ if (!$this->getTableByName($table->getName())) {
+ $this->tables[] = $table;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $tables
+ */
+ public function addTables(array $tables) {
+ foreach ($tables as $table) {
+ $this->addTable($table);
+ }
+ }
+
+ /**
+ * Recursive function to traverse the schema looking for a path
+ *
+ * @param Table $table
+ * The current table to base fromm
+ * @param string $target
+ * The target joinable table alias
+ * @param int $depth
+ * The current level of recursion which reflects the number of joins needed
+ * @param Joinable[] $path
+ * (By-reference) The possible paths to the target table
+ * @param Joinable[] $currentPath
+ * For internal use only to track the path to reach the target table
+ */
+ private function findPaths(Table $table, $target, $depth, &$path, $currentPath = []
+ ) {
+ static $visited = [];
+
+ // reset if new call
+ if ($depth === 1) {
+ $visited = [];
+ }
+
+ $canBeShorter = empty($path) || count($currentPath) + 1 < count($path);
+ $tooFar = $depth > self::MAX_JOIN_DEPTH;
+ $beenHere = in_array($table->getName(), $visited);
+
+ if ($tooFar || $beenHere || !$canBeShorter) {
+ return;
+ }
+
+ // prevent circular reference
+ $visited[] = $table->getName();
+
+ foreach ($table->getExternalLinks() as $link) {
+ if ($link->getAlias() === $target) {
+ $path = array_merge($currentPath, [$link]);
+ }
+ else {
+ $linkTable = $this->getTableByName($link->getTargetTable());
+ if ($linkTable) {
+ $nextStep = array_merge($currentPath, [$link]);
+ $this->findPaths($linkTable, $target, $depth + 1, $path, $nextStep);
+ }
+ }
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/SchemaMapBuilder.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/SchemaMapBuilder.php
new file mode 100644
index 00000000..b578b73a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/SchemaMapBuilder.php
@@ -0,0 +1,217 @@
+<?php
+
+namespace Civi\Api4\Service\Schema;
+
+use Civi\Api4\Entity;
+use Civi\Api4\Event\Events;
+use Civi\Api4\Event\SchemaMapBuildEvent;
+use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable;
+use Civi\Api4\Service\Schema\Joinable\Joinable;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Civi\Api4\Service\Schema\Joinable\OptionValueJoinable;
+use CRM_Core_DAO_AllCoreTables as TableHelper;
+use CRM_Utils_Array as UtilsArray;
+
+class SchemaMapBuilder {
+ /**
+ * @var EventDispatcherInterface
+ */
+ protected $dispatcher;
+ /**
+ * @var array
+ */
+ protected $apiEntities;
+
+ /**
+ * @param EventDispatcherInterface $dispatcher
+ */
+ public function __construct(EventDispatcherInterface $dispatcher) {
+ $this->dispatcher = $dispatcher;
+ $this->apiEntities = array_keys((array) Entity::get()->setCheckPermissions(FALSE)->addSelect('name')->execute()->indexBy('name'));
+ }
+
+ /**
+ * @return SchemaMap
+ */
+ public function build() {
+ $map = new SchemaMap();
+ $this->loadTables($map);
+
+ $event = new SchemaMapBuildEvent($map);
+ $this->dispatcher->dispatch(Events::SCHEMA_MAP_BUILD, $event);
+
+ return $map;
+ }
+
+ /**
+ * Add all tables and joins
+ *
+ * @param SchemaMap $map
+ */
+ private function loadTables(SchemaMap $map) {
+ /** @var \CRM_Core_DAO $daoName */
+ foreach (TableHelper::get() as $daoName => $data) {
+ $table = new Table($data['table']);
+ foreach ($daoName::fields() as $field => $fieldData) {
+ $this->addJoins($table, $field, $fieldData);
+ }
+ $map->addTable($table);
+ if (in_array($data['name'], $this->apiEntities)) {
+ $this->addCustomFields($map, $table, $data['name']);
+ }
+ }
+
+ $this->addBackReferences($map);
+ }
+
+ /**
+ * @param Table $table
+ * @param string $field
+ * @param array $data
+ */
+ private function addJoins(Table $table, $field, array $data) {
+ $fkClass = UtilsArray::value('FKClassName', $data);
+
+ // can there be multiple methods e.g. pseudoconstant and fkclass
+ if ($fkClass) {
+ $tableName = TableHelper::getTableForClass($fkClass);
+ $fkKey = UtilsArray::value('FKKeyColumn', $data, 'id');
+ $alias = str_replace('_id', '', $field);
+ $joinable = new Joinable($tableName, $fkKey, $alias);
+ $joinable->setJoinType($joinable::JOIN_TYPE_MANY_TO_ONE);
+ $table->addTableLink($field, $joinable);
+ }
+ elseif (UtilsArray::value('pseudoconstant', $data)) {
+ $this->addPseudoConstantJoin($table, $field, $data);
+ }
+ }
+
+ /**
+ * @param Table $table
+ * @param string $field
+ * @param array $data
+ */
+ private function addPseudoConstantJoin(Table $table, $field, array $data) {
+ $pseudoConstant = UtilsArray::value('pseudoconstant', $data);
+ $tableName = UtilsArray::value('table', $pseudoConstant);
+ $optionGroupName = UtilsArray::value('optionGroupName', $pseudoConstant);
+ $keyColumn = UtilsArray::value('keyColumn', $pseudoConstant, 'id');
+
+ if ($tableName) {
+ $alias = str_replace('civicrm_', '', $tableName);
+ $joinable = new Joinable($tableName, $keyColumn, $alias);
+ $condition = UtilsArray::value('condition', $pseudoConstant);
+ if ($condition) {
+ $joinable->addCondition($condition);
+ }
+ $table->addTableLink($field, $joinable);
+ }
+ elseif ($optionGroupName) {
+ $keyColumn = UtilsArray::value('keyColumn', $pseudoConstant, 'value');
+ $joinable = new OptionValueJoinable($optionGroupName, NULL, $keyColumn);
+
+ if (!empty($data['serialize'])) {
+ $joinable->setJoinType($joinable::JOIN_TYPE_ONE_TO_MANY);
+ }
+
+ $table->addTableLink($field, $joinable);
+ }
+ }
+
+ /**
+ * Loop through existing links and provide link from the other side
+ *
+ * @param SchemaMap $map
+ */
+ private function addBackReferences(SchemaMap $map) {
+ foreach ($map->getTables() as $table) {
+ foreach ($table->getTableLinks() as $link) {
+ // there are too many possible joins from option value so skip
+ if ($link instanceof OptionValueJoinable) {
+ continue;
+ }
+
+ $target = $map->getTableByName($link->getTargetTable());
+ $tableName = $link->getBaseTable();
+ $plural = str_replace('civicrm_', '', $this->getPlural($tableName));
+ $joinable = new Joinable($tableName, $link->getBaseColumn(), $plural);
+ $joinable->setJoinType($joinable::JOIN_TYPE_ONE_TO_MANY);
+ $target->addTableLink($link->getTargetColumn(), $joinable);
+ }
+ }
+ }
+
+ /**
+ * Simple implementation of pluralization.
+ * Could be replaced with symfony/inflector
+ *
+ * @param string $singular
+ *
+ * @return string
+ */
+ private function getPlural($singular) {
+ $last_letter = substr($singular, -1);
+ switch ($last_letter) {
+ case 'y':
+ return substr($singular, 0, -1) . 'ies';
+
+ case 's':
+ return $singular . 'es';
+
+ default:
+ return $singular . 's';
+ }
+ }
+
+ /**
+ * @param \Civi\Api4\Service\Schema\SchemaMap $map
+ * @param \Civi\Api4\Service\Schema\Table $baseTable
+ * @param string $entity
+ */
+ private function addCustomFields(SchemaMap $map, Table $baseTable, $entity) {
+ // Don't be silly
+ if (!array_key_exists($entity, \CRM_Core_SelectValues::customGroupExtends())) {
+ return;
+ }
+ $queryEntity = (array) $entity;
+ if ($entity == 'Contact') {
+ $queryEntity = ['Contact', 'Individual', 'Organization', 'Household'];
+ }
+ $fieldData = \CRM_Utils_SQL_Select::from('civicrm_custom_field f')
+ ->join('custom_group', 'INNER JOIN civicrm_custom_group g ON g.id = f.custom_group_id')
+ ->select(['g.name as custom_group_name', 'g.table_name', 'g.is_multiple', 'f.name', 'label', 'column_name', 'option_group_id'])
+ ->where('g.extends IN (@entity)', ['@entity' => $queryEntity])
+ ->where('g.is_active')
+ ->where('f.is_active')
+ ->execute();
+
+ $links = [];
+
+ while ($fieldData->fetch()) {
+ $tableName = $fieldData->table_name;
+
+ $customTable = $map->getTableByName($tableName);
+ if (!$customTable) {
+ $customTable = new Table($tableName);
+ }
+
+ if (!empty($fieldData->option_group_id)) {
+ $optionValueJoinable = new OptionValueJoinable($fieldData->option_group_id, $fieldData->label);
+ $customTable->addTableLink($fieldData->column_name, $optionValueJoinable);
+ }
+
+ $map->addTable($customTable);
+
+ $alias = $fieldData->custom_group_name;
+ $links[$alias]['tableName'] = $tableName;
+ $links[$alias]['isMultiple'] = !empty($fieldData->is_multiple);
+ $links[$alias]['columns'][$fieldData->name] = $fieldData->column_name;
+ }
+
+ foreach ($links as $alias => $link) {
+ $joinable = new CustomGroupJoinable($link['tableName'], $alias, $link['isMultiple'], $entity, $link['columns']);
+ $baseTable->addTableLink('id', $joinable);
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Table.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Table.php
new file mode 100644
index 00000000..1f464a45
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Schema/Table.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Civi\Api4\Service\Schema;
+
+use Civi\Api4\Service\Schema\Joinable\Joinable;
+
+class Table {
+
+ /**
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * @var Joinable[]
+ * Array of links to other tables
+ */
+ protected $tableLinks = [];
+
+ /**
+ * @param $name
+ */
+ public function __construct($name) {
+ $this->name = $name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name) {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * @return Joinable[]
+ */
+ public function getTableLinks() {
+ return $this->tableLinks;
+ }
+
+ /**
+ * @return Joinable[]
+ * Only those links that are not joining the table to itself
+ */
+ public function getExternalLinks() {
+ return array_filter($this->tableLinks, function (Joinable $joinable) {
+ return $joinable->getTargetTable() !== $this->getName();
+ });
+ }
+
+ /**
+ * @param Joinable $linkToRemove
+ */
+ public function removeLink(Joinable $linkToRemove) {
+ foreach ($this->tableLinks as $index => $link) {
+ if ($link === $linkToRemove) {
+ unset($this->tableLinks[$index]);
+ }
+ }
+ }
+
+ /**
+ * @param string $baseColumn
+ * @param Joinable $joinable
+ *
+ * @return $this
+ */
+ public function addTableLink($baseColumn, Joinable $joinable) {
+ $target = $joinable->getTargetTable();
+ $targetCol = $joinable->getTargetColumn();
+ $alias = $joinable->getAlias();
+
+ if (!$this->hasLink($target, $targetCol, $alias)) {
+ if (!$joinable->getBaseTable()) {
+ $joinable->setBaseTable($this->getName());
+ }
+ if (!$joinable->getBaseColumn()) {
+ $joinable->setBaseColumn($baseColumn);
+ }
+ $this->tableLinks[] = $joinable;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param mixed $tableLinks
+ *
+ * @return $this
+ */
+ public function setTableLinks($tableLinks) {
+ $this->tableLinks = $tableLinks;
+
+ return $this;
+ }
+
+ /**
+ * @param $target
+ * @param $targetCol
+ * @param $alias
+ *
+ * @return bool
+ */
+ private function hasLink($target, $targetCol, $alias) {
+ foreach ($this->tableLinks as $link) {
+ if ($link->getTargetTable() === $target
+ && $link->getTargetColumn() === $targetCol
+ && $link->getAlias() === $alias
+ ) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/CustomFieldSpec.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/CustomFieldSpec.php
new file mode 100644
index 00000000..2c689344
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/CustomFieldSpec.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace Civi\Api4\Service\Spec;
+
+class CustomFieldSpec extends FieldSpec {
+ /**
+ * @var int
+ */
+ protected $customFieldId;
+
+ /**
+ * @var int
+ */
+ protected $customGroup;
+
+ /**
+ * @var string
+ */
+ protected $tableName;
+
+ /**
+ * @var string
+ */
+ protected $columnName;
+
+ /**
+ * @inheritDoc
+ */
+ public function setDataType($dataType) {
+ switch ($dataType) {
+ case 'ContactReference':
+ $this->setFkEntity('Contact');
+ $dataType = 'Integer';
+ break;
+
+ case 'File':
+ case 'StateProvince':
+ case 'Country':
+ $this->setFkEntity($dataType);
+ $dataType = 'Integer';
+ break;
+ }
+ return parent::setDataType($dataType);
+ }
+
+ /**
+ * @return int
+ */
+ public function getCustomFieldId() {
+ return $this->customFieldId;
+ }
+
+ /**
+ * @param int $customFieldId
+ *
+ * @return $this
+ */
+ public function setCustomFieldId($customFieldId) {
+ $this->customFieldId = $customFieldId;
+
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCustomGroupName() {
+ return $this->customGroup;
+ }
+
+ /**
+ * @param string $customGroupName
+ *
+ * @return $this
+ */
+ public function setCustomGroupName($customGroupName) {
+ $this->customGroup = $customGroupName;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCustomTableName() {
+ return $this->tableName;
+ }
+
+ /**
+ * @param string $customFieldColumnName
+ *
+ * @return $this
+ */
+ public function setCustomTableName($customFieldColumnName) {
+ $this->tableName = $customFieldColumnName;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCustomFieldColumnName() {
+ return $this->columnName;
+ }
+
+ /**
+ * @param string $customFieldColumnName
+ *
+ * @return $this
+ */
+ public function setCustomFieldColumnName($customFieldColumnName) {
+ $this->columnName = $customFieldColumnName;
+
+ return $this;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/FieldSpec.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/FieldSpec.php
new file mode 100644
index 00000000..1db2941e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/FieldSpec.php
@@ -0,0 +1,320 @@
+<?php
+
+namespace Civi\Api4\Service\Spec;
+
+use Civi\Api4\Utils\CoreUtil;
+
+class FieldSpec {
+ /**
+ * @var mixed
+ */
+ protected $defaultValue;
+
+ /**
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * @var string
+ */
+ protected $title;
+
+ /**
+ * @var string
+ */
+ protected $entity;
+
+ /**
+ * @var string
+ */
+ protected $description;
+
+ /**
+ * @var bool
+ */
+ protected $required = FALSE;
+
+ /**
+ * @var bool
+ */
+ protected $requiredIf;
+
+ /**
+ * @var array|boolean
+ */
+ protected $options;
+
+ /**
+ * @var string
+ */
+ protected $dataType;
+
+ /**
+ * @var string
+ */
+ protected $fkEntity;
+
+ /**
+ * @var int
+ */
+ protected $serialize;
+
+ /**
+ * Aliases for the valid data types
+ *
+ * @var array
+ */
+ public static $typeAliases = [
+ 'Int' => 'Integer',
+ 'Link' => 'Url',
+ 'Memo' => 'Text',
+ ];
+
+ /**
+ * @param string $name
+ * @param string $entity
+ * @param string $dataType
+ */
+ public function __construct($name, $entity, $dataType = 'String') {
+ $this->entity = $entity;
+ $this->setName($name);
+ $this->setDataType($dataType);
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getDefaultValue() {
+ return $this->defaultValue;
+ }
+
+ /**
+ * @param mixed $defaultValue
+ *
+ * @return $this
+ */
+ public function setDefaultValue($defaultValue) {
+ $this->defaultValue = $defaultValue;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name) {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTitle() {
+ return $this->title;
+ }
+
+ /**
+ * @param string $title
+ *
+ * @return $this
+ */
+ public function setTitle($title) {
+ $this->title = $title;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEntity() {
+ return $this->entity;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDescription() {
+ return $this->description;
+ }
+
+ /**
+ * @param string $description
+ *
+ * @return $this
+ */
+ public function setDescription($description) {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isRequired() {
+ return $this->required;
+ }
+
+ /**
+ * @param bool $required
+ *
+ * @return $this
+ */
+ public function setRequired($required) {
+ $this->required = $required;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getRequiredIf() {
+ return $this->requiredIf;
+ }
+
+ /**
+ * @param bool $required
+ *
+ * @return $this
+ */
+ public function setRequiredIf($requiredIf) {
+ $this->requiredIf = $requiredIf;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDataType() {
+ return $this->dataType;
+ }
+
+ /**
+ * @param $dataType
+ *
+ * @return $this
+ * @throws \Exception
+ */
+ public function setDataType($dataType) {
+ if (array_key_exists($dataType, self::$typeAliases)) {
+ $dataType = self::$typeAliases[$dataType];
+ }
+
+ if (!in_array($dataType, $this->getValidDataTypes())) {
+ throw new \Exception(sprintf('Invalid data type "%s', $dataType));
+ }
+
+ $this->dataType = $dataType;
+
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getSerialize() {
+ return $this->serialize;
+ }
+
+ /**
+ * @param int|null $serialize
+ */
+ public function setSerialize($serialize) {
+ $this->serialize = $serialize;
+ }
+
+ /**
+ * Add valid types that are not not part of \CRM_Utils_Type::dataTypes
+ *
+ * @return array
+ */
+ private function getValidDataTypes() {
+ $extraTypes = ['Boolean', 'Text', 'Float', 'Url'];
+ $extraTypes = array_combine($extraTypes, $extraTypes);
+
+ return array_merge(\CRM_Utils_Type::dataTypes(), $extraTypes);
+ }
+
+ /**
+ * @return array
+ */
+ public function getOptions() {
+ if (!isset($this->options) || $this->options === TRUE) {
+ $fieldName = $this->getName();
+
+ if ($this instanceof CustomFieldSpec) {
+ // buildOptions relies on the custom_* type of field names
+ $fieldName = sprintf('custom_%d', $this->getCustomFieldId());
+ }
+
+ $dao = CoreUtil::getDAOFromApiName($this->getEntity());
+ $options = $dao::buildOptions($fieldName);
+
+ if (!is_array($options) || !$options) {
+ $options = FALSE;
+ }
+
+ $this->setOptions($options);
+ }
+ return $this->options;
+ }
+
+ /**
+ * @param array|bool $options
+ *
+ * @return $this
+ */
+ public function setOptions($options) {
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFkEntity() {
+ return $this->fkEntity;
+ }
+
+ /**
+ * @param string $fkEntity
+ *
+ * @return $this
+ */
+ public function setFkEntity($fkEntity) {
+ $this->fkEntity = $fkEntity;
+
+ return $this;
+ }
+
+ /**
+ * @param array $values
+ * @return array
+ */
+ public function toArray($values = []) {
+ $ret = [];
+ foreach (get_object_vars($this) as $key => $val) {
+ $key = strtolower(preg_replace('/(?=[A-Z])/', '_$0', $key));
+ if (!$values || in_array($key, $values)) {
+ $ret[$key] = $val;
+ }
+ }
+ return $ret;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ActionScheduleCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ActionScheduleCreationSpecProvider.php
new file mode 100644
index 00000000..660bfec9
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ActionScheduleCreationSpecProvider.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\FieldSpec;
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class ActionScheduleCreationSpecProvider implements SpecProviderInterface {
+ /**
+ * @inheritDoc
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('title')->setRequired(TRUE);
+ $spec->getFieldByName('mapping_id')->setRequired(TRUE);
+ $spec->getFieldByName('entity_value')->setRequired(TRUE);
+ $spec->getFieldByName('start_action_date')->setRequiredIf('empty($values.absolute_date)');
+ $spec->getFieldByName('absolute_date')->setRequiredIf('empty($values.start_action_date)');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applies($entity, $action) {
+ return $entity === 'ActionSchedule' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ActivityCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ActivityCreationSpecProvider.php
new file mode 100644
index 00000000..dc254342
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ActivityCreationSpecProvider.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\FieldSpec;
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class ActivityCreationSpecProvider implements SpecProviderInterface {
+ /**
+ * @inheritDoc
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $sourceContactField = new FieldSpec('source_contact_id', 'Activity', 'Integer');
+ $sourceContactField->setRequired(TRUE);
+ $sourceContactField->setFkEntity('Contact');
+
+ $spec->addFieldSpec($sourceContactField);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applies($entity, $action) {
+ return $entity === 'Activity' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/AddressCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/AddressCreationSpecProvider.php
new file mode 100644
index 00000000..afba9c79
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/AddressCreationSpecProvider.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\FieldSpec;
+use Civi\Api4\Service\Spec\RequestSpec;
+
+
+class AddressCreationSpecProvider implements SpecProviderInterface {
+
+ /**
+ * @param RequestSpec $spec
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('contact_id')->setRequired(TRUE);
+ $spec->getFieldByName('location_type_id')->setRequired(TRUE);
+ }
+
+ /**
+ * @param string $entity
+ * @param string $action
+ *
+ * @return bool
+ */
+ public function applies($entity, $action) {
+ return $entity === 'Address' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContactCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContactCreationSpecProvider.php
new file mode 100644
index 00000000..94c68d9d
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContactCreationSpecProvider.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class ContactCreationSpecProvider implements SpecProviderInterface {
+
+ /**
+ * @param RequestSpec $spec
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('contact_type')
+ ->setRequired(TRUE)
+ ->setDefaultValue('Individual');
+
+ $spec->getFieldByName('is_opt_out')->setRequired(FALSE);
+ $spec->getFieldByName('is_deleted')->setRequired(FALSE);
+
+ }
+
+ /**
+ * @param string $entity
+ * @param string $action
+ *
+ * @return bool
+ */
+ public function applies($entity, $action) {
+ return $entity === 'Contact' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContactTypeCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContactTypeCreationSpecProvider.php
new file mode 100644
index 00000000..f55deb1c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContactTypeCreationSpecProvider.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class ContactTypeCreationSpecProvider implements SpecProviderInterface {
+
+ /**
+ * @param RequestSpec $spec
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('label')->setRequired(TRUE);
+ $spec->getFieldByName('name')->setRequired(TRUE);
+ $spec->getFieldByName('parent_id')->setRequired(TRUE);
+
+ }
+
+ /**
+ * @param string $entity
+ * @param string $action
+ *
+ * @return bool
+ */
+ public function applies($entity, $action) {
+ return $entity === 'ContactType' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContributionCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContributionCreationSpecProvider.php
new file mode 100644
index 00000000..14861871
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/ContributionCreationSpecProvider.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class ContributionCreationSpecProvider implements SpecProviderInterface {
+ /**
+ * @inheritDoc
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('financial_type_id')->setRequired(TRUE);
+ $spec->getFieldByName('receive_date')->setDefaultValue('now');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applies($entity, $action) {
+ return $entity === 'Contribution' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/CustomGroupCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/CustomGroupCreationSpecProvider.php
new file mode 100644
index 00000000..cd033754
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/CustomGroupCreationSpecProvider.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class CustomGroupCreationSpecProvider implements SpecProviderInterface {
+ /**
+ * @inheritDoc
+ */
+ public function modifySpec(RequestSpec $spec) {
+ return $spec->getFieldByName('extends')->setRequired(TRUE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applies($entity, $action) {
+ return $entity === 'CustomGroup' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/CustomValueSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/CustomValueSpecProvider.php
new file mode 100644
index 00000000..cd82d438
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/CustomValueSpecProvider.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\FieldSpec;
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class CustomValueSpecProvider implements SpecProviderInterface {
+
+ /**
+ * @inheritDoc
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $action = $spec->getAction();
+ if ($action !== 'create') {
+ $idField = new FieldSpec('id', $spec->getEntity(), 'Integer');
+ $idField->setTitle(ts('Custom Value ID'));
+ $spec->addFieldSpec($idField);
+ }
+ $entityField = new FieldSpec('entity_id', $spec->getEntity(), 'Integer');
+ $entityField->setTitle(ts('Entity ID'));
+ $entityField->setRequired($action === 'create');
+ $entityField->setFkEntity('Contact');
+ $spec->addFieldSpec($entityField);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applies($entity, $action) {
+ return strstr($entity, 'Custom_');
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/EmailCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/EmailCreationSpecProvider.php
new file mode 100644
index 00000000..136b0e54
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/EmailCreationSpecProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class EmailCreationSpecProvider implements SpecProviderInterface {
+ /**
+ * @inheritDoc
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('contact_id')->setRequired(TRUE);
+ $spec->getFieldByName('email')->setRequired(TRUE);
+ $spec->getFieldByName('on_hold')->setRequired(FALSE);
+ $spec->getFieldByName('is_bulkmail')->setRequired(FALSE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applies($entity, $action) {
+ return $entity === 'Email' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/EventCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/EventCreationSpecProvider.php
new file mode 100644
index 00000000..42b74a6f
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/EventCreationSpecProvider.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class EventCreationSpecProvider implements SpecProviderInterface {
+ /**
+ * @inheritDoc
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('is_template')->setRequired(FALSE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applies($entity, $action) {
+ return $entity === 'Event' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/GroupCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/GroupCreationSpecProvider.php
new file mode 100644
index 00000000..8af69a0a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/GroupCreationSpecProvider.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class GroupCreationSpecProvider implements SpecProviderInterface {
+ /**
+ * @inheritDoc
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('title')->setRequired(TRUE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applies($entity, $action) {
+ return $entity === 'Group' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/NavigationCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/NavigationCreationSpecProvider.php
new file mode 100644
index 00000000..7d5fc270
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/NavigationCreationSpecProvider.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class NavigationCreationSpecProvider implements SpecProviderInterface {
+ /**
+ * @inheritDoc
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('domain_id')->setRequired(FALSE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applies($entity, $action) {
+ return $entity === 'Navigation' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/NoteCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/NoteCreationSpecProvider.php
new file mode 100644
index 00000000..f12e592c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/NoteCreationSpecProvider.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+
+class NoteCreationSpecProvider implements SpecProviderInterface {
+
+ /**
+ * @param RequestSpec $spec
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('note')->setRequired(TRUE);
+ $spec->getFieldByName('entity_table')->setDefaultValue('civicrm_contact');
+ }
+
+ /**
+ * @param string $entity
+ * @param string $action
+ *
+ * @return bool
+ */
+ public function applies($entity, $action) {
+ return $entity === 'Note' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/OptionValueCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/OptionValueCreationSpecProvider.php
new file mode 100644
index 00000000..4ea634c1
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/OptionValueCreationSpecProvider.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class OptionValueCreationSpecProvider implements SpecProviderInterface {
+ /**
+ * @inheritDoc
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('weight')->setRequired(FALSE);
+ $spec->getFieldByName('value')->setRequired(FALSE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applies($entity, $action) {
+ return $entity === 'OptionValue' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/PhoneCreationSpecProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/PhoneCreationSpecProvider.php
new file mode 100644
index 00000000..bb757d43
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/PhoneCreationSpecProvider.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+class PhoneCreationSpecProvider implements SpecProviderInterface {
+ /**
+ * @inheritDoc
+ */
+ public function modifySpec(RequestSpec $spec) {
+ $spec->getFieldByName('contact_id')->setRequired(TRUE);
+ $spec->getFieldByName('location_type_id')->setRequired(TRUE);
+ $spec->getFieldByName('phone')->setRequired(TRUE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applies($entity, $action) {
+ return $entity === 'Phone' && $action === 'create';
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/SpecProviderInterface.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/SpecProviderInterface.php
new file mode 100644
index 00000000..8be77e68
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/Provider/SpecProviderInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Civi\Api4\Service\Spec\Provider;
+
+use Civi\Api4\Service\Spec\RequestSpec;
+
+interface SpecProviderInterface {
+ /**
+ * @param RequestSpec $spec
+ *
+ * @return void
+ */
+ public function modifySpec(RequestSpec $spec);
+
+ /**
+ * @param string $entity
+ * @param string $action
+ *
+ * @return bool
+ */
+ public function applies($entity, $action);
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/RequestSpec.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/RequestSpec.php
new file mode 100644
index 00000000..9437d930
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/RequestSpec.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Civi\Api4\Service\Spec;
+
+class RequestSpec {
+
+ /**
+ * @var string
+ */
+ protected $entity;
+
+ /**
+ * @var string
+ */
+ protected $action;
+
+ /**
+ * @var FieldSpec[]
+ */
+ protected $fields = [];
+
+ /**
+ * @param string $entity
+ * @param string $action
+ */
+ public function __construct($entity, $action) {
+ $this->entity = $entity;
+ $this->action = $action;
+ }
+
+ public function addFieldSpec(FieldSpec $field) {
+ $this->fields[] = $field;
+ }
+
+ /**
+ * @param $name
+ *
+ * @return FieldSpec|null
+ */
+ public function getFieldByName($name) {
+ foreach ($this->fields as $field) {
+ if ($field->getName() === $name) {
+ return $field;
+ }
+ }
+
+ return NULL;
+ }
+
+ /**
+ * @return array
+ * Gets all the field names currently part of the specification
+ */
+ public function getFieldNames() {
+ return array_map(function(FieldSpec $field) {
+ return $field->getName();
+ }, $this->fields);
+ }
+
+ /**
+ * @return array|FieldSpec[]
+ */
+ public function getRequiredFields() {
+ return array_filter($this->fields, function (FieldSpec $field) {
+ return $field->isRequired();
+ });
+ }
+
+ /**
+ * @return array|FieldSpec[]
+ */
+ public function getConditionalRequiredFields() {
+ return array_filter($this->fields, function (FieldSpec $field) {
+ return $field->getRequiredIf();
+ });
+ }
+
+ /**
+ * @param array $fieldNames
+ * Optional array of fields to return
+ * @return FieldSpec[]
+ */
+ public function getFields($fieldNames = NULL) {
+ if (!$fieldNames) {
+ return $this->fields;
+ }
+ $fields = [];
+ foreach ($this->fields as $field) {
+ if (in_array($field->getName(), $fieldNames)) {
+ $fields[] = $field;
+ }
+ }
+ return $fields;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEntity() {
+ return $this->entity;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAction() {
+ return $this->action;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/SpecFormatter.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/SpecFormatter.php
new file mode 100644
index 00000000..c8e4d3da
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/SpecFormatter.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Civi\Api4\Service\Spec;
+
+use CRM_Utils_Array as ArrayHelper;
+use CRM_Core_DAO_AllCoreTables as TableHelper;
+
+class SpecFormatter {
+ /**
+ * @param FieldSpec[] $fields
+ * @param array $return
+ * @param bool $includeFieldOptions
+ *
+ * @return array
+ */
+ public static function specToArray($fields, $return = [], $includeFieldOptions = FALSE) {
+ $fieldArray = [];
+
+ foreach ($fields as $field) {
+ if ($includeFieldOptions || in_array('options', $return)) {
+ $field->getOptions();
+ }
+ $fieldArray[$field->getName()] = $field->toArray($return);
+ }
+
+ return $fieldArray;
+ }
+
+ /**
+ * @param array $data
+ * @param string $entity
+ *
+ * @return FieldSpec
+ */
+ public static function arrayToField(array $data, $entity) {
+ $dataTypeName = self::getDataType($data);
+
+ if (!empty($data['custom_group_id'])) {
+ $field = new CustomFieldSpec($data['name'], $entity, $dataTypeName);
+ if (strpos($entity, 'Custom_') !== 0) {
+ $field->setName($data['custom_group']['name'] . '.' . $data['name']);
+ }
+ else {
+ $field->setCustomTableName($data['custom_group']['table_name']);
+ $field->setCustomFieldColumnName($data['column_name']);
+ }
+ $field->setCustomFieldId(ArrayHelper::value('id', $data));
+ $field->setCustomGroupName($data['custom_group']['name']);
+ $field->setTitle(ArrayHelper::value('label', $data));
+ $field->setOptions(self::customFieldHasOptions($data));
+ if (\CRM_Core_BAO_CustomField::isSerialized($data)) {
+ $field->setSerialize(\CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND);
+ }
+ }
+ else {
+ $name = ArrayHelper::value('name', $data);
+ $field = new FieldSpec($name, $entity, $dataTypeName);
+ $field->setRequired((bool) ArrayHelper::value('required', $data, FALSE));
+ $field->setTitle(ArrayHelper::value('title', $data));
+ $field->setOptions(!empty($data['pseudoconstant']));
+ $field->setSerialize(ArrayHelper::value('serialize', $data));
+ }
+
+ $field->setDefaultValue(ArrayHelper::value('default', $data));
+ $field->setDescription(ArrayHelper::value('description', $data));
+
+ $fkAPIName = ArrayHelper::value('FKApiName', $data);
+ $fkClassName = ArrayHelper::value('FKClassName', $data);
+ if ($fkAPIName || $fkClassName) {
+ $field->setFkEntity($fkAPIName ?: TableHelper::getBriefName($fkClassName));
+ }
+
+ return $field;
+ }
+
+ /**
+ * Does this custom field have options
+ *
+ * @param array $field
+ * @return bool
+ */
+ private static function customFieldHasOptions($field) {
+ // This will include boolean fields with Yes/No options.
+ if (in_array($field['html_type'], ['Radio', 'CheckBox'])) {
+ return TRUE;
+ }
+ // Do this before the "Select" string search because date fields have a "Select Date" html_type
+ // and contactRef fields have an "Autocomplete-Select" html_type - contacts are an FK not an option list.
+ if (in_array($field['data_type'], ['ContactReference', 'Date'])) {
+ return FALSE;
+ }
+ if (strpos($field['html_type'], 'Select')) {
+ return TRUE;
+ }
+ return !empty($field['option_group_id']);
+ }
+
+ /**
+ * Get the data type from an array. Defaults to 'data_type' with fallback to
+ * mapping for the integer value 'type'
+ *
+ * @param array $data
+ *
+ * @return string
+ */
+ private static function getDataType(array $data) {
+ if (isset($data['data_type'])) {
+ return $data['data_type'];
+ }
+
+ $dataTypeInt = ArrayHelper::value('type', $data);
+ $dataTypeName = \CRM_Utils_Type::typeToString($dataTypeInt);
+
+ return $dataTypeName;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/SpecGatherer.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/SpecGatherer.php
new file mode 100644
index 00000000..b1c83c89
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Service/Spec/SpecGatherer.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Civi\Api4\Service\Spec;
+
+use Civi\Api4\CustomField;
+use Civi\Api4\Service\Spec\Provider\SpecProviderInterface;
+use Civi\Api4\Utils\CoreUtil;
+
+class SpecGatherer {
+
+ /**
+ * @var SpecProviderInterface[]
+ */
+ protected $specProviders = [];
+
+ /**
+ * A cache of DAOs based on entity
+ *
+ * @var \CRM_Core_DAO[]
+ */
+ protected $DAONames;
+
+ /**
+ * Returns a RequestSpec with all the fields available. Uses spec providers
+ * to add or modify field specifications.
+ * For an example @see CustomFieldSpecProvider.
+ *
+ * @param string $entity
+ * @param string $action
+ * @param $includeCustom
+ *
+ * @return \Civi\Api4\Service\Spec\RequestSpec
+ */
+ public function getSpec($entity, $action, $includeCustom) {
+ $specification = new RequestSpec($entity, $action);
+
+ // Real entities
+ if (strpos($entity, 'Custom_') !== 0) {
+ $this->addDAOFields($entity, $action, $specification);
+ if ($includeCustom && array_key_exists($entity, \CRM_Core_SelectValues::customGroupExtends())) {
+ $this->addCustomFields($entity, $specification);
+ }
+ }
+ // Custom pseudo-entities
+ else {
+ $this->getCustomGroupFields(substr($entity, 7), $specification);
+ }
+
+ foreach ($this->specProviders as $provider) {
+ if ($provider->applies($entity, $action)) {
+ $provider->modifySpec($specification);
+ }
+ }
+
+ return $specification;
+ }
+
+ /**
+ * @param SpecProviderInterface $provider
+ */
+ public function addSpecProvider(SpecProviderInterface $provider) {
+ $this->specProviders[] = $provider;
+ }
+
+ /**
+ * @param string $entity
+ * @param RequestSpec $specification
+ */
+ private function addDAOFields($entity, $action, RequestSpec $specification) {
+ $DAOFields = $this->getDAOFields($entity);
+
+ foreach ($DAOFields as $DAOField) {
+ if ($DAOField['name'] == 'id' && $action == 'create') {
+ continue;
+ }
+ if ($action !== 'create' || isset($DAOField['default'])) {
+ $DAOField['required'] = FALSE;
+ }
+ if ($DAOField['name'] == 'is_active' && empty($DAOField['default'])) {
+ $DAOField['default'] = '1';
+ }
+ $field = SpecFormatter::arrayToField($DAOField, $entity);
+ $specification->addFieldSpec($field);
+ }
+ }
+
+ /**
+ * @param string $entity
+ * @param RequestSpec $specification
+ */
+ private function addCustomFields($entity, RequestSpec $specification) {
+ $extends = ($entity == 'Contact') ? ['Contact', 'Individual', 'Organization', 'Household'] : [$entity];
+ $customFields = CustomField::get()
+ ->addWhere('custom_group.extends', 'IN', $extends)
+ ->setSelect(['custom_group.name', 'custom_group_id', 'name', 'label', 'data_type', 'html_type', 'is_searchable', 'is_search_range', 'weight', 'is_active', 'is_view', 'option_group_id', 'default_value'])
+ ->execute();
+
+ foreach ($customFields as $fieldArray) {
+ $field = SpecFormatter::arrayToField($fieldArray, $entity);
+ $specification->addFieldSpec($field);
+ }
+ }
+
+ /**
+ * @param string $customGroup
+ * @param RequestSpec $specification
+ */
+ private function getCustomGroupFields($customGroup, RequestSpec $specification) {
+ $customFields = CustomField::get()
+ ->addWhere('custom_group.name', '=', $customGroup)
+ ->setSelect(['custom_group.name', 'custom_group_id', 'name', 'label', 'data_type', 'html_type', 'is_searchable', 'is_search_range', 'weight', 'is_active', 'is_view', 'option_group_id', 'default_value', 'custom_group.table_name', 'column_name'])
+ ->execute();
+
+ foreach ($customFields as $fieldArray) {
+ $field = SpecFormatter::arrayToField($fieldArray, 'Custom_' . $customGroup);
+ $specification->addFieldSpec($field);
+ }
+ }
+
+ /**
+ * @param string $entityName
+ *
+ * @return array
+ */
+ private function getDAOFields($entityName) {
+ $dao = CoreUtil::getDAOFromApiName($entityName);
+
+ return $dao::fields();
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/UFGroup.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/UFGroup.php
new file mode 100644
index 00000000..aeea02c1
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/UFGroup.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * UFGroup entity - AKA profiles.
+ *
+ * @package Civi\Api4
+ */
+class UFGroup extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/UFJoin.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/UFJoin.php
new file mode 100644
index 00000000..4d68fc7a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/UFJoin.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * UFJoin entity - links profiles to the components/extensions they are used for.
+ *
+ * @package Civi\Api4
+ */
+class UFJoin extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ActionUtil.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ActionUtil.php
new file mode 100644
index 00000000..628bc6fa
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ActionUtil.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Civi\Api4\Utils;
+
+class ActionUtil {
+
+ /**
+ * @param $entityName
+ * @param $actionName
+ * @return \Civi\Api4\Generic\AbstractAction
+ * @throws \Civi\API\Exception\NotImplementedException
+ */
+ public static function getAction($entityName, $actionName) {
+ // For custom pseudo-entities
+ if (strpos($entityName, 'Custom_') === 0) {
+ return \Civi\Api4\CustomValue::$actionName(substr($entityName, 7));
+ }
+ else {
+ $callable = ["\\Civi\\Api4\\$entityName", $actionName];
+ if (!is_callable($callable)) {
+ throw new \Civi\API\Exception\NotImplementedException("API ($entityName, $actionName) does not exist (join the API team and implement it!)");
+ }
+ return call_user_func($callable);
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ArrayInsertionUtil.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ArrayInsertionUtil.php
new file mode 100644
index 00000000..54e3944b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ArrayInsertionUtil.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Civi\Api4\Utils;
+
+use CRM_Utils_Array as UtilsArray;
+
+class ArrayInsertionUtil {
+ /**
+ * If the values to be inserted contain a key _parent_id they will only be
+ * inserted if the parent node ID matches their ID
+ *
+ * @param $array
+ * The array to insert the value in
+ * @param array $parts
+ * Path to insertion point with structure:
+ * [[ name => is_multiple ], ..]
+ * @param mixed $values
+ * The value to be inserted
+ */
+ public static function insert(&$array, $parts, $values) {
+ $key = key($parts);
+ $isMulti = array_shift($parts);
+ if (!isset($array[$key])) {
+ $array[$key] = $isMulti ? [] : NULL;
+ }
+ if (empty($parts)) {
+ $values = self::filterValues($array, $isMulti, $values);
+ $array[$key] = $values;
+ }
+ else {
+ if ($isMulti) {
+ foreach ($array[$key] as &$subArray) {
+ self::insert($subArray, $parts, $values);
+ }
+ }
+ else {
+ self::insert($array[$key], $parts, $values);
+ }
+ }
+ }
+
+ /**
+ * @param $parentArray
+ * @param $isMulti
+ * @param $values
+ *
+ * @return array|mixed
+ */
+ private static function filterValues($parentArray, $isMulti, $values) {
+ $parentID = UtilsArray::value('id', $parentArray);
+
+ if ($parentID) {
+ $values = array_filter($values, function ($value) use ($parentID) {
+ return UtilsArray::value('_parent_id', $value) == $parentID;
+ });
+ }
+
+ $unsets = ['_parent_id', '_base_id'];
+ array_walk($values, function (&$value) use ($unsets) {
+ foreach ($unsets as $unset) {
+ if (isset($value[$unset])) {
+ unset($value[$unset]);
+ }
+ }
+ });
+
+ if (!$isMulti) {
+ $values = array_shift($values);
+ }
+ return $values;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/CoreUtil.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/CoreUtil.php
new file mode 100644
index 00000000..b43e62ca
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/CoreUtil.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Civi\Api4\Utils;
+
+use Civi\Api4\CustomGroup;
+
+require_once 'api/v3/utils.php';
+
+class CoreUtil {
+
+ /**
+ * todo this class should not rely on api3 code
+ *
+ * @param $entityName
+ *
+ * @return \CRM_Core_DAO|string
+ * The DAO name for use in static calls. Return doc block is hacked to allow
+ * auto-completion of static methods
+ */
+ public static function getDAOFromApiName($entityName) {
+ if ($entityName === 'CustomValue' || strpos($entityName, 'Custom_') === 0) {
+ return 'CRM_Contact_BAO_Contact';
+ }
+ return \_civicrm_api3_get_DAO($entityName);
+ }
+
+ /**
+ * Get table name of given Custom group
+ *
+ * @param string $customGroupName
+ *
+ * @return string
+ */
+ public static function getCustomTableByName($customGroupName) {
+ return CustomGroup::get()
+ ->addSelect('table_name')
+ ->addWhere('name', '=', $customGroupName)
+ ->execute()
+ ->first()['table_name'];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/FormattingUtil.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/FormattingUtil.php
new file mode 100644
index 00000000..3a7cdae5
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/FormattingUtil.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Civi\Api4\Utils;
+
+use CRM_Utils_Array as UtilsArray;
+
+require_once 'api/v3/utils.php';
+
+class FormattingUtil {
+
+ /**
+ * Massage values into the format the BAO expects for a write operation
+ *
+ * @param $params
+ * @param $entity
+ * @param $fields
+ * @throws \API_Exception
+ */
+ public static function formatWriteParams(&$params, $entity, $fields) {
+ foreach ($fields as $name => $field) {
+ if (!empty($params[$name])) {
+ $value =& $params[$name];
+ // Hack for null values -- see comment below
+ if ($value === 'null') {
+ $value = 'Null';
+ }
+ FormattingUtil::formatValue($value, $field, $entity);
+ // Ensure we have an array for serialized fields
+ if (!empty($field['serialize'] && !is_array($value))) {
+ $value = (array) $value;
+ }
+ }
+ /*
+ * Because of the wacky way that database values are saved we need to format
+ * some of the values here. In this strange world the string 'null' is used to
+ * unset values. Hence if we encounter true null we change it to string 'null'.
+ *
+ * If we encounter the string 'null' then we assume the user actually wants to
+ * set the value to string null. However since the string null is reserved for
+ * unsetting values we must change it. Another quirk of the DB_DataObject is
+ * that it allows 'Null' to be set, but any other variation of string 'null'
+ * will be converted to true null, e.g. 'nuLL', 'NUlL' etc. so we change it to
+ * 'Null'.
+ */
+ elseif (array_key_exists($name, $params) && $params[$name] === NULL) {
+ $params[$name] = 'null';
+ }
+
+ if (strstr($entity, 'Custom_')) {
+ if ($name == 'entity_id') {
+ $params['entityID'] = $params['entity_id'];
+ unset($params['entity_id']);
+ }
+ elseif (!empty($field['custom_field_id'])) {
+ $params['custom_' . $field['custom_field_id']] = $params[$name];
+ unset($params[$name]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Transform raw api input to appropriate format for use in a SQL query.
+ *
+ * This is used by read AND write actions (Get, Create, Update, Replace)
+ *
+ * @param $value
+ * @param $fieldSpec
+ * @throws \API_Exception
+ */
+ public static function formatValue(&$value, $fieldSpec, $entity) {
+ if (is_array($value)) {
+ foreach ($value as &$val) {
+ self::formatValue($val, $fieldSpec, $entity);
+ }
+ return;
+ }
+ $fk = UtilsArray::value('fk_entity', $fieldSpec);
+ if ($fieldSpec['name'] == 'id') {
+ $fk = $entity;
+ }
+ $dataType = UtilsArray::value('data_type', $fieldSpec);
+
+ if ($fk === 'Domain' && $value === 'current_domain') {
+ $value = \CRM_Core_Config::domainID();
+ }
+
+ if ($fk === 'Contact' && !is_numeric($value)) {
+ $value = \_civicrm_api3_resolve_contactID($value);
+ if ('unknown-user' === $value) {
+ throw new \API_Exception("\"{$fieldSpec['name']}\" \"{$value}\" cannot be resolved to a contact ID", 2002, ['error_field' => $fieldSpec['name'], "type" => "integer"]);
+ }
+ }
+
+ switch ($dataType) {
+ case 'Timestamp':
+ $value = date('Y-m-d H:i:s', strtotime($value));
+ break;
+
+ case 'Date':
+ $value = date('Ymd', strtotime($value));
+ break;
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ReflectionUtils.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ReflectionUtils.php
new file mode 100644
index 00000000..76662647
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Utils/ReflectionUtils.php
@@ -0,0 +1,119 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2015 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Utils;
+
+/**
+ * Just another place to put static functions...
+ */
+class ReflectionUtils {
+ /**
+ * @param \Reflector|\ReflectionClass $reflection
+ * @param string $type
+ * If we are not reflecting the class itself, specify "Method", "Property", etc.
+ *
+ * @return array
+ */
+ public static function getCodeDocs($reflection, $type = NULL) {
+ $docs = self::parseDocBlock($reflection->getDocComment());
+
+ // Recurse into parent functions
+ if (isset($docs['inheritDoc']) || isset($docs['inheritdoc'])) {
+ unset($docs['inheritDoc'], $docs['inheritdoc']);
+ $newReflection = NULL;
+ try {
+ if ($type) {
+ $name = $reflection->getName();
+ $reflectionClass = $reflection->getDeclaringClass()->getParentClass();
+ if ($reflectionClass) {
+ $getItem = "get$type";
+ $newReflection = $reflectionClass->$getItem($name);
+ }
+ }
+ else {
+ $newReflection = $reflection->getParentClass();
+ }
+ }
+ catch (\ReflectionException $e) {}
+ if ($newReflection) {
+ // Mix in
+ $additionalDocs = self::getCodeDocs($newReflection, $type);
+ if (!empty($docs['comment']) && !empty($additionalDocs['comment'])) {
+ $docs['comment'] .= "\n\n" . $additionalDocs['comment'];
+ }
+ $docs += $additionalDocs;
+ }
+ }
+ return $docs;
+ }
+
+ /**
+ * @param string $comment
+ * @return array
+ */
+ public static function parseDocBlock($comment) {
+ $info = [];
+ foreach (preg_split("/((\r?\n)|(\r\n?))/", $comment) as $num => $line) {
+ if (!$num || strpos($line, '*/') !== FALSE) {
+ continue;
+ }
+ $line = ltrim(trim($line), '* ');
+ if (strpos($line, '@') === 0) {
+ $words = explode(' ', $line);
+ $key = substr($words[0], 1);
+ if ($key == 'var') {
+ $info['type'] = explode('|', $words[1]);
+ }
+ elseif ($key == 'options') {
+ $val = str_replace(', ', ',', implode(' ', array_slice($words, 1)));
+ $info['options'] = explode(',', $val);
+ }
+ else {
+ // Unrecognized annotation, but we'll duly add it to the info array
+ $val = implode(' ', array_slice($words, 1));
+ $info[$key] = strlen($val) ? $val : TRUE;
+ }
+ }
+ elseif ($num == 1) {
+ $info['description'] = $line;
+ }
+ elseif (!$line) {
+ if (isset($info['comment'])) {
+ $info['comment'] .= "\n";
+ }
+ }
+ else {
+ $info['comment'] = isset($info['comment']) ? "{$info['comment']}\n$line" : $line;
+ }
+ }
+ if (isset($info['comment'])) {
+ $info['comment'] = trim($info['comment']);
+ }
+ return $info;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Website.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Website.php
new file mode 100644
index 00000000..fb890a0f
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Website.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * Website entity.
+ *
+ * @package Civi\Api4
+ */
+class Website extends Generic\DAOEntity {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/LICENSE.txt b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/LICENSE.txt
new file mode 100644
index 00000000..6a5653f4
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/LICENSE.txt
@@ -0,0 +1,667 @@
+Package: org.civicrm.api4
+Copyright (C) 2016, Coleman Watts <coleman@civicrm.org>
+Licensed under the GNU Affero Public License 3.0 (below).
+
+-------------------------------------------------------------------------------
+
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4.ang.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4.ang.php
new file mode 100644
index 00000000..f7d69b39
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4.ang.php
@@ -0,0 +1,12 @@
+<?php
+// Autoloader data for Api4 angular module.
+return [
+ 'js' => [
+ 'ang/api4.js',
+ 'ang/api4/*.js',
+ 'ang/api4/*/*.js',
+ ],
+ 'css' => [],
+ 'partials' => [],
+ 'requires' => [],
+];
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4.js
new file mode 100644
index 00000000..d1116fc4
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4.js
@@ -0,0 +1,4 @@
+(function(angular, $, _) {
+ // Declare a list of dependencies.
+ angular.module('api4', CRM.angRequires('api4'));
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4/crmApi4.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4/crmApi4.js
new file mode 100644
index 00000000..743b3591
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4/crmApi4.js
@@ -0,0 +1,37 @@
+(function(angular, $, _) {
+
+ angular.module('api4').factory('crmApi4', function($q) {
+ var crmApi4 = function(entity, action, params, index) {
+ // JSON serialization in CRM.api4 is not aware of Angular metadata like $$hash, so use angular.toJson()
+ var deferred = $q.defer();
+ var p;
+ var backend = crmApi4.backend || CRM.api4;
+ if (_.isObject(entity)) {
+ // eval content is locally generated.
+ /*jshint -W061 */
+ p = backend(eval('('+angular.toJson(entity)+')'), action);
+ } else {
+ // eval content is locally generated.
+ /*jshint -W061 */
+ p = backend(entity, action, eval('('+angular.toJson(params)+')'), index);
+ }
+ p.then(
+ function(result) {
+ deferred.resolve(result);
+ },
+ function(error) {
+ deferred.reject(error);
+ }
+ );
+ return deferred.promise;
+ };
+ crmApi4.backend = null;
+ crmApi4.val = function(value) {
+ var d = $.Deferred();
+ d.resolve(value);
+ return d.promise();
+ };
+ return crmApi4;
+ });
+
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer.ang.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer.ang.php
new file mode 100644
index 00000000..6583f277
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer.ang.php
@@ -0,0 +1,18 @@
+<?php
+// Autoloader data for Api4 explorer.
+return [
+ 'js' => [
+ 'ang/api4Explorer.js',
+ 'ang/api4Explorer/*.js',
+ 'ang/api4Explorer/*/*.js',
+ 'lib/*.js',
+ ],
+ 'css' => [
+ 'css/explorer.css',
+ ],
+ 'partials' => [
+ 'ang/api4Explorer',
+ ],
+ 'basePages' => [],
+ 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'crmRouteBinder', 'ui.sortable', 'api4'],
+];
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer.js
new file mode 100644
index 00000000..85e10c46
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer.js
@@ -0,0 +1,4 @@
+(function(angular, $, _) {
+ // Declare a list of dependencies.
+ angular.module('api4Explorer', CRM.angRequires('api4Explorer'));
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Chain.html b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Chain.html
new file mode 100644
index 00000000..257efdec
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Chain.html
@@ -0,0 +1,4 @@
+<input class="form-control" ng-model="chain[1][0]" crm-ui-select="{data: entities, allowClear: true, placeholder: 'None'}" />
+<select class="form-control api4-chain-action" ng-model="chain[1][1]" ng-options="a for a in actions" ></select>
+<input class="form-control api4-chain-params" ng-model="chain[1][2]" placeholder="{{ ts('Params') }}" />
+<input class="form-control api4-chain-index" ng-model="chain[1][3]" placeholder="{{ ts('Index') }}" />
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Explorer.html b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Explorer.html
new file mode 100644
index 00000000..6cea301f
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Explorer.html
@@ -0,0 +1,137 @@
+<div id="bootstrap-theme" class="api4-explorer-page">
+ <div crm-ui-debug="availableParams"></div>
+
+ <h1 crm-page-title>
+ {{ ts('CiviCRM API v4') }}{{ entity ? (' (' + entity + '::' + action + ')') : '' }}
+ </h1>
+
+ <!--This warning will show if bootstrap is unavailable. Normally it will be hidden by the bootstrap .collapse class.-->
+ <div class="messages warning no-popup collapse">
+ <p>
+ <i class="crm-i fa-exclamation-triangle"></i>
+ <strong>{{ ts('Bootstrap theme not found.') }}</strong>
+ </p>
+ <p>{{ ts('This screen may not work correctly without a bootstrap-based theme such as Shoreditch installed.') }}</p>
+ </div>
+
+ <div class="api4-explorer-row">
+ <form name="api4-explorer" class="panel panel-default explorer-params-panel">
+ <div class="panel-heading">
+ <div class="form-inline">
+ <input class="collapsible-optgroups form-control" ng-model="entity" ng-disabled="!entities.length" ng-class="{loading: !entities.length}" crm-ui-select="{placeholder: ts('Entity'), data: entities}" />
+ <input class="collapsible-optgroups form-control" ng-model="action" ng-disabled="!entity || !actions.length" ng-class="{loading: entity && !actions.length}" crm-ui-select="{placeholder: ts('Action'), data: actions}" />
+ <input class="form-control api4-index" ng-model="index" ng-mouseenter="help('index', indexHelp)" ng-mouseleave="help()" placeholder="{{ ts('Index') }}" />
+ <button class="btn btn-success pull-right" crm-icon="fa-bolt" ng-disabled="!entity || !action || loading" ng-click="execute()">{{ ts('Execute') }}</button>
+ </div>
+ </div>
+ <div class="panel-body">
+ <div class="api4-input form-inline">
+ <div class="form-control" ng-mouseenter="help(name, param)" ng-mouseleave="help()" ng-class="{'api4-option-selected': params[name]}" ng-repeat="(name, param) in availableParams" ng-if="!isSpecial(name) && param.type[0] === 'bool'">
+ <input type="checkbox" id="api4-param-{{ name }}" ng-model="params[name]"/>
+ <label for="api4-param-{{ name }}">{{ name }}<span class="crm-marker" ng-if="param.required"> *</span></label>
+ </div>
+ </div>
+ <div class="api4-input form-inline" ng-mouseenter="help('select', availableParams.select)" ng-mouseleave="help()" ng-if="availableParams.select">
+ <label for="api4-param-select">select<span class="crm-marker" ng-if="availableParams.select.required"> *</span></label>
+ <input class="collapsible-optgroups form-control" ng-list crm-ui-select="{data: fieldsAndJoins, multiple: true}" id="api4-param-select" ng-model="params.select" style="width: 85%;"/>
+ </div>
+ <div class="api4-input form-inline" ng-mouseenter="help('fields', availableParams.fields)" ng-mouseleave="help()"ng-if="availableParams.fields">
+ <label for="api4-param-fields">fields<span class="crm-marker" ng-if="availableParams.fields.required"> *</span></label>
+ <input class="form-control" ng-list crm-ui-select="{data: fields, multiple: true}" id="api4-param-fields" ng-model="params.fields" style="width: 85%;"/>
+ </div>
+ <div class="api4-input form-inline" ng-mouseenter="help('action', availableParams.action)" ng-mouseleave="help()"ng-if="availableParams.action">
+ <label for="api4-param-action">action<span class="crm-marker" ng-if="availableParams.action.required"> *</span></label>
+ <input class="form-control" crm-ui-select="{data: actions, allowClear: true, placeholder: 'None'}" id="api4-param-action" ng-model="params.action"/>
+ </div>
+ <div class="api4-input form-inline" ng-mouseenter="help(name, param)" ng-mouseleave="help()" ng-repeat="(name, param) in availableParams" ng-if="!isSpecial(name) && (param.type[0] === 'string' || param.type[0] === 'int')">
+ <label for="api4-param-{{ name }}">{{ name }}<span class="crm-marker" ng-if="param.required"> *</span></label>
+ <input class="form-control" type="{{ param.type[0] === 'int' && param.type.length === 1 ? 'number' : 'text' }}" id="api4-param-{{ name }}" ng-model="params[name]"/>
+ <a href class="crm-hover-button" title="Clear" ng-click="clearParam(name)" ng-show="!!params[name]"><i class="crm-i fa-times"></i></a>
+ </div>
+ <div class="api4-input" ng-mouseenter="help(name, param)" ng-mouseleave="help()" ng-repeat="(name, param) in availableParams" ng-if="!isSpecial(name) && param.type[0] === 'array'">
+ <label for="api4-param-{{ name }}">{{ name }}<span class="crm-marker" ng-if="param.required"> *</span></label>
+ <textarea class="form-control" type="{{ param.type[0] === 'int' && param.type.length === 1 ? 'number' : 'text' }}" id="api4-param-{{ name }}" ng-model="params[name]">
+ </textarea>
+ </div>
+ <fieldset ng-if="availableParams.where" class="api4-where-fieldset" ng-mouseenter="help('where', availableParams.where)" ng-mouseleave="help()" crm-api4-where-clause="{where: params.where, required: availableParams.where.required, op: 'AND', label: 'where', fields: fieldsAndJoins}">
+ </fieldset>
+ <fieldset ng-if="availableParams.values" ng-mouseenter="help('values', availableParams.values)" ng-mouseleave="help()">
+ <legend>values<span class="crm-marker" ng-if="availableParams.values.required"> *</span></legend>
+ <div class="api4-input form-inline" ng-repeat="clause in params.values">
+ <input class="collapsible-optgroups form-control" ng-model="clause[0]" crm-ui-select="{formatResult: formatSelect2Item, formatSelection: formatSelect2Item, data: valuesFields, allowClear: true, placeholder: 'Field'}" />
+ <input class="form-control" ng-model="clause[1]" api4-exp-value="{field: clause[0]}" />
+ </div>
+ <div class="api4-input form-inline">
+ <input class="collapsible-optgroups form-control" ng-model="controls.values" crm-ui-select="{formatResult: formatSelect2Item, formatSelection: formatSelect2Item, data: valuesFields}" placeholder="Add value" />
+ </div>
+ </fieldset>
+ <fieldset ng-if="availableParams.orderBy" ng-mouseenter="help('orderBy', availableParams.orderBy)" ng-mouseleave="help()">
+ <legend>orderBy<span class="crm-marker" ng-if="availableParams.orderBy.required"> *</span></legend>
+ <div class="api4-input form-inline" ng-repeat="clause in params.orderBy">
+ <input class="collapsible-optgroups form-control" ng-model="clause[0]" crm-ui-select="{data: fieldsAndJoins, allowClear: true, placeholder: 'Field'}" />
+ <select class="form-control" ng-model="clause[1]">
+ <option value="ASC">ASC</option>
+ <option value="DESC">DESC</option>
+ </select>
+ </div>
+ <div class="api4-input form-inline">
+ <input class="collapsible-optgroups form-control" ng-model="controls.orderBy" crm-ui-select="{data: fieldsAndJoins}" placeholder="Add orderBy" />
+ </div>
+ </fieldset>
+ <fieldset ng-if="availableParams.chain" ng-mouseenter="help('chain', availableParams.chain)" ng-mouseleave="help()">
+ <legend>chain</legend>
+ <div class="api4-input form-inline" ng-repeat="clause in params.chain" api4-exp-chain="clause" entities="entities" main-entity="entity" >
+ </div>
+ <div class="api4-input form-inline">
+ <input class="form-control" ng-model="controls.chain" crm-ui-select="{data: entities}" placeholder="Add chain" />
+ </div>
+ </fieldset>
+ </div>
+ </form>
+ <div class="panel panel-info explorer-help-panel">
+ <div class="panel-heading">
+ <h3 class="panel-title" crm-icon="fa-info-circle">{{ helpTitle }}</h3>
+ </div>
+ <div class="panel-body">
+ <h4>{{ helpContent.description }}</h4>
+ <div ng-if="helpContent.comment">
+ <p ng-repeat='text in helpContent.comment.split("\n\n")'>{{ text }}</p>
+ </div>
+ <p ng-repeat="(key, item) in helpContent" ng-if="key !== 'description' && key !== 'comment'">
+ <strong>{{ key }}:</strong> {{ item }}
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="api4-explorer-row">
+ <div class="panel panel-warning explorer-code-panel">
+ <div class="panel-heading">
+ <h3 class="panel-title" crm-icon="fa-code">{{ ts('Code') }}</h3>
+ </div>
+ <div class="panel-body">
+ <table>
+ <tr ng-repeat="(type, item) in code">
+ <td>{{ type }}</td>
+ <td><pre>{{ item }}</pre></td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ <div class="panel explorer-result-panel panel-{{ status }}" >
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ <i class="fa fa-circle-o" ng-if="status === 'default'"></i>
+ <i class="fa fa-check-circle" ng-if="status === 'success'"></i>
+ <i class="fa fa-minus-circle" ng-if="status === 'danger'"></i>
+ <i class="fa fa-spinner fa-pulse" ng-if="status === 'warning'"></i>
+ {{ ts('Result') }}
+ </h3>
+ </div>
+ <div class="panel-body">
+ <pre ng-repeat="code in result">{{ code }}</pre>
+ </div>
+ </div>
+ </div>
+
+
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Explorer.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Explorer.js
new file mode 100644
index 00000000..10391793
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/Explorer.js
@@ -0,0 +1,789 @@
+(function(angular, $, _, undefined) {
+
+ // Cache schema metadata
+ var schema = [];
+ // Cache fk schema data
+ var links = [];
+ // Cache list of entities
+ var entities = [];
+ // Cache list of actions
+ var actions = [];
+ // Field options
+ var fieldOptions = {};
+
+
+ angular.module('api4Explorer').config(function($routeProvider) {
+ $routeProvider.when('/explorer/:api4entity?/:api4action?', {
+ controller: 'Api4Explorer',
+ templateUrl: '~/api4Explorer/Explorer.html',
+ reloadOnSearch: false
+ });
+ });
+
+ angular.module('api4Explorer').controller('Api4Explorer', function($scope, $routeParams, $location, $timeout, crmUiHelp, crmApi4) {
+ var ts = $scope.ts = CRM.ts('api4');
+ $scope.entities = entities;
+ $scope.actions = actions;
+ $scope.fields = [];
+ $scope.fieldsAndJoins = [];
+ $scope.availableParams = {};
+ $scope.params = {};
+ $scope.index = '';
+ var getMetaParams = schema.length ? {} : {
+ schema: ['Entity', 'get', {chain: {fields: ['$name', 'getFields']}}],
+ links: ['Entity', 'getLinks']
+ },
+ objectParams = {orderBy: 'ASC', values: '', chain: ['Entity', '', '{}']},
+ helpTitle = '',
+ helpContent = {};
+ $scope.helpTitle = '';
+ $scope.helpContent = {};
+ $scope.entity = $routeParams.api4entity;
+ $scope.result = [];
+ $scope.status = 'default';
+ $scope.loading = false;
+ $scope.controls = {};
+ $scope.code = {
+ php: '',
+ javascript: '',
+ cli: ''
+ };
+
+ $scope.$bindToRoute({
+ expr: 'index',
+ param: 'index',
+ default: ''
+ });
+
+ function ucfirst(str) {
+ return str[0].toUpperCase() + str.slice(1);
+ }
+
+ function lcfirst(str) {
+ return str[0].toLowerCase() + str.slice(1);
+ }
+
+ function pluralize(str) {
+ switch (str[str.length-1]) {
+ case 's':
+ return str + 'es';
+ case 'y':
+ return str.slice(0, -1) + 'ies';
+ default:
+ return str + 's';
+ }
+ }
+
+ // Turn a flat array into a select2 array
+ function arrayToSelect2(array) {
+ var out = [];
+ _.each(array, function(item) {
+ out.push({id: item, text: item});
+ });
+ return out;
+ }
+
+ // Reformat an existing array of objects for compatibility with select2
+ function formatForSelect2(input, container, key, extra, prefix) {
+ _.each(input, function(item) {
+ var id = (prefix || '') + item[key];
+ var formatted = {id: id, text: id};
+ if (extra) {
+ _.merge(formatted, _.pick(item, extra));
+ }
+ container.push(formatted);
+ });
+ return container;
+ }
+
+ function getFieldList(source) {
+ var fields = [],
+ fieldInfo = _.findWhere(getEntity().actions, {name: $scope.action}).fields;
+ formatForSelect2(fieldInfo, fields, 'name', ['description', 'required', 'default_value']);
+ return fields;
+ }
+
+ function addJoins(fieldList) {
+ var fields = _.cloneDeep(fieldList),
+ fks = _.findWhere(links, {entity: $scope.entity}) || {};
+ _.each(fks.links, function(link) {
+ var linkFields = entityFields(link.entity);
+ if (linkFields) {
+ fields.push({
+ text: link.alias,
+ description: 'Join to ' + link.entity,
+ children: formatForSelect2(linkFields, [], 'name', ['description'], link.alias + '.')
+ });
+ }
+ });
+ return fields;
+ }
+
+ $scope.help = function(title, param) {
+ if (!param) {
+ $scope.helpTitle = helpTitle;
+ $scope.helpContent = helpContent;
+ } else {
+ $scope.helpTitle = title;
+ $scope.helpContent = param;
+ }
+ };
+
+ $scope.valuesFields = function() {
+ var fields = _.cloneDeep($scope.fields);
+ // Disable fields that are already in use
+ _.each($scope.params.values || [], function(val) {
+ (_.findWhere(fields, {id: val[0]}) || {}).disabled = true;
+ });
+ return {results: fields};
+ };
+
+ $scope.formatSelect2Item = function(row) {
+ return _.escape(row.text) +
+ (row.required ? '<span class="crm-marker"> *</span>' : '') +
+ (row.description ? '<div class="crm-select2-row-description"><p>' + _.escape(row.description) + '</p></div>' : '');
+ };
+
+ $scope.clearParam = function(name) {
+ $scope.params[name] = $scope.availableParams[name].default;
+ };
+
+ $scope.isSpecial = function(name) {
+ var specialParams = ['select', 'fields', 'action', 'where', 'values', 'orderBy', 'chain'];
+ return _.contains(specialParams, name);
+ };
+
+ function getEntity(entityName) {
+ return _.findWhere(schema, {name: entityName || $scope.entity});
+ }
+
+ // Get all params that have been set
+ function getParams() {
+ var params = {};
+ _.each($scope.params, function(param, key) {
+ if (param != $scope.availableParams[key].default && !(typeof param === 'object' && _.isEmpty(param))) {
+ if (_.contains($scope.availableParams[key].type, 'array') && (typeof objectParams[key] === 'undefined')) {
+ params[key] = parseYaml(_.cloneDeep(param));
+ } else {
+ params[key] = param;
+ }
+ }
+ });
+ _.each(objectParams, function(defaultVal, key) {
+ if (params[key]) {
+ var newParam = {};
+ _.each(params[key], function(item) {
+ newParam[item[0]] = parseYaml(_.cloneDeep(item[1]));
+ });
+ params[key] = newParam;
+ }
+ });
+ if (params.where) {
+ formatWhereClause(params.where);
+ }
+ return params;
+ }
+
+ // Coerce value to an array when the operator is IN or NOT IN
+ // Note this has already been passed through parseYaml once
+ function formatWhereClause(where) {
+ _.each(where, function(clause) {
+ if (_.isArray(clause)) {
+ if (clause.length === 3) {
+ if (_.contains(['IN', 'NOT IN'], clause[1]) && (_.isNumber(clause[2]) || (_.isString(clause[2]) && clause[2].length))) {
+ clause[2] = parseYaml('[' + clause[2] + ']');
+ }
+ } else {
+ formatWhereClause(clause);
+ }
+ }
+ });
+ }
+
+ function parseYaml(input) {
+ if (typeof input === 'undefined') {
+ return undefined;
+ }
+ if (_.isObject(input) || _.isArray(input)) {
+ _.each(input, function(item, index) {
+ input[index] = parseYaml(item);
+ });
+ return input;
+ }
+ try {
+ return input === '>' ? '>' : jsyaml.safeLoad(input);
+ } catch (e) {
+ return input;
+ }
+ }
+
+ function selectAction() {
+ $scope.action = $routeParams.api4action;
+ $scope.fieldsAndJoins = [];
+ formatForSelect2(getEntity().actions, actions, 'name', ['description', 'params']);
+ if ($scope.action) {
+ var actionInfo = _.findWhere(actions, {id: $scope.action});
+ $scope.fields = getFieldList();
+ if (_.contains(['get', 'update', 'delete', 'replace'], $scope.action)) {
+ $scope.fieldsAndJoins = addJoins($scope.fields);
+ } else {
+ $scope.fieldsAndJoins = $scope.fields;
+ }
+ _.each(actionInfo.params, function (param, name) {
+ var format,
+ defaultVal = _.cloneDeep(param.default);
+ if (param.type) {
+ switch (param.type[0]) {
+ case 'int':
+ case 'bool':
+ format = param.type[0];
+ break;
+
+ case 'array':
+ case 'object':
+ format = 'json';
+ break;
+
+ default:
+ format = 'raw';
+ }
+ if (name == 'limit') {
+ defaultVal = 25;
+ }
+ if (name === 'values') {
+ defaultVal = defaultValues(defaultVal);
+ }
+ $scope.$bindToRoute({
+ expr: 'params["' + name + '"]',
+ param: name,
+ format: format,
+ default: defaultVal,
+ deep: format === 'json'
+ });
+ }
+ if (typeof objectParams[name] !== 'undefined') {
+ $scope.$watch('params.' + name, function(values) {
+ // Remove empty values
+ _.each(values, function(clause, index) {
+ if (!clause || !clause[0]) {
+ $scope.params[name].splice(index, 1);
+ }
+ });
+ }, true);
+ $scope.$watch('controls.' + name, function(value) {
+ var field = value;
+ $timeout(function() {
+ if (field) {
+ var defaultOp = _.cloneDeep(objectParams[name]);
+ if (name === 'chain') {
+ var num = $scope.params.chain.length;
+ defaultOp[0] = field;
+ field = 'name_me_' + num;
+ }
+ $scope.params[name].push([field, defaultOp]);
+ $scope.controls[name] = null;
+ }
+ });
+ });
+ }
+ });
+ $scope.availableParams = actionInfo.params;
+ }
+ writeCode();
+ }
+
+ function defaultValues(defaultVal) {
+ _.each($scope.fields, function(field) {
+ if (field.required) {
+ defaultVal.push([field.id, '']);
+ }
+ });
+ return defaultVal;
+ }
+
+ function stringify(value, trim) {
+ if (typeof value === 'undefined') {
+ return '';
+ }
+ var str = JSON.stringify(value).replace(/,/g, ', ');
+ if (trim) {
+ str = str.slice(1, -1);
+ }
+ return str.trim();
+ }
+
+ function writeCode() {
+ var code = {
+ php: ts('Select an entity and action'),
+ javascript: '',
+ cli: ''
+ },
+ entity = $scope.entity,
+ action = $scope.action,
+ params = getParams(),
+ index = isInt($scope.index) ? +$scope.index : $scope.index,
+ result = 'result';
+ if ($scope.entity && $scope.action) {
+ if (action.slice(0, 3) === 'get') {
+ result = entity.substr(0, 7) === 'Custom_' ? _.camelCase(entity.substr(7)) : entity;
+ result = lcfirst(action.replace(/s$/, '').slice(3) || result);
+ }
+ var results = lcfirst(_.isNumber(index) ? result : pluralize(result)),
+ paramCount = _.size(params),
+ i = 0;
+
+ // Write javascript
+ code.javascript = "CRM.api4('" + entity + "', '" + action + "', {";
+ _.each(params, function(param, key) {
+ code.javascript += "\n " + key + ': ' + stringify(param) +
+ (++i < paramCount ? ',' : '');
+ if (key === 'checkPermissions') {
+ code.javascript += ' // IGNORED: permissions are always enforced from client-side requests';
+ }
+ });
+ code.javascript += "\n}";
+ if (index || index === 0) {
+ code.javascript += ', ' + JSON.stringify(index);
+ }
+ code.javascript += ").then(function(" + results + ") {\n // do something with " + results + " array\n}, function(failure) {\n // handle failure\n});";
+
+ // Write php code
+ if (entity.substr(0, 7) !== 'Custom_') {
+ code.php = '$' + results + " = \\Civi\\Api4\\" + entity + '::' + action + '()';
+ } else {
+ code.php = '$' + results + " = \\Civi\\Api4\\CustomValue::" + action + "('" + entity.substr(7) + "')";
+ }
+ _.each(params, function(param, key) {
+ var val = '';
+ if (typeof objectParams[key] !== 'undefined' && key !== 'chain') {
+ _.each(param, function(item, index) {
+ val = phpFormat(index) + ', ' + phpFormat(item, 4);
+ code.php += "\n ->add" + ucfirst(key).replace(/s$/, '') + '(' + val + ')';
+ });
+ } else if (key === 'where') {
+ _.each(param, function (clause) {
+ if (clause[0] === 'AND' || clause[0] === 'OR' || clause[0] === 'NOT') {
+ code.php += "\n ->addClause(" + phpFormat(clause[0]) + ", " + phpFormat(clause[1]).slice(1, -1) + ')';
+ } else {
+ code.php += "\n ->addWhere(" + phpFormat(clause).slice(1, -1) + ")";
+ }
+ });
+ } else {
+ code.php += "\n ->set" + ucfirst(key) + '(' + phpFormat(param, 4) + ')';
+ }
+ });
+ code.php += "\n ->execute()";
+ if (_.isNumber(index)) {
+ code.php += !index ? '\n ->first()' : (index === -1 ? '\n ->last()' : '\n ->itemAt(' + index + ')');
+ } else if (index) {
+ code.php += "\n ->indexBy('" + index + "')";
+ }
+ code.php += ";\n";
+ if (!_.isNumber(index)) {
+ code.php += "foreach ($" + results + ' as $' + ((_.isString(index) && index) ? index + ' => $' : '') + result + ') {\n // do something\n}';
+ }
+
+ // Write cli code
+ code.cli = 'cv api4 ' + entity + '.' + action + " '" + stringify(params) + "'";
+ }
+ $scope.code = code;
+ }
+
+ function isInt(value) {
+ if (_.isNumber(value)) {
+ return true;
+ }
+ if (!_.isString(value)) {
+ return false;
+ }
+ return /^-{0,1}\d+$/.test(value);
+ }
+
+ $scope.execute = function() {
+ $scope.status = 'warning';
+ $scope.loading = true;
+ crmApi4($scope.entity, $scope.action, getParams(), $scope.index)
+ .then(function(data) {
+ var meta = {length: _.size(data)},
+ result = JSON.stringify(data, null, 2);
+ if (_.isArray(data)) {
+ data.length = 0;
+ _.assign(meta, data);
+ }
+ $scope.loading = false;
+ $scope.status = 'success';
+ $scope.result = [JSON.stringify(meta).replace('{', '').replace(/}$/, ''), result];
+ }, function(data) {
+ $scope.loading = false;
+ $scope.status = 'danger';
+ $scope.result = [JSON.stringify(data, null, 2)];
+ });
+ };
+
+ /**
+ * Format value to look like php code
+ */
+ function phpFormat(val, indent) {
+ if (typeof val === 'undefined') {
+ return '';
+ }
+ indent = (typeof indent === 'number') ? _.repeat(' ', indent) : (indent || '');
+ var ret = '',
+ baseLine = indent ? indent.slice(0, -2) : '',
+ newLine = indent ? '\n' : '';
+ if ($.isPlainObject(val)) {
+ $.each(val, function(k, v) {
+ ret += (ret ? ', ' : '') + newLine + indent + "'" + k + "' => " + phpFormat(v);
+ });
+ return '[' + ret + newLine + baseLine + ']';
+ }
+ if ($.isArray(val)) {
+ $.each(val, function(k, v) {
+ ret += (ret ? ', ' : '') + newLine + indent + phpFormat(v);
+ });
+ return '[' + ret + newLine + baseLine + ']';
+ }
+ if (_.isString(val) && !_.contains(val, "'")) {
+ return "'" + val + "'";
+ }
+ return JSON.stringify(val).replace(/\$/g, '\\$');
+ }
+
+ function fetchMeta() {
+ crmApi4(getMetaParams)
+ .then(function(data) {
+ if (data.schema) {
+ schema = data.schema;
+ entities.length = 0;
+ formatForSelect2(schema, entities, 'name', ['description']);
+ if ($scope.entity && !$scope.action) {
+ showEntityHelp($scope.entity);
+ }
+ }
+ if (data.links) {
+ links = data.links;
+ }
+ if (data.actions) {
+ getEntity().actions = data.actions;
+ selectAction();
+ }
+ });
+ }
+
+ // Help for an entity with no action selected
+ function showEntityHelp(entityName) {
+ var entityInfo = getEntity(entityName);
+ $scope.helpTitle = helpTitle = $scope.entity;
+ $scope.helpContent = helpContent = {
+ description: entityInfo.description,
+ comment: entityInfo.comment
+ };
+ }
+
+ if (!$scope.entity) {
+ $scope.helpTitle = helpTitle = ts('Help');
+ $scope.helpContent = helpContent = {description: ts('Welcome to the api explorer.'), comment: ts('Select an entity to begin.')};
+ if (getMetaParams.schema) {
+ fetchMeta();
+ }
+ } else if (!actions.length && (!schema.length || !getEntity().actions)) {
+ if (getMetaParams.schema) {
+ entities.push({id: $scope.entity, text: $scope.entity});
+ }
+ getMetaParams.actions = [$scope.entity, 'getActions', {chain: {fields: [$scope.entity, 'getFields', {action: '$name'}]}}];
+ fetchMeta();
+ } else {
+ selectAction();
+ }
+
+ if ($scope.entity && schema.length) {
+ showEntityHelp($scope.entity);
+ }
+
+ // Update route when changing entity
+ $scope.$watch('entity', function(newVal, oldVal) {
+ if (oldVal !== newVal) {
+ // Flush actions cache to re-fetch for new entity
+ actions = [];
+ $location.url('/explorer/' + newVal);
+ }
+ });
+
+ // Update route when changing actions
+ $scope.$watch('action', function(newVal, oldVal) {
+ if ($scope.entity && $routeParams.api4action !== newVal && !_.isUndefined(newVal)) {
+ $location.url('/explorer/' + $scope.entity + '/' + newVal);
+ } else if (newVal) {
+ $scope.helpTitle = helpTitle = $scope.entity + '::' + newVal;
+ $scope.helpContent = helpContent = _.pick(_.findWhere(getEntity().actions, {name: newVal}), ['description', 'comment']);
+ }
+ });
+
+ $scope.indexHelp = {
+ description: ts('(string|int) Index results or select by index.'),
+ comment: ts('Pass a string to index the results by a field value. E.g. index: "name" will return an associative array with names as keys.') + '\n\n' +
+ ts('Pass an integer to return a single result; e.g. index: 0 will return the first result, 1 will return the second, and -1 will return the last.')
+ };
+
+ $scope.$watch('params', writeCode, true);
+ $scope.$watch('index', writeCode);
+ writeCode();
+
+ });
+
+ angular.module('api4Explorer').directive('crmApi4WhereClause', function($timeout) {
+ return {
+ scope: {
+ data: '=crmApi4WhereClause'
+ },
+ templateUrl: '~/api4Explorer/WhereClause.html',
+ link: function (scope, element, attrs) {
+ var ts = scope.ts = CRM.ts('api4');
+ scope.newClause = '';
+ scope.conjunctions = ['AND', 'OR', 'NOT'];
+ scope.operators = CRM.vars.api4.operators;
+
+ scope.addGroup = function(op) {
+ scope.data.where.push([op, []]);
+ };
+
+ scope.removeGroup = function() {
+ scope.data.groupParent.splice(scope.data.groupIndex, 1);
+ };
+
+ scope.onSort = function(event, ui) {
+ $('.api4-where-fieldset').toggleClass('api4-sorting', event.type === 'sortstart');
+ $('.api4-input.form-inline').css('margin-left', '');
+ };
+
+ // Indent clause while dragging between nested groups
+ scope.onSortOver = function(event, ui) {
+ var offset = 0;
+ if (ui.sender) {
+ offset = $(ui.placeholder).offset().left - $(ui.sender).offset().left;
+ }
+ $('.api4-input.form-inline.ui-sortable-helper').css('margin-left', '' + offset + 'px');
+ };
+
+ scope.$watch('newClause', function(value) {
+ var field = value;
+ $timeout(function() {
+ if (field) {
+ scope.data.where.push([field, '=', '']);
+ scope.newClause = null;
+ }
+ });
+ });
+ scope.$watch('data.where', function(values) {
+ // Remove empty values
+ _.each(values, function(clause, index) {
+ if (typeof clause !== 'undefined' && !clause[0]) {
+ values.splice(index, 1);
+ }
+ });
+ }, true);
+ }
+ };
+ });
+
+ angular.module('api4Explorer').directive('api4ExpValue', function($routeParams, crmApi4) {
+ return {
+ scope: {
+ data: '=api4ExpValue'
+ },
+ link: function (scope, element, attrs) {
+ var ts = scope.ts = CRM.ts('api4'),
+ entity = $routeParams.api4entity;
+
+ function getField(fieldName) {
+ var fieldNames = fieldName.split('.');
+ return get(entity, fieldNames);
+
+ function get(entity, fieldNames) {
+ if (fieldNames.length === 1) {
+ return _.findWhere(entityFields(entity), {name: fieldNames[0]});
+ }
+ var comboName = _.findWhere(entityFields(entity), {name: fieldNames[0] + '.' + fieldNames[1]});
+ if (comboName) {
+ return comboName;
+ }
+ var linkName = fieldNames.shift(),
+ entityLinks = _.findWhere(links, {entity: entity}).links,
+ newEntity = _.findWhere(entityLinks, {alias: linkName}).entity;
+ return get(newEntity, fieldNames);
+ }
+ }
+
+ function destroyWidget() {
+ var $el = $(element);
+ if ($el.is('.crm-form-date-wrapper .crm-hidden-date')) {
+ $el.crmDatepicker('destroy');
+ }
+ if ($el.is('.select2-container + input')) {
+ $el.crmEntityRef('destroy');
+ }
+ $(element).removeData().removeAttr('type').removeAttr('placeholder').show();
+ }
+
+ function makeWidget(field, op) {
+ var $el = $(element),
+ dataType = field.data_type,
+ multi = _.includes(['IN', 'NOT IN'], op);
+ if (op === 'IS NULL' || op === 'IS NOT NULL') {
+ $el.hide();
+ return;
+ }
+ if (dataType === 'Timestamp' || dataType === 'Date') {
+ if (_.includes(['=', '!=', '<>', '<', '>=', '<', '<='], op)) {
+ $el.crmDatepicker({time: dataType === 'Timestamp'});
+ }
+ } else if (_.includes(['=', '!=', '<>', 'IN', 'NOT IN'], op)) {
+ if (field.fk_entity) {
+ $el.crmEntityRef({entity: field.fk_entity, select:{multiple: multi}});
+ } else if (field.options) {
+ $el.addClass('loading').attr('placeholder', ts('- select -')).crmSelect2({multiple: multi, data: [{id: '', text: ''}]});
+ loadFieldOptions(field.entity).then(function(data) {
+ var options = [];
+ _.each(_.findWhere(data, {name: field.name}).options, function(val, key) {
+ options.push({id: key, text: val});
+ });
+ $el.removeClass('loading').select2({data: options, multiple: multi});
+ });
+ } else if (dataType === 'Boolean') {
+ $el.attr('placeholder', ts('- select -')).crmSelect2({allowClear: false, multiple: multi, placeholder: ts('- select -'), data: [
+ {id: '1', text: ts('Yes')},
+ {id: '0', text: ts('No')}
+ ]});
+ }
+ }
+ }
+
+ function loadFieldOptions(entity) {
+ var action = $routeParams.api4action;
+ if (!fieldOptions[entity + action]) {
+ fieldOptions[entity + action] = crmApi4(entity, 'getFields', {
+ loadOptions: true,
+ action: action,
+ where: [["options", "!=", false]],
+ select: ["name", "options"]
+ });
+ }
+ return fieldOptions[entity + action];
+ }
+
+ scope.$watchCollection('data', function(data) {
+ destroyWidget();
+ var field = getField(data.field);
+ if (field) {
+ makeWidget(field, data.op || '=');
+ }
+ });
+ }
+ };
+ });
+
+
+ angular.module('api4Explorer').directive('api4ExpChain', function(crmApi4) {
+ return {
+ scope: {
+ chain: '=api4ExpChain',
+ mainEntity: '=',
+ entities: '='
+ },
+ templateUrl: '~/api4Explorer/Chain.html',
+ link: function (scope, element, attrs) {
+ var ts = scope.ts = CRM.ts('api4');
+
+ function changeEntity(newEntity, oldEntity) {
+ // When clearing entity remove this chain
+ if (!newEntity) {
+ scope.chain[0] = '';
+ return;
+ }
+ // Reset action && index
+ if (newEntity !== oldEntity) {
+ scope.chain[1][1] = scope.chain[1][2] = '';
+ }
+ if (getEntity(newEntity).actions) {
+ setActions();
+ } else {
+ crmApi4(newEntity, 'getActions', {chain: {fields: [newEntity, 'getFields', {action: '$name'}]}})
+ .then(function(data) {
+ getEntity(data.entity).actions = data;
+ if (data.entity === scope.chain[1][0]) {
+ setActions();
+ }
+ });
+ }
+ }
+
+ function setActions() {
+ scope.actions = [''].concat(_.pluck(getEntity(scope.chain[1][0]).actions, 'name'));
+ }
+
+ // Set default params when choosing action
+ function changeAction(newAction, oldAction) {
+ var link;
+ // Prepopulate links
+ if (newAction && newAction !== oldAction) {
+ // Clear index
+ scope.chain[1][3] = '';
+ // Look for links back to main entity
+ _.each(entityFields(scope.chain[1][0]), function(field) {
+ if (field.fk_entity === scope.mainEntity) {
+ link = [field.name, '$id'];
+ }
+ });
+ // Look for links from main entity
+ if (!link && newAction !== 'create') {
+ _.each(entityFields(scope.mainEntity), function(field) {
+ if (field.fk_entity === scope.chain[1][0]) {
+ link = ['id', '$' + field.name];
+ // Since we're specifying the id, set index to getsingle
+ scope.chain[1][3] = '0';
+ }
+ });
+ }
+ if (link && _.contains(['get', 'update', 'replace', 'delete'], newAction)) {
+ scope.chain[1][2] = '{where: [[' + link[0] + ', =, ' + link[1] + ']]}';
+ }
+ else if (link && _.contains(['create'], newAction)) {
+ scope.chain[1][2] = '{values: {' + link[0] + ': ' + link[1] + '}}';
+ } else {
+ scope.chain[1][2] = '{}';
+ }
+ }
+ }
+
+ scope.$watch("chain[1][0]", changeEntity);
+ scope.$watch("chain[1][1]", changeAction);
+ }
+ };
+ });
+
+ function getEntity(entityName) {
+ return _.findWhere(schema, {name: entityName});
+ }
+
+ function entityFields(entityName) {
+ return _.result(getEntity(entityName), 'fields');
+ }
+
+ // Collapsible optgroups for select2
+ $(function() {
+ $('body')
+ .on('select2-open', function(e) {
+ if ($(e.target).hasClass('collapsible-optgroups')) {
+ $('#select2-drop')
+ .off('.collapseOptionGroup')
+ .addClass('collapsible-optgroups-enabled')
+ .on('click.collapseOptionGroup', '.select2-result-with-children > .select2-result-label', function() {
+ $(this).parent().toggleClass('optgroup-expanded');
+ });
+ }
+ })
+ .on('select2-close', function() {
+ $('#select2-drop').off('.collapseOptionGroup').removeClass('collapsible-optgroups-enabled');
+ });
+ });
+})(angular, CRM.$, CRM._);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/WhereClause.html b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/WhereClause.html
new file mode 100644
index 00000000..d36480f9
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/ang/api4Explorer/WhereClause.html
@@ -0,0 +1,39 @@
+<legend>{{ data.label || data.op + ' group' }}<span class="crm-marker" ng-if="data.required"> *</span></legend>
+<div class="btn-group btn-group-xs" ng-if="data.groupParent">
+ <button class="btn btn-danger-outline" ng-click="removeGroup()" title="{{ ts('Remove group') }}">
+ <i class="crm-i fa-trash"></i>
+ </button>
+</div>
+<div class="api4-where-group-sortable" ng-model="data.where" ui-sortable="{axis: 'y', connectWith: '.api4-where-group-sortable', containment: '.api4-where-fieldset', over: onSortOver, start: onSort, stop: onSort}">
+ <div class="api4-input form-inline clearfix" ng-repeat="(index, clause) in data.where">
+ <div class="api4-clause-badge" title="{{ ts('Drag to reposition') }}">
+ <span class="badge badge-info">
+ <span ng-if="!index && !data.groupParent">Where</span>
+ <span ng-if="index || data.groupParent">{{ data.op }}</span>
+ <i class="crm-i fa-arrows"></i>
+ </span>
+ </div>
+ <div ng-if="clause[0] !== 'AND' && clause[0] !== 'OR' && clause[0] !== 'NOT'" class="api4-input-group">
+ <input class="collapsible-optgroups form-control" ng-model="clause[0]" crm-ui-select="{data: data.fields, allowClear: true, placeholder: 'Field'}" />
+ <select class="form-control api4-operator" ng-model="clause[1]" ng-options="o for o in operators" ></select>
+ <input class="form-control" ng-model="clause[2]" api4-exp-value="{field: clause[0], op: clause[1]}" />
+ </div>
+ <fieldset class="clearfix" ng-if="clause[0] === 'AND' || clause[0] === 'OR' || clause[0] === 'NOT'" crm-api4-where-clause="{where: clause[1], op: clause[0], fields: data.fields, operators: data.operators, groupParent: data.where, groupIndex: index}">
+ </fieldset>
+ </div>
+</div>
+<div class="api4-input form-inline">
+ <div class="api4-clause-badge">
+ <div class="btn-group btn-group-xs" title="{{ data.groupParent ? ts('Add a subgroup of clauses') : ts('Add a group of clauses') }}">
+ <button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ {{ data.op }} <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu api4-add-where-group-menu">
+ <li ng-repeat="con in conjunctions" ng-if="data.op !== con">
+ <a href ng-click="addGroup(con)">{{ con }}</a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <input class="collapsible-optgroups form-control" ng-model="newClause" title="Add a single clause" crm-ui-select="{data: data.fields, placeholder: 'Add clause'}" />
+</div> \ No newline at end of file
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/api4.civix.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/api4.civix.php
new file mode 100644
index 00000000..4bd0b6be
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/api4.civix.php
@@ -0,0 +1,460 @@
+<?php
+
+// AUTO-GENERATED FILE -- Civix may overwrite any changes made to this file
+
+/**
+ * The ExtensionUtil class provides small stubs for accessing resources of this
+ * extension.
+ */
+class CRM_Api4_ExtensionUtil {
+ const SHORT_NAME = "api4";
+ const LONG_NAME = "org.civicrm.api4";
+ const CLASS_PREFIX = "CRM_Api4";
+
+ /**
+ * Translate a string using the extension's domain.
+ *
+ * If the extension doesn't have a specific translation
+ * for the string, fallback to the default translations.
+ *
+ * @param string $text
+ * Canonical message text (generally en_US).
+ * @param array $params
+ * @return string
+ * Translated text.
+ * @see ts
+ */
+ public static function ts($text, $params = array()) {
+ if (!array_key_exists('domain', $params)) {
+ $params['domain'] = array(self::LONG_NAME, NULL);
+ }
+ return ts($text, $params);
+ }
+
+ /**
+ * Get the URL of a resource file (in this extension).
+ *
+ * @param string|NULL $file
+ * Ex: NULL.
+ * Ex: 'css/foo.css'.
+ * @return string
+ * Ex: 'http://example.org/sites/default/ext/org.example.foo'.
+ * Ex: 'http://example.org/sites/default/ext/org.example.foo/css/foo.css'.
+ */
+ public static function url($file = NULL) {
+ if ($file === NULL) {
+ return rtrim(CRM_Core_Resources::singleton()->getUrl(self::LONG_NAME), '/');
+ }
+ return CRM_Core_Resources::singleton()->getUrl(self::LONG_NAME, $file);
+ }
+
+ /**
+ * Get the path of a resource file (in this extension).
+ *
+ * @param string|NULL $file
+ * Ex: NULL.
+ * Ex: 'css/foo.css'.
+ * @return string
+ * Ex: '/var/www/example.org/sites/default/ext/org.example.foo'.
+ * Ex: '/var/www/example.org/sites/default/ext/org.example.foo/css/foo.css'.
+ */
+ public static function path($file = NULL) {
+ // return CRM_Core_Resources::singleton()->getPath(self::LONG_NAME, $file);
+ return __DIR__ . ($file === NULL ? '' : (DIRECTORY_SEPARATOR . $file));
+ }
+
+ /**
+ * Get the name of a class within this extension.
+ *
+ * @param string $suffix
+ * Ex: 'Page_HelloWorld' or 'Page\\HelloWorld'.
+ * @return string
+ * Ex: 'CRM_Foo_Page_HelloWorld'.
+ */
+ public static function findClass($suffix) {
+ return self::CLASS_PREFIX . '_' . str_replace('\\', '_', $suffix);
+ }
+
+}
+
+use CRM_Api4_ExtensionUtil as E;
+
+/**
+ * (Delegated) Implements hook_civicrm_config().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_config
+ */
+function _api4_civix_civicrm_config(&$config = NULL) {
+ static $configured = FALSE;
+ if ($configured) {
+ return;
+ }
+ $configured = TRUE;
+
+ $template =& CRM_Core_Smarty::singleton();
+
+ $extRoot = dirname(__FILE__) . DIRECTORY_SEPARATOR;
+ $extDir = $extRoot . 'templates';
+
+ if (is_array($template->template_dir)) {
+ array_unshift($template->template_dir, $extDir);
+ }
+ else {
+ $template->template_dir = array($extDir, $template->template_dir);
+ }
+
+ $include_path = $extRoot . PATH_SEPARATOR . get_include_path();
+ set_include_path($include_path);
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_xmlMenu().
+ *
+ * @param $files array(string)
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_xmlMenu
+ */
+function _api4_civix_civicrm_xmlMenu(&$files) {
+ foreach (_api4_civix_glob(__DIR__ . '/xml/Menu/*.xml') as $file) {
+ $files[] = $file;
+ }
+}
+
+/**
+ * Implements hook_civicrm_install().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_install
+ */
+function _api4_civix_civicrm_install() {
+ _api4_civix_civicrm_config();
+ if ($upgrader = _api4_civix_upgrader()) {
+ $upgrader->onInstall();
+ }
+}
+
+/**
+ * Implements hook_civicrm_postInstall().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_postInstall
+ */
+function _api4_civix_civicrm_postInstall() {
+ _api4_civix_civicrm_config();
+ if ($upgrader = _api4_civix_upgrader()) {
+ if (is_callable(array($upgrader, 'onPostInstall'))) {
+ $upgrader->onPostInstall();
+ }
+ }
+}
+
+/**
+ * Implements hook_civicrm_uninstall().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_uninstall
+ */
+function _api4_civix_civicrm_uninstall() {
+ _api4_civix_civicrm_config();
+ if ($upgrader = _api4_civix_upgrader()) {
+ $upgrader->onUninstall();
+ }
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_enable().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_enable
+ */
+function _api4_civix_civicrm_enable() {
+ _api4_civix_civicrm_config();
+ if ($upgrader = _api4_civix_upgrader()) {
+ if (is_callable(array($upgrader, 'onEnable'))) {
+ $upgrader->onEnable();
+ }
+ }
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_disable().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_disable
+ * @return mixed
+ */
+function _api4_civix_civicrm_disable() {
+ _api4_civix_civicrm_config();
+ if ($upgrader = _api4_civix_upgrader()) {
+ if (is_callable(array($upgrader, 'onDisable'))) {
+ $upgrader->onDisable();
+ }
+ }
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_upgrade().
+ *
+ * @param $op string, the type of operation being performed; 'check' or 'enqueue'
+ * @param $queue CRM_Queue_Queue, (for 'enqueue') the modifiable list of pending up upgrade tasks
+ *
+ * @return mixed based on op. for 'check', returns array(boolean) (TRUE if upgrades are pending)
+ * for 'enqueue', returns void
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_upgrade
+ */
+function _api4_civix_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) {
+ if ($upgrader = _api4_civix_upgrader()) {
+ return $upgrader->onUpgrade($op, $queue);
+ }
+}
+
+/**
+ * @return CRM_Api4_Upgrader
+ */
+function _api4_civix_upgrader() {
+ if (!file_exists(__DIR__ . '/CRM/Api4/Upgrader.php')) {
+ return NULL;
+ }
+ else {
+ return CRM_Api4_Upgrader_Base::instance();
+ }
+}
+
+/**
+ * Search directory tree for files which match a glob pattern
+ *
+ * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
+ * Note: In Civi 4.3+, delegate to CRM_Utils_File::findFiles()
+ *
+ * @param $dir string, base dir
+ * @param $pattern string, glob pattern, eg "*.txt"
+ * @return array(string)
+ */
+function _api4_civix_find_files($dir, $pattern) {
+ if (is_callable(array('CRM_Utils_File', 'findFiles'))) {
+ return CRM_Utils_File::findFiles($dir, $pattern);
+ }
+
+ $todos = array($dir);
+ $result = array();
+ while (!empty($todos)) {
+ $subdir = array_shift($todos);
+ foreach (_api4_civix_glob("$subdir/$pattern") as $match) {
+ if (!is_dir($match)) {
+ $result[] = $match;
+ }
+ }
+ if ($dh = opendir($subdir)) {
+ while (FALSE !== ($entry = readdir($dh))) {
+ $path = $subdir . DIRECTORY_SEPARATOR . $entry;
+ if ($entry{0} == '.') {
+ }
+ elseif (is_dir($path)) {
+ $todos[] = $path;
+ }
+ }
+ closedir($dh);
+ }
+ }
+ return $result;
+}
+/**
+ * (Delegated) Implements hook_civicrm_managed().
+ *
+ * Find any *.mgd.php files, merge their content, and return.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_managed
+ */
+function _api4_civix_civicrm_managed(&$entities) {
+ $mgdFiles = _api4_civix_find_files(__DIR__, '*.mgd.php');
+ foreach ($mgdFiles as $file) {
+ $es = include $file;
+ foreach ($es as $e) {
+ if (empty($e['module'])) {
+ $e['module'] = E::LONG_NAME;
+ }
+ $entities[] = $e;
+ if (empty($e['params']['version'])) {
+ $e['params']['version'] = '3';
+ }
+ }
+ }
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_caseTypes().
+ *
+ * Find any and return any files matching "xml/case/*.xml"
+ *
+ * Note: This hook only runs in CiviCRM 4.4+.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_caseTypes
+ */
+function _api4_civix_civicrm_caseTypes(&$caseTypes) {
+ if (!is_dir(__DIR__ . '/xml/case')) {
+ return;
+ }
+
+ foreach (_api4_civix_glob(__DIR__ . '/xml/case/*.xml') as $file) {
+ $name = preg_replace('/\.xml$/', '', basename($file));
+ if ($name != CRM_Case_XMLProcessor::mungeCaseType($name)) {
+ $errorMessage = sprintf("Case-type file name is malformed (%s vs %s)", $name, CRM_Case_XMLProcessor::mungeCaseType($name));
+ CRM_Core_Error::fatal($errorMessage);
+ // throw new CRM_Core_Exception($errorMessage);
+ }
+ $caseTypes[$name] = array(
+ 'module' => E::LONG_NAME,
+ 'name' => $name,
+ 'file' => $file,
+ );
+ }
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_angularModules().
+ *
+ * Find any and return any files matching "ang/*.ang.php"
+ *
+ * Note: This hook only runs in CiviCRM 4.5+.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_angularModules
+ */
+function _api4_civix_civicrm_angularModules(&$angularModules) {
+ if (!is_dir(__DIR__ . '/ang')) {
+ return;
+ }
+
+ $files = _api4_civix_glob(__DIR__ . '/ang/*.ang.php');
+ foreach ($files as $file) {
+ $name = preg_replace(':\.ang\.php$:', '', basename($file));
+ $module = include $file;
+ if (empty($module['ext'])) {
+ $module['ext'] = E::LONG_NAME;
+ }
+ $angularModules[$name] = $module;
+ }
+}
+
+/**
+ * Glob wrapper which is guaranteed to return an array.
+ *
+ * The documentation for glob() says, "On some systems it is impossible to
+ * distinguish between empty match and an error." Anecdotally, the return
+ * result for an empty match is sometimes array() and sometimes FALSE.
+ * This wrapper provides consistency.
+ *
+ * @link http://php.net/glob
+ * @param string $pattern
+ * @return array, possibly empty
+ */
+function _api4_civix_glob($pattern) {
+ $result = glob($pattern);
+ return is_array($result) ? $result : array();
+}
+
+/**
+ * Inserts a navigation menu item at a given place in the hierarchy.
+ *
+ * @param array $menu - menu hierarchy
+ * @param string $path - path to parent of this item, e.g. 'my_extension/submenu'
+ * 'Mailing', or 'Administer/System Settings'
+ * @param array $item - the item to insert (parent/child attributes will be
+ * filled for you)
+ */
+function _api4_civix_insert_navigation_menu(&$menu, $path, $item) {
+ // If we are done going down the path, insert menu
+ if (empty($path)) {
+ $menu[] = array(
+ 'attributes' => array_merge(array(
+ 'label' => CRM_Utils_Array::value('name', $item),
+ 'active' => 1,
+ ), $item),
+ );
+ return TRUE;
+ }
+ else {
+ // Find an recurse into the next level down
+ $found = FALSE;
+ $path = explode('/', $path);
+ $first = array_shift($path);
+ foreach ($menu as $key => &$entry) {
+ if ($entry['attributes']['name'] == $first) {
+ if (!isset($entry['child'])) {
+ $entry['child'] = array();
+ }
+ $found = _api4_civix_insert_navigation_menu($entry['child'], implode('/', $path), $item, $key);
+ }
+ }
+ return $found;
+ }
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_navigationMenu().
+ */
+function _api4_civix_navigationMenu(&$nodes) {
+ if (!is_callable(array('CRM_Core_BAO_Navigation', 'fixNavigationMenu'))) {
+ _api4_civix_fixNavigationMenu($nodes);
+ }
+}
+
+/**
+ * Given a navigation menu, generate navIDs for any items which are
+ * missing them.
+ */
+function _api4_civix_fixNavigationMenu(&$nodes) {
+ $maxNavID = 1;
+ array_walk_recursive($nodes, function($item, $key) use (&$maxNavID) {
+ if ($key === 'navID') {
+ $maxNavID = max($maxNavID, $item);
+ }
+ });
+ _api4_civix_fixNavigationMenuItems($nodes, $maxNavID, NULL);
+}
+
+function _api4_civix_fixNavigationMenuItems(&$nodes, &$maxNavID, $parentID) {
+ $origKeys = array_keys($nodes);
+ foreach ($origKeys as $origKey) {
+ if (!isset($nodes[$origKey]['attributes']['parentID']) && $parentID !== NULL) {
+ $nodes[$origKey]['attributes']['parentID'] = $parentID;
+ }
+ // If no navID, then assign navID and fix key.
+ if (!isset($nodes[$origKey]['attributes']['navID'])) {
+ $newKey = ++$maxNavID;
+ $nodes[$origKey]['attributes']['navID'] = $newKey;
+ $nodes[$newKey] = $nodes[$origKey];
+ unset($nodes[$origKey]);
+ $origKey = $newKey;
+ }
+ if (isset($nodes[$origKey]['child']) && is_array($nodes[$origKey]['child'])) {
+ _api4_civix_fixNavigationMenuItems($nodes[$origKey]['child'], $maxNavID, $nodes[$origKey]['attributes']['navID']);
+ }
+ }
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_alterSettingsFolders().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_alterSettingsFolders
+ */
+function _api4_civix_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) {
+ static $configured = FALSE;
+ if ($configured) {
+ return;
+ }
+ $configured = TRUE;
+
+ $settingsDir = __DIR__ . DIRECTORY_SEPARATOR . 'settings';
+ if (is_dir($settingsDir) && !in_array($settingsDir, $metaDataFolders)) {
+ $metaDataFolders[] = $settingsDir;
+ }
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_entityTypes().
+ *
+ * Find any *.entityType.php files, merge their content, and return.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_entityTypes
+ */
+
+function _api4_civix_civicrm_entityTypes(&$entityTypes) {
+ $entityTypes = array_merge($entityTypes, array (
+ ));
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/api4.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/api4.php
new file mode 100644
index 00000000..9c05d773
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/api4.php
@@ -0,0 +1,193 @@
+<?php
+
+require_once 'api4.civix.php';
+require_once 'api/Exception.php';
+
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
+use Symfony\Component\Config\FileLocator;
+
+/**
+ * Procedural wrapper for the OO api version 4.
+ *
+ * @param string $entity
+ * @param string $action
+ * @param array $params
+ * @param string|int $index
+ * If $index is a string, the results array will be indexed by that key.
+ * If $index is an integer, only the result at that index will be returned.
+ *
+ * @return \Civi\Api4\Generic\Result
+ * @throws \API_Exception
+ * @throws \Civi\API\Exception\NotImplementedException
+ */
+function civicrm_api4($entity, $action, $params = [], $index = NULL) {
+ $apiCall = \Civi\Api4\Utils\ActionUtil::getAction($entity, $action);
+ foreach ($params as $name => $param) {
+ $setter = 'set' . ucfirst($name);
+ $apiCall->$setter($param);
+ }
+ $result = $apiCall->execute();
+
+ // Index results by key
+ if ($index && is_string($index) && !CRM_Utils_Rule::integer($index)) {
+ $result->indexBy($index);
+ }
+ // Return result at index
+ if (CRM_Utils_Rule::integer($index)) {
+ $item = $result->itemAt($index);
+ if (is_null($item)) {
+ throw new \API_Exception("Index $index not found in api results");
+ }
+ $result->exchangeArray($item);
+
+ }
+ return $result;
+}
+
+/**
+ * @param ContainerBuilder $container
+ */
+function api4_civicrm_container($container) {
+ $loader = new XmlFileLoader($container, new FileLocator(__DIR__));
+ $loader->load('services.xml');
+
+ $container->getDefinition('civi_api_kernel')->addMethodCall(
+ 'registerApiProvider',
+ [new Reference('action_object_provider')]
+ );
+
+ // add event subscribers$container->get(
+ $dispatcher = $container->getDefinition('dispatcher');
+ $subscribers = $container->findTaggedServiceIds('event_subscriber');
+
+ foreach (array_keys($subscribers) as $subscriber) {
+ $dispatcher->addMethodCall(
+ 'addSubscriber',
+ [new Reference($subscriber)]
+ );
+ }
+
+ // add spec providers
+ $providers = $container->findTaggedServiceIds('spec_provider');
+ $gatherer = $container->getDefinition('spec_gatherer');
+
+ foreach (array_keys($providers) as $provider) {
+ $gatherer->addMethodCall(
+ 'addSpecProvider',
+ [new Reference($provider)]
+ );
+ }
+
+ if (defined('CIVICRM_UF') && CIVICRM_UF === 'UnitTests') {
+ $loader->load('tests/services.xml');
+ }
+}
+
+/**
+ * Implements hook_civicrm_coreResourceList().
+ */
+function api4_civicrm_coreResourceList(&$list, $region) {
+ if ($region == 'html-header') {
+ Civi::resources()->addScriptFile('org.civicrm.api4', 'js/api4.js', -9000, $region);
+ }
+}
+
+/**
+ * Implements hook_civicrm_config().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_config
+ */
+function api4_civicrm_config(&$config) {
+ _api4_civix_civicrm_config($config);
+}
+
+/**
+ * Implements hook_civicrm_xmlMenu().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_xmlMenu
+ */
+function api4_civicrm_xmlMenu(&$files) {
+ _api4_civix_civicrm_xmlMenu($files);
+}
+
+/**
+ * Implements hook_civicrm_install().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_install
+ */
+function api4_civicrm_install() {
+ _api4_civix_civicrm_install();
+}
+
+/**
+ * Implements hook_civicrm_uninstall().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_uninstall
+ */
+function api4_civicrm_uninstall() {
+ _api4_civix_civicrm_uninstall();
+}
+
+/**
+ * Implements hook_civicrm_enable().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_enable
+ */
+function api4_civicrm_enable() {
+ _api4_civix_civicrm_enable();
+}
+
+/**
+ * Implements hook_civicrm_disable().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_disable
+ */
+function api4_civicrm_disable() {
+ _api4_civix_civicrm_disable();
+}
+
+/**
+ * Implements hook_civicrm_upgrade().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_upgrade
+ */
+function api4_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) {
+ return _api4_civix_civicrm_upgrade($op, $queue);
+}
+
+/**
+ * Implements hook_civicrm_managed().
+ *
+ * Generate a list of entities to create/deactivate/delete when this module
+ * is installed, disabled, uninstalled.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_managed
+ */
+function api4_civicrm_managed(&$entities) {
+ _api4_civix_civicrm_managed($entities);
+}
+
+/**
+ * Implements hook_civicrm_angularModules().
+ *
+ * Generate a list of Angular modules.
+ *
+ * Note: This hook only runs in CiviCRM 4.5+. It may
+ * use features only available in v4.6+.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_caseTypes
+ */
+function api4_civicrm_angularModules(&$angularModules) {
+ _api4_civix_civicrm_angularModules($angularModules);
+}
+
+/**
+ * Implements hook_civicrm_alterSettingsFolders().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_alterSettingsFolders
+ */
+function api4_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) {
+ _api4_civix_civicrm_alterSettingsFolders($metaDataFolders);
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/css/explorer.css b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/css/explorer.css
new file mode 100644
index 00000000..0a5f5d17
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/css/explorer.css
@@ -0,0 +1,191 @@
+/* Style rules for Api4 Explorer */
+
+#bootstrap-theme.api4-explorer-page .panel-heading {
+ height: 50px;
+}
+#bootstrap-theme.api4-explorer-page .panel-body {
+ min-height: calc( 100% - 50px);
+}
+#bootstrap-theme.api4-explorer-page .explorer-params-panel .panel-heading {
+ padding-top: 12px;
+}
+#bootstrap-theme.api4-explorer-page .explorer-params-panel .panel-heading button {
+ position: relative;
+ top: -5px;
+}
+#bootstrap-theme .explorer-params-panel .panel-heading .form-inline > .select2-container {
+ max-width: 25% !important;
+}
+#bootstrap-theme.api4-explorer-page .api4-explorer-row {
+ display: flex;
+}
+#bootstrap-theme.api4-explorer-page > div > .panel {
+ flex: 1;
+ margin: 10px;
+ min-height: 400px;
+}
+#bootstrap-theme.api4-explorer-page > div > form.panel {
+ flex: 2;
+}
+/* Fix weird shorditch style */
+#bootstrap-theme.api4-explorer-page .api4-explorer-row .panel .panel-heading {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ margin-bottom: 0;
+}
+#bootstrap-theme.api4-explorer-page .explorer-code-panel table td:first-child {
+ width: 5em;
+}
+
+#bootstrap-theme.api4-explorer-page .explorer-params-panel > .panel-body > div.api4-input {
+ margin-bottom: 10px;
+}
+
+#bootstrap-theme.api4-explorer-page .explorer-help-panel .panel-body {
+ word-break: break-word;
+}
+
+#bootstrap-theme.api4-explorer-page form label {
+ text-transform: capitalize;
+}
+
+#bootstrap-theme.api4-explorer-page fieldset {
+ padding: 6px;
+ border: 1px solid lightgrey;
+ margin-bottom: 10px;
+ position: relative;
+}
+
+#bootstrap-theme.api4-explorer-page fieldset legend {
+ background-color: white;
+ font-size: 13px;
+ margin: 0;
+ width: auto;
+ border: 0 none;
+ padding: 2px 5px;
+ text-transform: capitalize;
+}
+#bootstrap-theme.api4-explorer-page fieldset > .btn-group {
+ position: absolute;
+ right: 0;
+ top: 11px;
+}
+#bootstrap-theme.api4-explorer-page fieldset > .btn-group .btn {
+ border: 0 none;
+}
+
+#bootstrap-theme.api4-explorer-page fieldset div.api4-input {
+ margin-bottom: 10px;
+}
+
+#bootstrap-theme.api4-explorer-page fieldset div.api4-input.ui-sortable-helper {
+ background-color: rgba(255, 255, 255, .9);
+}
+
+#bootstrap-theme.api4-explorer-page fieldset div.api4-input.ui-sortable-helper {
+ background-color: rgba(255, 255, 255, .9);
+}
+
+#bootstrap-theme.api4-explorer-page div.api4-input.form-inline .form-control {
+ margin-right: 6px;
+}
+
+#bootstrap-theme.api4-explorer-page div.api4-input.form-inline .form-control:not(.api4-option-selected) {
+ transition: none;
+ box-shadow: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+}
+
+#bootstrap-theme.api4-explorer-page div.api4-input.form-inline .form-control label {
+ font-weight: normal;
+ position: relative;
+ top: -2px;
+}
+
+#bootstrap-theme.api4-explorer-page .api4-where-fieldset fieldset {
+ float: right;
+ width: calc(100% - 58px);
+ margin-top: -8px;
+}
+
+#bootstrap-theme.api4-explorer-page .api4-where-fieldset.api4-sorting fieldset .api4-where-group-sortable {
+ min-height: 3.5em;
+}
+
+#bootstrap-theme.api4-explorer-page .api4-input-group {
+ display: inline-block;
+}
+
+#bootstrap-theme.api4-explorer-page .api4-clause-badge {
+ width: 55px;
+ display: inline-block;
+ cursor: move;
+}
+#bootstrap-theme.api4-explorer-page .api4-clause-badge .badge {
+ opacity: .5;
+ position: relative;
+}
+#bootstrap-theme.api4-explorer-page .api4-clause-badge .caret {
+ margin: 0;
+}
+#bootstrap-theme.api4-explorer-page .api4-clause-badge .crm-i {
+ display: none;
+ padding: 0 6px;
+}
+#bootstrap-theme.api4-explorer-page .ui-sortable-helper .api4-clause-badge .badge span {
+ display: none;
+}
+#bootstrap-theme.api4-explorer-page .ui-sortable-helper .api4-clause-badge .crm-i {
+ display: inline-block;
+}
+
+#bootstrap-theme.api4-explorer-page .api4-operator,
+#bootstrap-theme.api4-explorer-page .api4-chain-index,
+#bootstrap-theme.api4-explorer-page .api4-index,
+#bootstrap-theme.api4-explorer-page .api4-chain-action {
+ width: 70px;
+}
+#bootstrap-theme.api4-explorer-page .api4-chain-params {
+ width: calc( 100% - 346px);
+}
+
+#bootstrap-theme.api4-explorer-page .api4-add-where-group-menu {
+ min-width: 80px;
+ background-color: rgba(186, 225, 251, 0.94);
+}
+#bootstrap-theme.api4-explorer-page .api4-add-where-group-menu a {
+ padding: 5px 10px;
+}
+
+/* Collapsible optgroups for select2 */
+div.select2-drop.collapsible-optgroups-enabled .select2-result-with-children:not(.optgroup-expanded) > .select2-result-sub > li.select2-result {
+ display: none;
+}
+div.select2-drop.collapsible-optgroups-enabled .select2-result-with-children > .select2-result-label:before {
+ font-family: FontAwesome;
+ content: "\f0da";
+ display: inline-block;
+ padding-right: 3px;
+ vertical-align: middle;
+ font-weight: normal;
+}
+div.select2-drop.collapsible-optgroups-enabled .select2-result-with-children.optgroup-expanded > .select2-result-label:before {
+ content: "\f0d7";
+}
+
+/**
+ * Shims so the UI isn't completely broken when a Bootstrap theme is not installed
+ */
+#bootstrap-theme.api4-explorer-page * {
+ box-sizing: border-box;
+}
+.api4-explorer-page.panel {
+ border: 1px solid grey;
+ background-color: white;
+}
+.api4-explorer-page.panel-heading {
+ padding: 10px 20px;
+ color: #464354;
+ background-color: #f3f6f7;
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/images/ApiExplorer.png b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/images/ApiExplorer.png
new file mode 100644
index 00000000..1eb25124
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/images/ApiExplorer.png
Binary files differ
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/info.xml b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/info.xml
new file mode 100644
index 00000000..4e04b8ef
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/info.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<extension key="org.civicrm.api4" type="module">
+ <file>api4</file>
+ <name>API v4</name>
+ <description>Makes version 4 of the CiviCRM API available to other extensions.</description>
+ <license>AGPL-3.0</license>
+ <maintainer>
+ <author>Coleman Watts</author>
+ <email>coleman@civicrm.org</email>
+ </maintainer>
+ <urls>
+ <url desc="Main Extension Page">https://github.com/civicrm/api4</url>
+ <url desc="Documentation">https://wiki.civicrm.org/confluence/display/CRM/API+v4+Spec</url>
+ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url>
+ </urls>
+ <releaseDate>2019-05-22</releaseDate>
+ <version>4.4.2</version>
+ <develStage>stable</develStage>
+ <compatibility>
+ <ver>5.13</ver>
+ </compatibility>
+ <comments>This extension does nothing on its own. Install it if other extensions require you to do so.</comments>
+ <classloader>
+ <psr4 prefix="Civi\" path="Civi" />
+ </classloader>
+ <civix>
+ <namespace>CRM/Api4</namespace>
+ </civix>
+</extension>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/js/api4.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/js/api4.js
new file mode 100644
index 00000000..7306168a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/js/api4.js
@@ -0,0 +1,43 @@
+(function($, _) {
+
+ // result is an array, but in js, an array is also an object
+ // Assign all the metadata properties to it, mirroring the results arrayObject in php
+ function arrayObject(data) {
+ var result = data.values || [];
+ if (_.isArray(result)) {
+ delete(data.values);
+ _.assign(result, data);
+ }
+ return result;
+ }
+
+ CRM.api4 = function(entity, action, params, index) {
+ return new Promise(function(resolve, reject) {
+ if (typeof entity === 'string') {
+ $.post(CRM.url('civicrm/ajax/api4/' + entity + '/' + action), {
+ params: JSON.stringify(params),
+ index: index
+ })
+ .done(function (data) {
+ resolve(arrayObject(data));
+ })
+ .fail(function (data) {
+ reject(data.responseJSON);
+ });
+ } else {
+ $.post(CRM.url('civicrm/ajax/api4'), {
+ calls: JSON.stringify(entity)
+ })
+ .done(function(data) {
+ _.each(data, function(item, key) {
+ data[key] = arrayObject(item);
+ });
+ resolve(data);
+ })
+ .fail(function (data) {
+ reject(data.responseJSON);
+ });
+ }
+ });
+ };
+})(CRM.$, CRM._); \ No newline at end of file
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/js/load-bootstrap.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/js/load-bootstrap.js
new file mode 100644
index 00000000..aff280a0
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/js/load-bootstrap.js
@@ -0,0 +1,7 @@
+// Loads a copy of shoreditch's bootstrap if bootstrap is missing
+CRM.$(function($) {
+ if (!$.isFunction($.fn.dropdown)) {
+ CRM.loadScript(CRM.vars.api4.basePath + 'lib/shoreditch/dropdown.js');
+ $('head').append('<link type="text/css" rel="stylesheet" href="' + CRM.vars.api4.basePath + 'lib/shoreditch/bootstrap.css" />');
+ }
+}); \ No newline at end of file
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/js-yaml.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/js-yaml.js
new file mode 100644
index 00000000..0f8df899
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/js-yaml.js
@@ -0,0 +1,3919 @@
+/* js-yaml 3.12.1 https://github.com/nodeca/js-yaml */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.jsyaml = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+'use strict';
+
+
+var loader = require('./js-yaml/loader');
+var dumper = require('./js-yaml/dumper');
+
+
+function deprecated(name) {
+ return function () {
+ throw new Error('Function ' + name + ' is deprecated and cannot be used.');
+ };
+}
+
+
+module.exports.Type = require('./js-yaml/type');
+module.exports.Schema = require('./js-yaml/schema');
+module.exports.FAILSAFE_SCHEMA = require('./js-yaml/schema/failsafe');
+module.exports.JSON_SCHEMA = require('./js-yaml/schema/json');
+module.exports.CORE_SCHEMA = require('./js-yaml/schema/core');
+module.exports.DEFAULT_SAFE_SCHEMA = require('./js-yaml/schema/default_safe');
+module.exports.DEFAULT_FULL_SCHEMA = require('./js-yaml/schema/default_full');
+module.exports.load = loader.load;
+module.exports.loadAll = loader.loadAll;
+module.exports.safeLoad = loader.safeLoad;
+module.exports.safeLoadAll = loader.safeLoadAll;
+module.exports.dump = dumper.dump;
+module.exports.safeDump = dumper.safeDump;
+module.exports.YAMLException = require('./js-yaml/exception');
+
+// Deprecated schema names from JS-YAML 2.0.x
+module.exports.MINIMAL_SCHEMA = require('./js-yaml/schema/failsafe');
+module.exports.SAFE_SCHEMA = require('./js-yaml/schema/default_safe');
+module.exports.DEFAULT_SCHEMA = require('./js-yaml/schema/default_full');
+
+// Deprecated functions from JS-YAML 1.x.x
+module.exports.scan = deprecated('scan');
+module.exports.parse = deprecated('parse');
+module.exports.compose = deprecated('compose');
+module.exports.addConstructor = deprecated('addConstructor');
+
+},{"./js-yaml/dumper":3,"./js-yaml/exception":4,"./js-yaml/loader":5,"./js-yaml/schema":7,"./js-yaml/schema/core":8,"./js-yaml/schema/default_full":9,"./js-yaml/schema/default_safe":10,"./js-yaml/schema/failsafe":11,"./js-yaml/schema/json":12,"./js-yaml/type":13}],2:[function(require,module,exports){
+'use strict';
+
+
+function isNothing(subject) {
+ return (typeof subject === 'undefined') || (subject === null);
+}
+
+
+function isObject(subject) {
+ return (typeof subject === 'object') && (subject !== null);
+}
+
+
+function toArray(sequence) {
+ if (Array.isArray(sequence)) return sequence;
+ else if (isNothing(sequence)) return [];
+
+ return [ sequence ];
+}
+
+
+function extend(target, source) {
+ var index, length, key, sourceKeys;
+
+ if (source) {
+ sourceKeys = Object.keys(source);
+
+ for (index = 0, length = sourceKeys.length; index < length; index += 1) {
+ key = sourceKeys[index];
+ target[key] = source[key];
+ }
+ }
+
+ return target;
+}
+
+
+function repeat(string, count) {
+ var result = '', cycle;
+
+ for (cycle = 0; cycle < count; cycle += 1) {
+ result += string;
+ }
+
+ return result;
+}
+
+
+function isNegativeZero(number) {
+ return (number === 0) && (Number.NEGATIVE_INFINITY === 1 / number);
+}
+
+
+module.exports.isNothing = isNothing;
+module.exports.isObject = isObject;
+module.exports.toArray = toArray;
+module.exports.repeat = repeat;
+module.exports.isNegativeZero = isNegativeZero;
+module.exports.extend = extend;
+
+},{}],3:[function(require,module,exports){
+'use strict';
+
+/*eslint-disable no-use-before-define*/
+
+var common = require('./common');
+var YAMLException = require('./exception');
+var DEFAULT_FULL_SCHEMA = require('./schema/default_full');
+var DEFAULT_SAFE_SCHEMA = require('./schema/default_safe');
+
+var _toString = Object.prototype.toString;
+var _hasOwnProperty = Object.prototype.hasOwnProperty;
+
+var CHAR_TAB = 0x09; /* Tab */
+var CHAR_LINE_FEED = 0x0A; /* LF */
+var CHAR_SPACE = 0x20; /* Space */
+var CHAR_EXCLAMATION = 0x21; /* ! */
+var CHAR_DOUBLE_QUOTE = 0x22; /* " */
+var CHAR_SHARP = 0x23; /* # */
+var CHAR_PERCENT = 0x25; /* % */
+var CHAR_AMPERSAND = 0x26; /* & */
+var CHAR_SINGLE_QUOTE = 0x27; /* ' */
+var CHAR_ASTERISK = 0x2A; /* * */
+var CHAR_COMMA = 0x2C; /* , */
+var CHAR_MINUS = 0x2D; /* - */
+var CHAR_COLON = 0x3A; /* : */
+var CHAR_GREATER_THAN = 0x3E; /* > */
+var CHAR_QUESTION = 0x3F; /* ? */
+var CHAR_COMMERCIAL_AT = 0x40; /* @ */
+var CHAR_LEFT_SQUARE_BRACKET = 0x5B; /* [ */
+var CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */
+var CHAR_GRAVE_ACCENT = 0x60; /* ` */
+var CHAR_LEFT_CURLY_BRACKET = 0x7B; /* { */
+var CHAR_VERTICAL_LINE = 0x7C; /* | */
+var CHAR_RIGHT_CURLY_BRACKET = 0x7D; /* } */
+
+var ESCAPE_SEQUENCES = {};
+
+ESCAPE_SEQUENCES[0x00] = '\\0';
+ESCAPE_SEQUENCES[0x07] = '\\a';
+ESCAPE_SEQUENCES[0x08] = '\\b';
+ESCAPE_SEQUENCES[0x09] = '\\t';
+ESCAPE_SEQUENCES[0x0A] = '\\n';
+ESCAPE_SEQUENCES[0x0B] = '\\v';
+ESCAPE_SEQUENCES[0x0C] = '\\f';
+ESCAPE_SEQUENCES[0x0D] = '\\r';
+ESCAPE_SEQUENCES[0x1B] = '\\e';
+ESCAPE_SEQUENCES[0x22] = '\\"';
+ESCAPE_SEQUENCES[0x5C] = '\\\\';
+ESCAPE_SEQUENCES[0x85] = '\\N';
+ESCAPE_SEQUENCES[0xA0] = '\\_';
+ESCAPE_SEQUENCES[0x2028] = '\\L';
+ESCAPE_SEQUENCES[0x2029] = '\\P';
+
+var DEPRECATED_BOOLEANS_SYNTAX = [
+ 'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON',
+ 'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF'
+];
+
+function compileStyleMap(schema, map) {
+ var result, keys, index, length, tag, style, type;
+
+ if (map === null) return {};
+
+ result = {};
+ keys = Object.keys(map);
+
+ for (index = 0, length = keys.length; index < length; index += 1) {
+ tag = keys[index];
+ style = String(map[tag]);
+
+ if (tag.slice(0, 2) === '!!') {
+ tag = 'tag:yaml.org,2002:' + tag.slice(2);
+ }
+ type = schema.compiledTypeMap['fallback'][tag];
+
+ if (type && _hasOwnProperty.call(type.styleAliases, style)) {
+ style = type.styleAliases[style];
+ }
+
+ result[tag] = style;
+ }
+
+ return result;
+}
+
+function encodeHex(character) {
+ var string, handle, length;
+
+ string = character.toString(16).toUpperCase();
+
+ if (character <= 0xFF) {
+ handle = 'x';
+ length = 2;
+ } else if (character <= 0xFFFF) {
+ handle = 'u';
+ length = 4;
+ } else if (character <= 0xFFFFFFFF) {
+ handle = 'U';
+ length = 8;
+ } else {
+ throw new YAMLException('code point within a string may not be greater than 0xFFFFFFFF');
+ }
+
+ return '\\' + handle + common.repeat('0', length - string.length) + string;
+}
+
+function State(options) {
+ this.schema = options['schema'] || DEFAULT_FULL_SCHEMA;
+ this.indent = Math.max(1, (options['indent'] || 2));
+ this.noArrayIndent = options['noArrayIndent'] || false;
+ this.skipInvalid = options['skipInvalid'] || false;
+ this.flowLevel = (common.isNothing(options['flowLevel']) ? -1 : options['flowLevel']);
+ this.styleMap = compileStyleMap(this.schema, options['styles'] || null);
+ this.sortKeys = options['sortKeys'] || false;
+ this.lineWidth = options['lineWidth'] || 80;
+ this.noRefs = options['noRefs'] || false;
+ this.noCompatMode = options['noCompatMode'] || false;
+ this.condenseFlow = options['condenseFlow'] || false;
+
+ this.implicitTypes = this.schema.compiledImplicit;
+ this.explicitTypes = this.schema.compiledExplicit;
+
+ this.tag = null;
+ this.result = '';
+
+ this.duplicates = [];
+ this.usedDuplicates = null;
+}
+
+// Indents every line in a string. Empty lines (\n only) are not indented.
+function indentString(string, spaces) {
+ var ind = common.repeat(' ', spaces),
+ position = 0,
+ next = -1,
+ result = '',
+ line,
+ length = string.length;
+
+ while (position < length) {
+ next = string.indexOf('\n', position);
+ if (next === -1) {
+ line = string.slice(position);
+ position = length;
+ } else {
+ line = string.slice(position, next + 1);
+ position = next + 1;
+ }
+
+ if (line.length && line !== '\n') result += ind;
+
+ result += line;
+ }
+
+ return result;
+}
+
+function generateNextLine(state, level) {
+ return '\n' + common.repeat(' ', state.indent * level);
+}
+
+function testImplicitResolving(state, str) {
+ var index, length, type;
+
+ for (index = 0, length = state.implicitTypes.length; index < length; index += 1) {
+ type = state.implicitTypes[index];
+
+ if (type.resolve(str)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// [33] s-white ::= s-space | s-tab
+function isWhitespace(c) {
+ return c === CHAR_SPACE || c === CHAR_TAB;
+}
+
+// Returns true if the character can be printed without escaping.
+// From YAML 1.2: "any allowed characters known to be non-printable
+// should also be escaped. [However,] This isn’t mandatory"
+// Derived from nb-char - \t - #x85 - #xA0 - #x2028 - #x2029.
+function isPrintable(c) {
+ return (0x00020 <= c && c <= 0x00007E)
+ || ((0x000A1 <= c && c <= 0x00D7FF) && c !== 0x2028 && c !== 0x2029)
+ || ((0x0E000 <= c && c <= 0x00FFFD) && c !== 0xFEFF /* BOM */)
+ || (0x10000 <= c && c <= 0x10FFFF);
+}
+
+// Simplified test for values allowed after the first character in plain style.
+function isPlainSafe(c) {
+ // Uses a subset of nb-char - c-flow-indicator - ":" - "#"
+ // where nb-char ::= c-printable - b-char - c-byte-order-mark.
+ return isPrintable(c) && c !== 0xFEFF
+ // - c-flow-indicator
+ && c !== CHAR_COMMA
+ && c !== CHAR_LEFT_SQUARE_BRACKET
+ && c !== CHAR_RIGHT_SQUARE_BRACKET
+ && c !== CHAR_LEFT_CURLY_BRACKET
+ && c !== CHAR_RIGHT_CURLY_BRACKET
+ // - ":" - "#"
+ && c !== CHAR_COLON
+ && c !== CHAR_SHARP;
+}
+
+// Simplified test for values allowed as the first character in plain style.
+function isPlainSafeFirst(c) {
+ // Uses a subset of ns-char - c-indicator
+ // where ns-char = nb-char - s-white.
+ return isPrintable(c) && c !== 0xFEFF
+ && !isWhitespace(c) // - s-white
+ // - (c-indicator ::=
+ // “-” | “?” | “:” | “,” | “[” | “]” | “{” | “}”
+ && c !== CHAR_MINUS
+ && c !== CHAR_QUESTION
+ && c !== CHAR_COLON
+ && c !== CHAR_COMMA
+ && c !== CHAR_LEFT_SQUARE_BRACKET
+ && c !== CHAR_RIGHT_SQUARE_BRACKET
+ && c !== CHAR_LEFT_CURLY_BRACKET
+ && c !== CHAR_RIGHT_CURLY_BRACKET
+ // | “#” | “&” | “*” | “!” | “|” | “>” | “'” | “"”
+ && c !== CHAR_SHARP
+ && c !== CHAR_AMPERSAND
+ && c !== CHAR_ASTERISK
+ && c !== CHAR_EXCLAMATION
+ && c !== CHAR_VERTICAL_LINE
+ && c !== CHAR_GREATER_THAN
+ && c !== CHAR_SINGLE_QUOTE
+ && c !== CHAR_DOUBLE_QUOTE
+ // | “%” | “@” | “`”)
+ && c !== CHAR_PERCENT
+ && c !== CHAR_COMMERCIAL_AT
+ && c !== CHAR_GRAVE_ACCENT;
+}
+
+// Determines whether block indentation indicator is required.
+function needIndentIndicator(string) {
+ var leadingSpaceRe = /^\n* /;
+ return leadingSpaceRe.test(string);
+}
+
+var STYLE_PLAIN = 1,
+ STYLE_SINGLE = 2,
+ STYLE_LITERAL = 3,
+ STYLE_FOLDED = 4,
+ STYLE_DOUBLE = 5;
+
+// Determines which scalar styles are possible and returns the preferred style.
+// lineWidth = -1 => no limit.
+// Pre-conditions: str.length > 0.
+// Post-conditions:
+// STYLE_PLAIN or STYLE_SINGLE => no \n are in the string.
+// STYLE_LITERAL => no lines are suitable for folding (or lineWidth is -1).
+// STYLE_FOLDED => a line > lineWidth and can be folded (and lineWidth != -1).
+function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, testAmbiguousType) {
+ var i;
+ var char;
+ var hasLineBreak = false;
+ var hasFoldableLine = false; // only checked if shouldTrackWidth
+ var shouldTrackWidth = lineWidth !== -1;
+ var previousLineBreak = -1; // count the first line correctly
+ var plain = isPlainSafeFirst(string.charCodeAt(0))
+ && !isWhitespace(string.charCodeAt(string.length - 1));
+
+ if (singleLineOnly) {
+ // Case: no block styles.
+ // Check for disallowed characters to rule out plain and single.
+ for (i = 0; i < string.length; i++) {
+ char = string.charCodeAt(i);
+ if (!isPrintable(char)) {
+ return STYLE_DOUBLE;
+ }
+ plain = plain && isPlainSafe(char);
+ }
+ } else {
+ // Case: block styles permitted.
+ for (i = 0; i < string.length; i++) {
+ char = string.charCodeAt(i);
+ if (char === CHAR_LINE_FEED) {
+ hasLineBreak = true;
+ // Check if any line can be folded.
+ if (shouldTrackWidth) {
+ hasFoldableLine = hasFoldableLine ||
+ // Foldable line = too long, and not more-indented.
+ (i - previousLineBreak - 1 > lineWidth &&
+ string[previousLineBreak + 1] !== ' ');
+ previousLineBreak = i;
+ }
+ } else if (!isPrintable(char)) {
+ return STYLE_DOUBLE;
+ }
+ plain = plain && isPlainSafe(char);
+ }
+ // in case the end is missing a \n
+ hasFoldableLine = hasFoldableLine || (shouldTrackWidth &&
+ (i - previousLineBreak - 1 > lineWidth &&
+ string[previousLineBreak + 1] !== ' '));
+ }
+ // Although every style can represent \n without escaping, prefer block styles
+ // for multiline, since they're more readable and they don't add empty lines.
+ // Also prefer folding a super-long line.
+ if (!hasLineBreak && !hasFoldableLine) {
+ // Strings interpretable as another type have to be quoted;
+ // e.g. the string 'true' vs. the boolean true.
+ return plain && !testAmbiguousType(string)
+ ? STYLE_PLAIN : STYLE_SINGLE;
+ }
+ // Edge case: block indentation indicator can only have one digit.
+ if (indentPerLevel > 9 && needIndentIndicator(string)) {
+ return STYLE_DOUBLE;
+ }
+ // At this point we know block styles are valid.
+ // Prefer literal style unless we want to fold.
+ return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL;
+}
+
+// Note: line breaking/folding is implemented for only the folded style.
+// NB. We drop the last trailing newline (if any) of a returned block scalar
+// since the dumper adds its own newline. This always works:
+// • No ending newline => unaffected; already using strip "-" chomping.
+// • Ending newline => removed then restored.
+// Importantly, this keeps the "+" chomp indicator from gaining an extra line.
+function writeScalar(state, string, level, iskey) {
+ state.dump = (function () {
+ if (string.length === 0) {
+ return "''";
+ }
+ if (!state.noCompatMode &&
+ DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1) {
+ return "'" + string + "'";
+ }
+
+ var indent = state.indent * Math.max(1, level); // no 0-indent scalars
+ // As indentation gets deeper, let the width decrease monotonically
+ // to the lower bound min(state.lineWidth, 40).
+ // Note that this implies
+ // state.lineWidth ≤ 40 + state.indent: width is fixed at the lower bound.
+ // state.lineWidth > 40 + state.indent: width decreases until the lower bound.
+ // This behaves better than a constant minimum width which disallows narrower options,
+ // or an indent threshold which causes the width to suddenly increase.
+ var lineWidth = state.lineWidth === -1
+ ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent);
+
+ // Without knowing if keys are implicit/explicit, assume implicit for safety.
+ var singleLineOnly = iskey
+ // No block styles in flow mode.
+ || (state.flowLevel > -1 && level >= state.flowLevel);
+ function testAmbiguity(string) {
+ return testImplicitResolving(state, string);
+ }
+
+ switch (chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, testAmbiguity)) {
+ case STYLE_PLAIN:
+ return string;
+ case STYLE_SINGLE:
+ return "'" + string.replace(/'/g, "''") + "'";
+ case STYLE_LITERAL:
+ return '|' + blockHeader(string, state.indent)
+ + dropEndingNewline(indentString(string, indent));
+ case STYLE_FOLDED:
+ return '>' + blockHeader(string, state.indent)
+ + dropEndingNewline(indentString(foldString(string, lineWidth), indent));
+ case STYLE_DOUBLE:
+ return '"' + escapeString(string, lineWidth) + '"';
+ default:
+ throw new YAMLException('impossible error: invalid scalar style');
+ }
+ }());
+}
+
+// Pre-conditions: string is valid for a block scalar, 1 <= indentPerLevel <= 9.
+function blockHeader(string, indentPerLevel) {
+ var indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : '';
+
+ // note the special case: the string '\n' counts as a "trailing" empty line.
+ var clip = string[string.length - 1] === '\n';
+ var keep = clip && (string[string.length - 2] === '\n' || string === '\n');
+ var chomp = keep ? '+' : (clip ? '' : '-');
+
+ return indentIndicator + chomp + '\n';
+}
+
+// (See the note for writeScalar.)
+function dropEndingNewline(string) {
+ return string[string.length - 1] === '\n' ? string.slice(0, -1) : string;
+}
+
+// Note: a long line without a suitable break point will exceed the width limit.
+// Pre-conditions: every char in str isPrintable, str.length > 0, width > 0.
+function foldString(string, width) {
+ // In folded style, $k$ consecutive newlines output as $k+1$ newlines—
+ // unless they're before or after a more-indented line, or at the very
+ // beginning or end, in which case $k$ maps to $k$.
+ // Therefore, parse each chunk as newline(s) followed by a content line.
+ var lineRe = /(\n+)([^\n]*)/g;
+
+ // first line (possibly an empty line)
+ var result = (function () {
+ var nextLF = string.indexOf('\n');
+ nextLF = nextLF !== -1 ? nextLF : string.length;
+ lineRe.lastIndex = nextLF;
+ return foldLine(string.slice(0, nextLF), width);
+ }());
+ // If we haven't reached the first content line yet, don't add an extra \n.
+ var prevMoreIndented = string[0] === '\n' || string[0] === ' ';
+ var moreIndented;
+
+ // rest of the lines
+ var match;
+ while ((match = lineRe.exec(string))) {
+ var prefix = match[1], line = match[2];
+ moreIndented = (line[0] === ' ');
+ result += prefix
+ + (!prevMoreIndented && !moreIndented && line !== ''
+ ? '\n' : '')
+ + foldLine(line, width);
+ prevMoreIndented = moreIndented;
+ }
+
+ return result;
+}
+
+// Greedy line breaking.
+// Picks the longest line under the limit each time,
+// otherwise settles for the shortest line over the limit.
+// NB. More-indented lines *cannot* be folded, as that would add an extra \n.
+function foldLine(line, width) {
+ if (line === '' || line[0] === ' ') return line;
+
+ // Since a more-indented line adds a \n, breaks can't be followed by a space.
+ var breakRe = / [^ ]/g; // note: the match index will always be <= length-2.
+ var match;
+ // start is an inclusive index. end, curr, and next are exclusive.
+ var start = 0, end, curr = 0, next = 0;
+ var result = '';
+
+ // Invariants: 0 <= start <= length-1.
+ // 0 <= curr <= next <= max(0, length-2). curr - start <= width.
+ // Inside the loop:
+ // A match implies length >= 2, so curr and next are <= length-2.
+ while ((match = breakRe.exec(line))) {
+ next = match.index;
+ // maintain invariant: curr - start <= width
+ if (next - start > width) {
+ end = (curr > start) ? curr : next; // derive end <= length-2
+ result += '\n' + line.slice(start, end);
+ // skip the space that was output as \n
+ start = end + 1; // derive start <= length-1
+ }
+ curr = next;
+ }
+
+ // By the invariants, start <= length-1, so there is something left over.
+ // It is either the whole string or a part starting from non-whitespace.
+ result += '\n';
+ // Insert a break if the remainder is too long and there is a break available.
+ if (line.length - start > width && curr > start) {
+ result += line.slice(start, curr) + '\n' + line.slice(curr + 1);
+ } else {
+ result += line.slice(start);
+ }
+
+ return result.slice(1); // drop extra \n joiner
+}
+
+// Escapes a double-quoted string.
+function escapeString(string) {
+ var result = '';
+ var char, nextChar;
+ var escapeSeq;
+
+ for (var i = 0; i < string.length; i++) {
+ char = string.charCodeAt(i);
+ // Check for surrogate pairs (reference Unicode 3.0 section "3.7 Surrogates").
+ if (char >= 0xD800 && char <= 0xDBFF/* high surrogate */) {
+ nextChar = string.charCodeAt(i + 1);
+ if (nextChar >= 0xDC00 && nextChar <= 0xDFFF/* low surrogate */) {
+ // Combine the surrogate pair and store it escaped.
+ result += encodeHex((char - 0xD800) * 0x400 + nextChar - 0xDC00 + 0x10000);
+ // Advance index one extra since we already used that char here.
+ i++; continue;
+ }
+ }
+ escapeSeq = ESCAPE_SEQUENCES[char];
+ result += !escapeSeq && isPrintable(char)
+ ? string[i]
+ : escapeSeq || encodeHex(char);
+ }
+
+ return result;
+}
+
+function writeFlowSequence(state, level, object) {
+ var _result = '',
+ _tag = state.tag,
+ index,
+ length;
+
+ for (index = 0, length = object.length; index < length; index += 1) {
+ // Write only valid elements.
+ if (writeNode(state, level, object[index], false, false)) {
+ if (index !== 0) _result += ',' + (!state.condenseFlow ? ' ' : '');
+ _result += state.dump;
+ }
+ }
+
+ state.tag = _tag;
+ state.dump = '[' + _result + ']';
+}
+
+function writeBlockSequence(state, level, object, compact) {
+ var _result = '',
+ _tag = state.tag,
+ index,
+ length;
+
+ for (index = 0, length = object.length; index < length; index += 1) {
+ // Write only valid elements.
+ if (writeNode(state, level + 1, object[index], true, true)) {
+ if (!compact || index !== 0) {
+ _result += generateNextLine(state, level);
+ }
+
+ if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
+ _result += '-';
+ } else {
+ _result += '- ';
+ }
+
+ _result += state.dump;
+ }
+ }
+
+ state.tag = _tag;
+ state.dump = _result || '[]'; // Empty sequence if no valid values.
+}
+
+function writeFlowMapping(state, level, object) {
+ var _result = '',
+ _tag = state.tag,
+ objectKeyList = Object.keys(object),
+ index,
+ length,
+ objectKey,
+ objectValue,
+ pairBuffer;
+
+ for (index = 0, length = objectKeyList.length; index < length; index += 1) {
+ pairBuffer = state.condenseFlow ? '"' : '';
+
+ if (index !== 0) pairBuffer += ', ';
+
+ objectKey = objectKeyList[index];
+ objectValue = object[objectKey];
+
+ if (!writeNode(state, level, objectKey, false, false)) {
+ continue; // Skip this pair because of invalid key;
+ }
+
+ if (state.dump.length > 1024) pairBuffer += '? ';
+
+ pairBuffer += state.dump + (state.condenseFlow ? '"' : '') + ':' + (state.condenseFlow ? '' : ' ');
+
+ if (!writeNode(state, level, objectValue, false, false)) {
+ continue; // Skip this pair because of invalid value.
+ }
+
+ pairBuffer += state.dump;
+
+ // Both key and value are valid.
+ _result += pairBuffer;
+ }
+
+ state.tag = _tag;
+ state.dump = '{' + _result + '}';
+}
+
+function writeBlockMapping(state, level, object, compact) {
+ var _result = '',
+ _tag = state.tag,
+ objectKeyList = Object.keys(object),
+ index,
+ length,
+ objectKey,
+ objectValue,
+ explicitPair,
+ pairBuffer;
+
+ // Allow sorting keys so that the output file is deterministic
+ if (state.sortKeys === true) {
+ // Default sorting
+ objectKeyList.sort();
+ } else if (typeof state.sortKeys === 'function') {
+ // Custom sort function
+ objectKeyList.sort(state.sortKeys);
+ } else if (state.sortKeys) {
+ // Something is wrong
+ throw new YAMLException('sortKeys must be a boolean or a function');
+ }
+
+ for (index = 0, length = objectKeyList.length; index < length; index += 1) {
+ pairBuffer = '';
+
+ if (!compact || index !== 0) {
+ pairBuffer += generateNextLine(state, level);
+ }
+
+ objectKey = objectKeyList[index];
+ objectValue = object[objectKey];
+
+ if (!writeNode(state, level + 1, objectKey, true, true, true)) {
+ continue; // Skip this pair because of invalid key.
+ }
+
+ explicitPair = (state.tag !== null && state.tag !== '?') ||
+ (state.dump && state.dump.length > 1024);
+
+ if (explicitPair) {
+ if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
+ pairBuffer += '?';
+ } else {
+ pairBuffer += '? ';
+ }
+ }
+
+ pairBuffer += state.dump;
+
+ if (explicitPair) {
+ pairBuffer += generateNextLine(state, level);
+ }
+
+ if (!writeNode(state, level + 1, objectValue, true, explicitPair)) {
+ continue; // Skip this pair because of invalid value.
+ }
+
+ if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
+ pairBuffer += ':';
+ } else {
+ pairBuffer += ': ';
+ }
+
+ pairBuffer += state.dump;
+
+ // Both key and value are valid.
+ _result += pairBuffer;
+ }
+
+ state.tag = _tag;
+ state.dump = _result || '{}'; // Empty mapping if no valid pairs.
+}
+
+function detectType(state, object, explicit) {
+ var _result, typeList, index, length, type, style;
+
+ typeList = explicit ? state.explicitTypes : state.implicitTypes;
+
+ for (index = 0, length = typeList.length; index < length; index += 1) {
+ type = typeList[index];
+
+ if ((type.instanceOf || type.predicate) &&
+ (!type.instanceOf || ((typeof object === 'object') && (object instanceof type.instanceOf))) &&
+ (!type.predicate || type.predicate(object))) {
+
+ state.tag = explicit ? type.tag : '?';
+
+ if (type.represent) {
+ style = state.styleMap[type.tag] || type.defaultStyle;
+
+ if (_toString.call(type.represent) === '[object Function]') {
+ _result = type.represent(object, style);
+ } else if (_hasOwnProperty.call(type.represent, style)) {
+ _result = type.represent[style](object, style);
+ } else {
+ throw new YAMLException('!<' + type.tag + '> tag resolver accepts not "' + style + '" style');
+ }
+
+ state.dump = _result;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Serializes `object` and writes it to global `result`.
+// Returns true on success, or false on invalid object.
+//
+function writeNode(state, level, object, block, compact, iskey) {
+ state.tag = null;
+ state.dump = object;
+
+ if (!detectType(state, object, false)) {
+ detectType(state, object, true);
+ }
+
+ var type = _toString.call(state.dump);
+
+ if (block) {
+ block = (state.flowLevel < 0 || state.flowLevel > level);
+ }
+
+ var objectOrArray = type === '[object Object]' || type === '[object Array]',
+ duplicateIndex,
+ duplicate;
+
+ if (objectOrArray) {
+ duplicateIndex = state.duplicates.indexOf(object);
+ duplicate = duplicateIndex !== -1;
+ }
+
+ if ((state.tag !== null && state.tag !== '?') || duplicate || (state.indent !== 2 && level > 0)) {
+ compact = false;
+ }
+
+ if (duplicate && state.usedDuplicates[duplicateIndex]) {
+ state.dump = '*ref_' + duplicateIndex;
+ } else {
+ if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) {
+ state.usedDuplicates[duplicateIndex] = true;
+ }
+ if (type === '[object Object]') {
+ if (block && (Object.keys(state.dump).length !== 0)) {
+ writeBlockMapping(state, level, state.dump, compact);
+ if (duplicate) {
+ state.dump = '&ref_' + duplicateIndex + state.dump;
+ }
+ } else {
+ writeFlowMapping(state, level, state.dump);
+ if (duplicate) {
+ state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
+ }
+ }
+ } else if (type === '[object Array]') {
+ var arrayLevel = (state.noArrayIndent) ? level - 1 : level;
+ if (block && (state.dump.length !== 0)) {
+ writeBlockSequence(state, arrayLevel, state.dump, compact);
+ if (duplicate) {
+ state.dump = '&ref_' + duplicateIndex + state.dump;
+ }
+ } else {
+ writeFlowSequence(state, arrayLevel, state.dump);
+ if (duplicate) {
+ state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
+ }
+ }
+ } else if (type === '[object String]') {
+ if (state.tag !== '?') {
+ writeScalar(state, state.dump, level, iskey);
+ }
+ } else {
+ if (state.skipInvalid) return false;
+ throw new YAMLException('unacceptable kind of an object to dump ' + type);
+ }
+
+ if (state.tag !== null && state.tag !== '?') {
+ state.dump = '!<' + state.tag + '> ' + state.dump;
+ }
+ }
+
+ return true;
+}
+
+function getDuplicateReferences(object, state) {
+ var objects = [],
+ duplicatesIndexes = [],
+ index,
+ length;
+
+ inspectNode(object, objects, duplicatesIndexes);
+
+ for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) {
+ state.duplicates.push(objects[duplicatesIndexes[index]]);
+ }
+ state.usedDuplicates = new Array(length);
+}
+
+function inspectNode(object, objects, duplicatesIndexes) {
+ var objectKeyList,
+ index,
+ length;
+
+ if (object !== null && typeof object === 'object') {
+ index = objects.indexOf(object);
+ if (index !== -1) {
+ if (duplicatesIndexes.indexOf(index) === -1) {
+ duplicatesIndexes.push(index);
+ }
+ } else {
+ objects.push(object);
+
+ if (Array.isArray(object)) {
+ for (index = 0, length = object.length; index < length; index += 1) {
+ inspectNode(object[index], objects, duplicatesIndexes);
+ }
+ } else {
+ objectKeyList = Object.keys(object);
+
+ for (index = 0, length = objectKeyList.length; index < length; index += 1) {
+ inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes);
+ }
+ }
+ }
+ }
+}
+
+function dump(input, options) {
+ options = options || {};
+
+ var state = new State(options);
+
+ if (!state.noRefs) getDuplicateReferences(input, state);
+
+ if (writeNode(state, 0, input, true, true)) return state.dump + '\n';
+
+ return '';
+}
+
+function safeDump(input, options) {
+ return dump(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));
+}
+
+module.exports.dump = dump;
+module.exports.safeDump = safeDump;
+
+},{"./common":2,"./exception":4,"./schema/default_full":9,"./schema/default_safe":10}],4:[function(require,module,exports){
+// YAML error class. http://stackoverflow.com/questions/8458984
+//
+'use strict';
+
+function YAMLException(reason, mark) {
+ // Super constructor
+ Error.call(this);
+
+ this.name = 'YAMLException';
+ this.reason = reason;
+ this.mark = mark;
+ this.message = (this.reason || '(unknown reason)') + (this.mark ? ' ' + this.mark.toString() : '');
+
+ // Include stack trace in error object
+ if (Error.captureStackTrace) {
+ // Chrome and NodeJS
+ Error.captureStackTrace(this, this.constructor);
+ } else {
+ // FF, IE 10+ and Safari 6+. Fallback for others
+ this.stack = (new Error()).stack || '';
+ }
+}
+
+
+// Inherit from Error
+YAMLException.prototype = Object.create(Error.prototype);
+YAMLException.prototype.constructor = YAMLException;
+
+
+YAMLException.prototype.toString = function toString(compact) {
+ var result = this.name + ': ';
+
+ result += this.reason || '(unknown reason)';
+
+ if (!compact && this.mark) {
+ result += ' ' + this.mark.toString();
+ }
+
+ return result;
+};
+
+
+module.exports = YAMLException;
+
+},{}],5:[function(require,module,exports){
+'use strict';
+
+/*eslint-disable max-len,no-use-before-define*/
+
+var common = require('./common');
+var YAMLException = require('./exception');
+var Mark = require('./mark');
+var DEFAULT_SAFE_SCHEMA = require('./schema/default_safe');
+var DEFAULT_FULL_SCHEMA = require('./schema/default_full');
+
+
+var _hasOwnProperty = Object.prototype.hasOwnProperty;
+
+
+var CONTEXT_FLOW_IN = 1;
+var CONTEXT_FLOW_OUT = 2;
+var CONTEXT_BLOCK_IN = 3;
+var CONTEXT_BLOCK_OUT = 4;
+
+
+var CHOMPING_CLIP = 1;
+var CHOMPING_STRIP = 2;
+var CHOMPING_KEEP = 3;
+
+
+var PATTERN_NON_PRINTABLE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/;
+var PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/;
+var PATTERN_FLOW_INDICATORS = /[,\[\]\{\}]/;
+var PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i;
+var PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;
+
+
+function is_EOL(c) {
+ return (c === 0x0A/* LF */) || (c === 0x0D/* CR */);
+}
+
+function is_WHITE_SPACE(c) {
+ return (c === 0x09/* Tab */) || (c === 0x20/* Space */);
+}
+
+function is_WS_OR_EOL(c) {
+ return (c === 0x09/* Tab */) ||
+ (c === 0x20/* Space */) ||
+ (c === 0x0A/* LF */) ||
+ (c === 0x0D/* CR */);
+}
+
+function is_FLOW_INDICATOR(c) {
+ return c === 0x2C/* , */ ||
+ c === 0x5B/* [ */ ||
+ c === 0x5D/* ] */ ||
+ c === 0x7B/* { */ ||
+ c === 0x7D/* } */;
+}
+
+function fromHexCode(c) {
+ var lc;
+
+ if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) {
+ return c - 0x30;
+ }
+
+ /*eslint-disable no-bitwise*/
+ lc = c | 0x20;
+
+ if ((0x61/* a */ <= lc) && (lc <= 0x66/* f */)) {
+ return lc - 0x61 + 10;
+ }
+
+ return -1;
+}
+
+function escapedHexLen(c) {
+ if (c === 0x78/* x */) { return 2; }
+ if (c === 0x75/* u */) { return 4; }
+ if (c === 0x55/* U */) { return 8; }
+ return 0;
+}
+
+function fromDecimalCode(c) {
+ if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) {
+ return c - 0x30;
+ }
+
+ return -1;
+}
+
+function simpleEscapeSequence(c) {
+ /* eslint-disable indent */
+ return (c === 0x30/* 0 */) ? '\x00' :
+ (c === 0x61/* a */) ? '\x07' :
+ (c === 0x62/* b */) ? '\x08' :
+ (c === 0x74/* t */) ? '\x09' :
+ (c === 0x09/* Tab */) ? '\x09' :
+ (c === 0x6E/* n */) ? '\x0A' :
+ (c === 0x76/* v */) ? '\x0B' :
+ (c === 0x66/* f */) ? '\x0C' :
+ (c === 0x72/* r */) ? '\x0D' :
+ (c === 0x65/* e */) ? '\x1B' :
+ (c === 0x20/* Space */) ? ' ' :
+ (c === 0x22/* " */) ? '\x22' :
+ (c === 0x2F/* / */) ? '/' :
+ (c === 0x5C/* \ */) ? '\x5C' :
+ (c === 0x4E/* N */) ? '\x85' :
+ (c === 0x5F/* _ */) ? '\xA0' :
+ (c === 0x4C/* L */) ? '\u2028' :
+ (c === 0x50/* P */) ? '\u2029' : '';
+}
+
+function charFromCodepoint(c) {
+ if (c <= 0xFFFF) {
+ return String.fromCharCode(c);
+ }
+ // Encode UTF-16 surrogate pair
+ // https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B010000_to_U.2B10FFFF
+ return String.fromCharCode(
+ ((c - 0x010000) >> 10) + 0xD800,
+ ((c - 0x010000) & 0x03FF) + 0xDC00
+ );
+}
+
+var simpleEscapeCheck = new Array(256); // integer, for fast access
+var simpleEscapeMap = new Array(256);
+for (var i = 0; i < 256; i++) {
+ simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0;
+ simpleEscapeMap[i] = simpleEscapeSequence(i);
+}
+
+
+function State(input, options) {
+ this.input = input;
+
+ this.filename = options['filename'] || null;
+ this.schema = options['schema'] || DEFAULT_FULL_SCHEMA;
+ this.onWarning = options['onWarning'] || null;
+ this.legacy = options['legacy'] || false;
+ this.json = options['json'] || false;
+ this.listener = options['listener'] || null;
+
+ this.implicitTypes = this.schema.compiledImplicit;
+ this.typeMap = this.schema.compiledTypeMap;
+
+ this.length = input.length;
+ this.position = 0;
+ this.line = 0;
+ this.lineStart = 0;
+ this.lineIndent = 0;
+
+ this.documents = [];
+
+ /*
+ this.version;
+ this.checkLineBreaks;
+ this.tagMap;
+ this.anchorMap;
+ this.tag;
+ this.anchor;
+ this.kind;
+ this.result;*/
+
+}
+
+
+function generateError(state, message) {
+ return new YAMLException(
+ message,
+ new Mark(state.filename, state.input, state.position, state.line, (state.position - state.lineStart)));
+}
+
+function throwError(state, message) {
+ throw generateError(state, message);
+}
+
+function throwWarning(state, message) {
+ if (state.onWarning) {
+ state.onWarning.call(null, generateError(state, message));
+ }
+}
+
+
+var directiveHandlers = {
+
+ YAML: function handleYamlDirective(state, name, args) {
+
+ var match, major, minor;
+
+ if (state.version !== null) {
+ throwError(state, 'duplication of %YAML directive');
+ }
+
+ if (args.length !== 1) {
+ throwError(state, 'YAML directive accepts exactly one argument');
+ }
+
+ match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]);
+
+ if (match === null) {
+ throwError(state, 'ill-formed argument of the YAML directive');
+ }
+
+ major = parseInt(match[1], 10);
+ minor = parseInt(match[2], 10);
+
+ if (major !== 1) {
+ throwError(state, 'unacceptable YAML version of the document');
+ }
+
+ state.version = args[0];
+ state.checkLineBreaks = (minor < 2);
+
+ if (minor !== 1 && minor !== 2) {
+ throwWarning(state, 'unsupported YAML version of the document');
+ }
+ },
+
+ TAG: function handleTagDirective(state, name, args) {
+
+ var handle, prefix;
+
+ if (args.length !== 2) {
+ throwError(state, 'TAG directive accepts exactly two arguments');
+ }
+
+ handle = args[0];
+ prefix = args[1];
+
+ if (!PATTERN_TAG_HANDLE.test(handle)) {
+ throwError(state, 'ill-formed tag handle (first argument) of the TAG directive');
+ }
+
+ if (_hasOwnProperty.call(state.tagMap, handle)) {
+ throwError(state, 'there is a previously declared suffix for "' + handle + '" tag handle');
+ }
+
+ if (!PATTERN_TAG_URI.test(prefix)) {
+ throwError(state, 'ill-formed tag prefix (second argument) of the TAG directive');
+ }
+
+ state.tagMap[handle] = prefix;
+ }
+};
+
+
+function captureSegment(state, start, end, checkJson) {
+ var _position, _length, _character, _result;
+
+ if (start < end) {
+ _result = state.input.slice(start, end);
+
+ if (checkJson) {
+ for (_position = 0, _length = _result.length; _position < _length; _position += 1) {
+ _character = _result.charCodeAt(_position);
+ if (!(_character === 0x09 ||
+ (0x20 <= _character && _character <= 0x10FFFF))) {
+ throwError(state, 'expected valid JSON character');
+ }
+ }
+ } else if (PATTERN_NON_PRINTABLE.test(_result)) {
+ throwError(state, 'the stream contains non-printable characters');
+ }
+
+ state.result += _result;
+ }
+}
+
+function mergeMappings(state, destination, source, overridableKeys) {
+ var sourceKeys, key, index, quantity;
+
+ if (!common.isObject(source)) {
+ throwError(state, 'cannot merge mappings; the provided source object is unacceptable');
+ }
+
+ sourceKeys = Object.keys(source);
+
+ for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
+ key = sourceKeys[index];
+
+ if (!_hasOwnProperty.call(destination, key)) {
+ destination[key] = source[key];
+ overridableKeys[key] = true;
+ }
+ }
+}
+
+function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, startLine, startPos) {
+ var index, quantity;
+
+ keyNode = String(keyNode);
+
+ if (_result === null) {
+ _result = {};
+ }
+
+ if (keyTag === 'tag:yaml.org,2002:merge') {
+ if (Array.isArray(valueNode)) {
+ for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
+ mergeMappings(state, _result, valueNode[index], overridableKeys);
+ }
+ } else {
+ mergeMappings(state, _result, valueNode, overridableKeys);
+ }
+ } else {
+ if (!state.json &&
+ !_hasOwnProperty.call(overridableKeys, keyNode) &&
+ _hasOwnProperty.call(_result, keyNode)) {
+ state.line = startLine || state.line;
+ state.position = startPos || state.position;
+ throwError(state, 'duplicated mapping key');
+ }
+ _result[keyNode] = valueNode;
+ delete overridableKeys[keyNode];
+ }
+
+ return _result;
+}
+
+function readLineBreak(state) {
+ var ch;
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch === 0x0A/* LF */) {
+ state.position++;
+ } else if (ch === 0x0D/* CR */) {
+ state.position++;
+ if (state.input.charCodeAt(state.position) === 0x0A/* LF */) {
+ state.position++;
+ }
+ } else {
+ throwError(state, 'a line break is expected');
+ }
+
+ state.line += 1;
+ state.lineStart = state.position;
+}
+
+function skipSeparationSpace(state, allowComments, checkIndent) {
+ var lineBreaks = 0,
+ ch = state.input.charCodeAt(state.position);
+
+ while (ch !== 0) {
+ while (is_WHITE_SPACE(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (allowComments && ch === 0x23/* # */) {
+ do {
+ ch = state.input.charCodeAt(++state.position);
+ } while (ch !== 0x0A/* LF */ && ch !== 0x0D/* CR */ && ch !== 0);
+ }
+
+ if (is_EOL(ch)) {
+ readLineBreak(state);
+
+ ch = state.input.charCodeAt(state.position);
+ lineBreaks++;
+ state.lineIndent = 0;
+
+ while (ch === 0x20/* Space */) {
+ state.lineIndent++;
+ ch = state.input.charCodeAt(++state.position);
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) {
+ throwWarning(state, 'deficient indentation');
+ }
+
+ return lineBreaks;
+}
+
+function testDocumentSeparator(state) {
+ var _position = state.position,
+ ch;
+
+ ch = state.input.charCodeAt(_position);
+
+ // Condition state.position === state.lineStart is tested
+ // in parent on each call, for efficiency. No needs to test here again.
+ if ((ch === 0x2D/* - */ || ch === 0x2E/* . */) &&
+ ch === state.input.charCodeAt(_position + 1) &&
+ ch === state.input.charCodeAt(_position + 2)) {
+
+ _position += 3;
+
+ ch = state.input.charCodeAt(_position);
+
+ if (ch === 0 || is_WS_OR_EOL(ch)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function writeFoldedLines(state, count) {
+ if (count === 1) {
+ state.result += ' ';
+ } else if (count > 1) {
+ state.result += common.repeat('\n', count - 1);
+ }
+}
+
+
+function readPlainScalar(state, nodeIndent, withinFlowCollection) {
+ var preceding,
+ following,
+ captureStart,
+ captureEnd,
+ hasPendingContent,
+ _line,
+ _lineStart,
+ _lineIndent,
+ _kind = state.kind,
+ _result = state.result,
+ ch;
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (is_WS_OR_EOL(ch) ||
+ is_FLOW_INDICATOR(ch) ||
+ ch === 0x23/* # */ ||
+ ch === 0x26/* & */ ||
+ ch === 0x2A/* * */ ||
+ ch === 0x21/* ! */ ||
+ ch === 0x7C/* | */ ||
+ ch === 0x3E/* > */ ||
+ ch === 0x27/* ' */ ||
+ ch === 0x22/* " */ ||
+ ch === 0x25/* % */ ||
+ ch === 0x40/* @ */ ||
+ ch === 0x60/* ` */) {
+ return false;
+ }
+
+ if (ch === 0x3F/* ? */ || ch === 0x2D/* - */) {
+ following = state.input.charCodeAt(state.position + 1);
+
+ if (is_WS_OR_EOL(following) ||
+ withinFlowCollection && is_FLOW_INDICATOR(following)) {
+ return false;
+ }
+ }
+
+ state.kind = 'scalar';
+ state.result = '';
+ captureStart = captureEnd = state.position;
+ hasPendingContent = false;
+
+ while (ch !== 0) {
+ if (ch === 0x3A/* : */) {
+ following = state.input.charCodeAt(state.position + 1);
+
+ if (is_WS_OR_EOL(following) ||
+ withinFlowCollection && is_FLOW_INDICATOR(following)) {
+ break;
+ }
+
+ } else if (ch === 0x23/* # */) {
+ preceding = state.input.charCodeAt(state.position - 1);
+
+ if (is_WS_OR_EOL(preceding)) {
+ break;
+ }
+
+ } else if ((state.position === state.lineStart && testDocumentSeparator(state)) ||
+ withinFlowCollection && is_FLOW_INDICATOR(ch)) {
+ break;
+
+ } else if (is_EOL(ch)) {
+ _line = state.line;
+ _lineStart = state.lineStart;
+ _lineIndent = state.lineIndent;
+ skipSeparationSpace(state, false, -1);
+
+ if (state.lineIndent >= nodeIndent) {
+ hasPendingContent = true;
+ ch = state.input.charCodeAt(state.position);
+ continue;
+ } else {
+ state.position = captureEnd;
+ state.line = _line;
+ state.lineStart = _lineStart;
+ state.lineIndent = _lineIndent;
+ break;
+ }
+ }
+
+ if (hasPendingContent) {
+ captureSegment(state, captureStart, captureEnd, false);
+ writeFoldedLines(state, state.line - _line);
+ captureStart = captureEnd = state.position;
+ hasPendingContent = false;
+ }
+
+ if (!is_WHITE_SPACE(ch)) {
+ captureEnd = state.position + 1;
+ }
+
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ captureSegment(state, captureStart, captureEnd, false);
+
+ if (state.result) {
+ return true;
+ }
+
+ state.kind = _kind;
+ state.result = _result;
+ return false;
+}
+
+function readSingleQuotedScalar(state, nodeIndent) {
+ var ch,
+ captureStart, captureEnd;
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch !== 0x27/* ' */) {
+ return false;
+ }
+
+ state.kind = 'scalar';
+ state.result = '';
+ state.position++;
+ captureStart = captureEnd = state.position;
+
+ while ((ch = state.input.charCodeAt(state.position)) !== 0) {
+ if (ch === 0x27/* ' */) {
+ captureSegment(state, captureStart, state.position, true);
+ ch = state.input.charCodeAt(++state.position);
+
+ if (ch === 0x27/* ' */) {
+ captureStart = state.position;
+ state.position++;
+ captureEnd = state.position;
+ } else {
+ return true;
+ }
+
+ } else if (is_EOL(ch)) {
+ captureSegment(state, captureStart, captureEnd, true);
+ writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));
+ captureStart = captureEnd = state.position;
+
+ } else if (state.position === state.lineStart && testDocumentSeparator(state)) {
+ throwError(state, 'unexpected end of the document within a single quoted scalar');
+
+ } else {
+ state.position++;
+ captureEnd = state.position;
+ }
+ }
+
+ throwError(state, 'unexpected end of the stream within a single quoted scalar');
+}
+
+function readDoubleQuotedScalar(state, nodeIndent) {
+ var captureStart,
+ captureEnd,
+ hexLength,
+ hexResult,
+ tmp,
+ ch;
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch !== 0x22/* " */) {
+ return false;
+ }
+
+ state.kind = 'scalar';
+ state.result = '';
+ state.position++;
+ captureStart = captureEnd = state.position;
+
+ while ((ch = state.input.charCodeAt(state.position)) !== 0) {
+ if (ch === 0x22/* " */) {
+ captureSegment(state, captureStart, state.position, true);
+ state.position++;
+ return true;
+
+ } else if (ch === 0x5C/* \ */) {
+ captureSegment(state, captureStart, state.position, true);
+ ch = state.input.charCodeAt(++state.position);
+
+ if (is_EOL(ch)) {
+ skipSeparationSpace(state, false, nodeIndent);
+
+ // TODO: rework to inline fn with no type cast?
+ } else if (ch < 256 && simpleEscapeCheck[ch]) {
+ state.result += simpleEscapeMap[ch];
+ state.position++;
+
+ } else if ((tmp = escapedHexLen(ch)) > 0) {
+ hexLength = tmp;
+ hexResult = 0;
+
+ for (; hexLength > 0; hexLength--) {
+ ch = state.input.charCodeAt(++state.position);
+
+ if ((tmp = fromHexCode(ch)) >= 0) {
+ hexResult = (hexResult << 4) + tmp;
+
+ } else {
+ throwError(state, 'expected hexadecimal character');
+ }
+ }
+
+ state.result += charFromCodepoint(hexResult);
+
+ state.position++;
+
+ } else {
+ throwError(state, 'unknown escape sequence');
+ }
+
+ captureStart = captureEnd = state.position;
+
+ } else if (is_EOL(ch)) {
+ captureSegment(state, captureStart, captureEnd, true);
+ writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));
+ captureStart = captureEnd = state.position;
+
+ } else if (state.position === state.lineStart && testDocumentSeparator(state)) {
+ throwError(state, 'unexpected end of the document within a double quoted scalar');
+
+ } else {
+ state.position++;
+ captureEnd = state.position;
+ }
+ }
+
+ throwError(state, 'unexpected end of the stream within a double quoted scalar');
+}
+
+function readFlowCollection(state, nodeIndent) {
+ var readNext = true,
+ _line,
+ _tag = state.tag,
+ _result,
+ _anchor = state.anchor,
+ following,
+ terminator,
+ isPair,
+ isExplicitPair,
+ isMapping,
+ overridableKeys = {},
+ keyNode,
+ keyTag,
+ valueNode,
+ ch;
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch === 0x5B/* [ */) {
+ terminator = 0x5D;/* ] */
+ isMapping = false;
+ _result = [];
+ } else if (ch === 0x7B/* { */) {
+ terminator = 0x7D;/* } */
+ isMapping = true;
+ _result = {};
+ } else {
+ return false;
+ }
+
+ if (state.anchor !== null) {
+ state.anchorMap[state.anchor] = _result;
+ }
+
+ ch = state.input.charCodeAt(++state.position);
+
+ while (ch !== 0) {
+ skipSeparationSpace(state, true, nodeIndent);
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch === terminator) {
+ state.position++;
+ state.tag = _tag;
+ state.anchor = _anchor;
+ state.kind = isMapping ? 'mapping' : 'sequence';
+ state.result = _result;
+ return true;
+ } else if (!readNext) {
+ throwError(state, 'missed comma between flow collection entries');
+ }
+
+ keyTag = keyNode = valueNode = null;
+ isPair = isExplicitPair = false;
+
+ if (ch === 0x3F/* ? */) {
+ following = state.input.charCodeAt(state.position + 1);
+
+ if (is_WS_OR_EOL(following)) {
+ isPair = isExplicitPair = true;
+ state.position++;
+ skipSeparationSpace(state, true, nodeIndent);
+ }
+ }
+
+ _line = state.line;
+ composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);
+ keyTag = state.tag;
+ keyNode = state.result;
+ skipSeparationSpace(state, true, nodeIndent);
+
+ ch = state.input.charCodeAt(state.position);
+
+ if ((isExplicitPair || state.line === _line) && ch === 0x3A/* : */) {
+ isPair = true;
+ ch = state.input.charCodeAt(++state.position);
+ skipSeparationSpace(state, true, nodeIndent);
+ composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);
+ valueNode = state.result;
+ }
+
+ if (isMapping) {
+ storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode);
+ } else if (isPair) {
+ _result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode));
+ } else {
+ _result.push(keyNode);
+ }
+
+ skipSeparationSpace(state, true, nodeIndent);
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch === 0x2C/* , */) {
+ readNext = true;
+ ch = state.input.charCodeAt(++state.position);
+ } else {
+ readNext = false;
+ }
+ }
+
+ throwError(state, 'unexpected end of the stream within a flow collection');
+}
+
+function readBlockScalar(state, nodeIndent) {
+ var captureStart,
+ folding,
+ chomping = CHOMPING_CLIP,
+ didReadContent = false,
+ detectedIndent = false,
+ textIndent = nodeIndent,
+ emptyLines = 0,
+ atMoreIndented = false,
+ tmp,
+ ch;
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch === 0x7C/* | */) {
+ folding = false;
+ } else if (ch === 0x3E/* > */) {
+ folding = true;
+ } else {
+ return false;
+ }
+
+ state.kind = 'scalar';
+ state.result = '';
+
+ while (ch !== 0) {
+ ch = state.input.charCodeAt(++state.position);
+
+ if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
+ if (CHOMPING_CLIP === chomping) {
+ chomping = (ch === 0x2B/* + */) ? CHOMPING_KEEP : CHOMPING_STRIP;
+ } else {
+ throwError(state, 'repeat of a chomping mode identifier');
+ }
+
+ } else if ((tmp = fromDecimalCode(ch)) >= 0) {
+ if (tmp === 0) {
+ throwError(state, 'bad explicit indentation width of a block scalar; it cannot be less than one');
+ } else if (!detectedIndent) {
+ textIndent = nodeIndent + tmp - 1;
+ detectedIndent = true;
+ } else {
+ throwError(state, 'repeat of an indentation width identifier');
+ }
+
+ } else {
+ break;
+ }
+ }
+
+ if (is_WHITE_SPACE(ch)) {
+ do { ch = state.input.charCodeAt(++state.position); }
+ while (is_WHITE_SPACE(ch));
+
+ if (ch === 0x23/* # */) {
+ do { ch = state.input.charCodeAt(++state.position); }
+ while (!is_EOL(ch) && (ch !== 0));
+ }
+ }
+
+ while (ch !== 0) {
+ readLineBreak(state);
+ state.lineIndent = 0;
+
+ ch = state.input.charCodeAt(state.position);
+
+ while ((!detectedIndent || state.lineIndent < textIndent) &&
+ (ch === 0x20/* Space */)) {
+ state.lineIndent++;
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (!detectedIndent && state.lineIndent > textIndent) {
+ textIndent = state.lineIndent;
+ }
+
+ if (is_EOL(ch)) {
+ emptyLines++;
+ continue;
+ }
+
+ // End of the scalar.
+ if (state.lineIndent < textIndent) {
+
+ // Perform the chomping.
+ if (chomping === CHOMPING_KEEP) {
+ state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines);
+ } else if (chomping === CHOMPING_CLIP) {
+ if (didReadContent) { // i.e. only if the scalar is not empty.
+ state.result += '\n';
+ }
+ }
+
+ // Break this `while` cycle and go to the funciton's epilogue.
+ break;
+ }
+
+ // Folded style: use fancy rules to handle line breaks.
+ if (folding) {
+
+ // Lines starting with white space characters (more-indented lines) are not folded.
+ if (is_WHITE_SPACE(ch)) {
+ atMoreIndented = true;
+ // except for the first content line (cf. Example 8.1)
+ state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines);
+
+ // End of more-indented block.
+ } else if (atMoreIndented) {
+ atMoreIndented = false;
+ state.result += common.repeat('\n', emptyLines + 1);
+
+ // Just one line break - perceive as the same line.
+ } else if (emptyLines === 0) {
+ if (didReadContent) { // i.e. only if we have already read some scalar content.
+ state.result += ' ';
+ }
+
+ // Several line breaks - perceive as different lines.
+ } else {
+ state.result += common.repeat('\n', emptyLines);
+ }
+
+ // Literal style: just add exact number of line breaks between content lines.
+ } else {
+ // Keep all line breaks except the header line break.
+ state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines);
+ }
+
+ didReadContent = true;
+ detectedIndent = true;
+ emptyLines = 0;
+ captureStart = state.position;
+
+ while (!is_EOL(ch) && (ch !== 0)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ captureSegment(state, captureStart, state.position, false);
+ }
+
+ return true;
+}
+
+function readBlockSequence(state, nodeIndent) {
+ var _line,
+ _tag = state.tag,
+ _anchor = state.anchor,
+ _result = [],
+ following,
+ detected = false,
+ ch;
+
+ if (state.anchor !== null) {
+ state.anchorMap[state.anchor] = _result;
+ }
+
+ ch = state.input.charCodeAt(state.position);
+
+ while (ch !== 0) {
+
+ if (ch !== 0x2D/* - */) {
+ break;
+ }
+
+ following = state.input.charCodeAt(state.position + 1);
+
+ if (!is_WS_OR_EOL(following)) {
+ break;
+ }
+
+ detected = true;
+ state.position++;
+
+ if (skipSeparationSpace(state, true, -1)) {
+ if (state.lineIndent <= nodeIndent) {
+ _result.push(null);
+ ch = state.input.charCodeAt(state.position);
+ continue;
+ }
+ }
+
+ _line = state.line;
+ composeNode(state, nodeIndent, CONTEXT_BLOCK_IN, false, true);
+ _result.push(state.result);
+ skipSeparationSpace(state, true, -1);
+
+ ch = state.input.charCodeAt(state.position);
+
+ if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) {
+ throwError(state, 'bad indentation of a sequence entry');
+ } else if (state.lineIndent < nodeIndent) {
+ break;
+ }
+ }
+
+ if (detected) {
+ state.tag = _tag;
+ state.anchor = _anchor;
+ state.kind = 'sequence';
+ state.result = _result;
+ return true;
+ }
+ return false;
+}
+
+function readBlockMapping(state, nodeIndent, flowIndent) {
+ var following,
+ allowCompact,
+ _line,
+ _pos,
+ _tag = state.tag,
+ _anchor = state.anchor,
+ _result = {},
+ overridableKeys = {},
+ keyTag = null,
+ keyNode = null,
+ valueNode = null,
+ atExplicitKey = false,
+ detected = false,
+ ch;
+
+ if (state.anchor !== null) {
+ state.anchorMap[state.anchor] = _result;
+ }
+
+ ch = state.input.charCodeAt(state.position);
+
+ while (ch !== 0) {
+ following = state.input.charCodeAt(state.position + 1);
+ _line = state.line; // Save the current line.
+ _pos = state.position;
+
+ //
+ // Explicit notation case. There are two separate blocks:
+ // first for the key (denoted by "?") and second for the value (denoted by ":")
+ //
+ if ((ch === 0x3F/* ? */ || ch === 0x3A/* : */) && is_WS_OR_EOL(following)) {
+
+ if (ch === 0x3F/* ? */) {
+ if (atExplicitKey) {
+ storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null);
+ keyTag = keyNode = valueNode = null;
+ }
+
+ detected = true;
+ atExplicitKey = true;
+ allowCompact = true;
+
+ } else if (atExplicitKey) {
+ // i.e. 0x3A/* : */ === character after the explicit key.
+ atExplicitKey = false;
+ allowCompact = true;
+
+ } else {
+ throwError(state, 'incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line');
+ }
+
+ state.position += 1;
+ ch = following;
+
+ //
+ // Implicit notation case. Flow-style node as the key first, then ":", and the value.
+ //
+ } else if (composeNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true)) {
+
+ if (state.line === _line) {
+ ch = state.input.charCodeAt(state.position);
+
+ while (is_WHITE_SPACE(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (ch === 0x3A/* : */) {
+ ch = state.input.charCodeAt(++state.position);
+
+ if (!is_WS_OR_EOL(ch)) {
+ throwError(state, 'a whitespace character is expected after the key-value separator within a block mapping');
+ }
+
+ if (atExplicitKey) {
+ storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null);
+ keyTag = keyNode = valueNode = null;
+ }
+
+ detected = true;
+ atExplicitKey = false;
+ allowCompact = false;
+ keyTag = state.tag;
+ keyNode = state.result;
+
+ } else if (detected) {
+ throwError(state, 'can not read an implicit mapping pair; a colon is missed');
+
+ } else {
+ state.tag = _tag;
+ state.anchor = _anchor;
+ return true; // Keep the result of `composeNode`.
+ }
+
+ } else if (detected) {
+ throwError(state, 'can not read a block mapping entry; a multiline key may not be an implicit key');
+
+ } else {
+ state.tag = _tag;
+ state.anchor = _anchor;
+ return true; // Keep the result of `composeNode`.
+ }
+
+ } else {
+ break; // Reading is done. Go to the epilogue.
+ }
+
+ //
+ // Common reading code for both explicit and implicit notations.
+ //
+ if (state.line === _line || state.lineIndent > nodeIndent) {
+ if (composeNode(state, nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)) {
+ if (atExplicitKey) {
+ keyNode = state.result;
+ } else {
+ valueNode = state.result;
+ }
+ }
+
+ if (!atExplicitKey) {
+ storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _line, _pos);
+ keyTag = keyNode = valueNode = null;
+ }
+
+ skipSeparationSpace(state, true, -1);
+ ch = state.input.charCodeAt(state.position);
+ }
+
+ if (state.lineIndent > nodeIndent && (ch !== 0)) {
+ throwError(state, 'bad indentation of a mapping entry');
+ } else if (state.lineIndent < nodeIndent) {
+ break;
+ }
+ }
+
+ //
+ // Epilogue.
+ //
+
+ // Special case: last mapping's node contains only the key in explicit notation.
+ if (atExplicitKey) {
+ storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null);
+ }
+
+ // Expose the resulting mapping.
+ if (detected) {
+ state.tag = _tag;
+ state.anchor = _anchor;
+ state.kind = 'mapping';
+ state.result = _result;
+ }
+
+ return detected;
+}
+
+function readTagProperty(state) {
+ var _position,
+ isVerbatim = false,
+ isNamed = false,
+ tagHandle,
+ tagName,
+ ch;
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch !== 0x21/* ! */) return false;
+
+ if (state.tag !== null) {
+ throwError(state, 'duplication of a tag property');
+ }
+
+ ch = state.input.charCodeAt(++state.position);
+
+ if (ch === 0x3C/* < */) {
+ isVerbatim = true;
+ ch = state.input.charCodeAt(++state.position);
+
+ } else if (ch === 0x21/* ! */) {
+ isNamed = true;
+ tagHandle = '!!';
+ ch = state.input.charCodeAt(++state.position);
+
+ } else {
+ tagHandle = '!';
+ }
+
+ _position = state.position;
+
+ if (isVerbatim) {
+ do { ch = state.input.charCodeAt(++state.position); }
+ while (ch !== 0 && ch !== 0x3E/* > */);
+
+ if (state.position < state.length) {
+ tagName = state.input.slice(_position, state.position);
+ ch = state.input.charCodeAt(++state.position);
+ } else {
+ throwError(state, 'unexpected end of the stream within a verbatim tag');
+ }
+ } else {
+ while (ch !== 0 && !is_WS_OR_EOL(ch)) {
+
+ if (ch === 0x21/* ! */) {
+ if (!isNamed) {
+ tagHandle = state.input.slice(_position - 1, state.position + 1);
+
+ if (!PATTERN_TAG_HANDLE.test(tagHandle)) {
+ throwError(state, 'named tag handle cannot contain such characters');
+ }
+
+ isNamed = true;
+ _position = state.position + 1;
+ } else {
+ throwError(state, 'tag suffix cannot contain exclamation marks');
+ }
+ }
+
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ tagName = state.input.slice(_position, state.position);
+
+ if (PATTERN_FLOW_INDICATORS.test(tagName)) {
+ throwError(state, 'tag suffix cannot contain flow indicator characters');
+ }
+ }
+
+ if (tagName && !PATTERN_TAG_URI.test(tagName)) {
+ throwError(state, 'tag name cannot contain such characters: ' + tagName);
+ }
+
+ if (isVerbatim) {
+ state.tag = tagName;
+
+ } else if (_hasOwnProperty.call(state.tagMap, tagHandle)) {
+ state.tag = state.tagMap[tagHandle] + tagName;
+
+ } else if (tagHandle === '!') {
+ state.tag = '!' + tagName;
+
+ } else if (tagHandle === '!!') {
+ state.tag = 'tag:yaml.org,2002:' + tagName;
+
+ } else {
+ throwError(state, 'undeclared tag handle "' + tagHandle + '"');
+ }
+
+ return true;
+}
+
+function readAnchorProperty(state) {
+ var _position,
+ ch;
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch !== 0x26/* & */) return false;
+
+ if (state.anchor !== null) {
+ throwError(state, 'duplication of an anchor property');
+ }
+
+ ch = state.input.charCodeAt(++state.position);
+ _position = state.position;
+
+ while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (state.position === _position) {
+ throwError(state, 'name of an anchor node must contain at least one character');
+ }
+
+ state.anchor = state.input.slice(_position, state.position);
+ return true;
+}
+
+function readAlias(state) {
+ var _position, alias,
+ ch;
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (ch !== 0x2A/* * */) return false;
+
+ ch = state.input.charCodeAt(++state.position);
+ _position = state.position;
+
+ while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (state.position === _position) {
+ throwError(state, 'name of an alias node must contain at least one character');
+ }
+
+ alias = state.input.slice(_position, state.position);
+
+ if (!state.anchorMap.hasOwnProperty(alias)) {
+ throwError(state, 'unidentified alias "' + alias + '"');
+ }
+
+ state.result = state.anchorMap[alias];
+ skipSeparationSpace(state, true, -1);
+ return true;
+}
+
+function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) {
+ var allowBlockStyles,
+ allowBlockScalars,
+ allowBlockCollections,
+ indentStatus = 1, // 1: this>parent, 0: this=parent, -1: this<parent
+ atNewLine = false,
+ hasContent = false,
+ typeIndex,
+ typeQuantity,
+ type,
+ flowIndent,
+ blockIndent;
+
+ if (state.listener !== null) {
+ state.listener('open', state);
+ }
+
+ state.tag = null;
+ state.anchor = null;
+ state.kind = null;
+ state.result = null;
+
+ allowBlockStyles = allowBlockScalars = allowBlockCollections =
+ CONTEXT_BLOCK_OUT === nodeContext ||
+ CONTEXT_BLOCK_IN === nodeContext;
+
+ if (allowToSeek) {
+ if (skipSeparationSpace(state, true, -1)) {
+ atNewLine = true;
+
+ if (state.lineIndent > parentIndent) {
+ indentStatus = 1;
+ } else if (state.lineIndent === parentIndent) {
+ indentStatus = 0;
+ } else if (state.lineIndent < parentIndent) {
+ indentStatus = -1;
+ }
+ }
+ }
+
+ if (indentStatus === 1) {
+ while (readTagProperty(state) || readAnchorProperty(state)) {
+ if (skipSeparationSpace(state, true, -1)) {
+ atNewLine = true;
+ allowBlockCollections = allowBlockStyles;
+
+ if (state.lineIndent > parentIndent) {
+ indentStatus = 1;
+ } else if (state.lineIndent === parentIndent) {
+ indentStatus = 0;
+ } else if (state.lineIndent < parentIndent) {
+ indentStatus = -1;
+ }
+ } else {
+ allowBlockCollections = false;
+ }
+ }
+ }
+
+ if (allowBlockCollections) {
+ allowBlockCollections = atNewLine || allowCompact;
+ }
+
+ if (indentStatus === 1 || CONTEXT_BLOCK_OUT === nodeContext) {
+ if (CONTEXT_FLOW_IN === nodeContext || CONTEXT_FLOW_OUT === nodeContext) {
+ flowIndent = parentIndent;
+ } else {
+ flowIndent = parentIndent + 1;
+ }
+
+ blockIndent = state.position - state.lineStart;
+
+ if (indentStatus === 1) {
+ if (allowBlockCollections &&
+ (readBlockSequence(state, blockIndent) ||
+ readBlockMapping(state, blockIndent, flowIndent)) ||
+ readFlowCollection(state, flowIndent)) {
+ hasContent = true;
+ } else {
+ if ((allowBlockScalars && readBlockScalar(state, flowIndent)) ||
+ readSingleQuotedScalar(state, flowIndent) ||
+ readDoubleQuotedScalar(state, flowIndent)) {
+ hasContent = true;
+
+ } else if (readAlias(state)) {
+ hasContent = true;
+
+ if (state.tag !== null || state.anchor !== null) {
+ throwError(state, 'alias node should not have any properties');
+ }
+
+ } else if (readPlainScalar(state, flowIndent, CONTEXT_FLOW_IN === nodeContext)) {
+ hasContent = true;
+
+ if (state.tag === null) {
+ state.tag = '?';
+ }
+ }
+
+ if (state.anchor !== null) {
+ state.anchorMap[state.anchor] = state.result;
+ }
+ }
+ } else if (indentStatus === 0) {
+ // Special case: block sequences are allowed to have same indentation level as the parent.
+ // http://www.yaml.org/spec/1.2/spec.html#id2799784
+ hasContent = allowBlockCollections && readBlockSequence(state, blockIndent);
+ }
+ }
+
+ if (state.tag !== null && state.tag !== '!') {
+ if (state.tag === '?') {
+ for (typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex += 1) {
+ type = state.implicitTypes[typeIndex];
+
+ // Implicit resolving is not allowed for non-scalar types, and '?'
+ // non-specific tag is only assigned to plain scalars. So, it isn't
+ // needed to check for 'kind' conformity.
+
+ if (type.resolve(state.result)) { // `state.result` updated in resolver if matched
+ state.result = type.construct(state.result);
+ state.tag = type.tag;
+ if (state.anchor !== null) {
+ state.anchorMap[state.anchor] = state.result;
+ }
+ break;
+ }
+ }
+ } else if (_hasOwnProperty.call(state.typeMap[state.kind || 'fallback'], state.tag)) {
+ type = state.typeMap[state.kind || 'fallback'][state.tag];
+
+ if (state.result !== null && type.kind !== state.kind) {
+ throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be "' + type.kind + '", not "' + state.kind + '"');
+ }
+
+ if (!type.resolve(state.result)) { // `state.result` updated in resolver if matched
+ throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag');
+ } else {
+ state.result = type.construct(state.result);
+ if (state.anchor !== null) {
+ state.anchorMap[state.anchor] = state.result;
+ }
+ }
+ } else {
+ throwError(state, 'unknown tag !<' + state.tag + '>');
+ }
+ }
+
+ if (state.listener !== null) {
+ state.listener('close', state);
+ }
+ return state.tag !== null || state.anchor !== null || hasContent;
+}
+
+function readDocument(state) {
+ var documentStart = state.position,
+ _position,
+ directiveName,
+ directiveArgs,
+ hasDirectives = false,
+ ch;
+
+ state.version = null;
+ state.checkLineBreaks = state.legacy;
+ state.tagMap = {};
+ state.anchorMap = {};
+
+ while ((ch = state.input.charCodeAt(state.position)) !== 0) {
+ skipSeparationSpace(state, true, -1);
+
+ ch = state.input.charCodeAt(state.position);
+
+ if (state.lineIndent > 0 || ch !== 0x25/* % */) {
+ break;
+ }
+
+ hasDirectives = true;
+ ch = state.input.charCodeAt(++state.position);
+ _position = state.position;
+
+ while (ch !== 0 && !is_WS_OR_EOL(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ directiveName = state.input.slice(_position, state.position);
+ directiveArgs = [];
+
+ if (directiveName.length < 1) {
+ throwError(state, 'directive name must not be less than one character in length');
+ }
+
+ while (ch !== 0) {
+ while (is_WHITE_SPACE(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ if (ch === 0x23/* # */) {
+ do { ch = state.input.charCodeAt(++state.position); }
+ while (ch !== 0 && !is_EOL(ch));
+ break;
+ }
+
+ if (is_EOL(ch)) break;
+
+ _position = state.position;
+
+ while (ch !== 0 && !is_WS_OR_EOL(ch)) {
+ ch = state.input.charCodeAt(++state.position);
+ }
+
+ directiveArgs.push(state.input.slice(_position, state.position));
+ }
+
+ if (ch !== 0) readLineBreak(state);
+
+ if (_hasOwnProperty.call(directiveHandlers, directiveName)) {
+ directiveHandlers[directiveName](state, directiveName, directiveArgs);
+ } else {
+ throwWarning(state, 'unknown document directive "' + directiveName + '"');
+ }
+ }
+
+ skipSeparationSpace(state, true, -1);
+
+ if (state.lineIndent === 0 &&
+ state.input.charCodeAt(state.position) === 0x2D/* - */ &&
+ state.input.charCodeAt(state.position + 1) === 0x2D/* - */ &&
+ state.input.charCodeAt(state.position + 2) === 0x2D/* - */) {
+ state.position += 3;
+ skipSeparationSpace(state, true, -1);
+
+ } else if (hasDirectives) {
+ throwError(state, 'directives end mark is expected');
+ }
+
+ composeNode(state, state.lineIndent - 1, CONTEXT_BLOCK_OUT, false, true);
+ skipSeparationSpace(state, true, -1);
+
+ if (state.checkLineBreaks &&
+ PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) {
+ throwWarning(state, 'non-ASCII line breaks are interpreted as content');
+ }
+
+ state.documents.push(state.result);
+
+ if (state.position === state.lineStart && testDocumentSeparator(state)) {
+
+ if (state.input.charCodeAt(state.position) === 0x2E/* . */) {
+ state.position += 3;
+ skipSeparationSpace(state, true, -1);
+ }
+ return;
+ }
+
+ if (state.position < (state.length - 1)) {
+ throwError(state, 'end of the stream or a document separator is expected');
+ } else {
+ return;
+ }
+}
+
+
+function loadDocuments(input, options) {
+ input = String(input);
+ options = options || {};
+
+ if (input.length !== 0) {
+
+ // Add tailing `\n` if not exists
+ if (input.charCodeAt(input.length - 1) !== 0x0A/* LF */ &&
+ input.charCodeAt(input.length - 1) !== 0x0D/* CR */) {
+ input += '\n';
+ }
+
+ // Strip BOM
+ if (input.charCodeAt(0) === 0xFEFF) {
+ input = input.slice(1);
+ }
+ }
+
+ var state = new State(input, options);
+
+ // Use 0 as string terminator. That significantly simplifies bounds check.
+ state.input += '\0';
+
+ while (state.input.charCodeAt(state.position) === 0x20/* Space */) {
+ state.lineIndent += 1;
+ state.position += 1;
+ }
+
+ while (state.position < (state.length - 1)) {
+ readDocument(state);
+ }
+
+ return state.documents;
+}
+
+
+function loadAll(input, iterator, options) {
+ var documents = loadDocuments(input, options), index, length;
+
+ if (typeof iterator !== 'function') {
+ return documents;
+ }
+
+ for (index = 0, length = documents.length; index < length; index += 1) {
+ iterator(documents[index]);
+ }
+}
+
+
+function load(input, options) {
+ var documents = loadDocuments(input, options);
+
+ if (documents.length === 0) {
+ /*eslint-disable no-undefined*/
+ return undefined;
+ } else if (documents.length === 1) {
+ return documents[0];
+ }
+ throw new YAMLException('expected a single document in the stream, but found more');
+}
+
+
+function safeLoadAll(input, output, options) {
+ if (typeof output === 'function') {
+ loadAll(input, output, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));
+ } else {
+ return loadAll(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));
+ }
+}
+
+
+function safeLoad(input, options) {
+ return load(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));
+}
+
+
+module.exports.loadAll = loadAll;
+module.exports.load = load;
+module.exports.safeLoadAll = safeLoadAll;
+module.exports.safeLoad = safeLoad;
+
+},{"./common":2,"./exception":4,"./mark":6,"./schema/default_full":9,"./schema/default_safe":10}],6:[function(require,module,exports){
+'use strict';
+
+
+var common = require('./common');
+
+
+function Mark(name, buffer, position, line, column) {
+ this.name = name;
+ this.buffer = buffer;
+ this.position = position;
+ this.line = line;
+ this.column = column;
+}
+
+
+Mark.prototype.getSnippet = function getSnippet(indent, maxLength) {
+ var head, start, tail, end, snippet;
+
+ if (!this.buffer) return null;
+
+ indent = indent || 4;
+ maxLength = maxLength || 75;
+
+ head = '';
+ start = this.position;
+
+ while (start > 0 && '\x00\r\n\x85\u2028\u2029'.indexOf(this.buffer.charAt(start - 1)) === -1) {
+ start -= 1;
+ if (this.position - start > (maxLength / 2 - 1)) {
+ head = ' ... ';
+ start += 5;
+ break;
+ }
+ }
+
+ tail = '';
+ end = this.position;
+
+ while (end < this.buffer.length && '\x00\r\n\x85\u2028\u2029'.indexOf(this.buffer.charAt(end)) === -1) {
+ end += 1;
+ if (end - this.position > (maxLength / 2 - 1)) {
+ tail = ' ... ';
+ end -= 5;
+ break;
+ }
+ }
+
+ snippet = this.buffer.slice(start, end);
+
+ return common.repeat(' ', indent) + head + snippet + tail + '\n' +
+ common.repeat(' ', indent + this.position - start + head.length) + '^';
+};
+
+
+Mark.prototype.toString = function toString(compact) {
+ var snippet, where = '';
+
+ if (this.name) {
+ where += 'in "' + this.name + '" ';
+ }
+
+ where += 'at line ' + (this.line + 1) + ', column ' + (this.column + 1);
+
+ if (!compact) {
+ snippet = this.getSnippet();
+
+ if (snippet) {
+ where += ':\n' + snippet;
+ }
+ }
+
+ return where;
+};
+
+
+module.exports = Mark;
+
+},{"./common":2}],7:[function(require,module,exports){
+'use strict';
+
+/*eslint-disable max-len*/
+
+var common = require('./common');
+var YAMLException = require('./exception');
+var Type = require('./type');
+
+
+function compileList(schema, name, result) {
+ var exclude = [];
+
+ schema.include.forEach(function (includedSchema) {
+ result = compileList(includedSchema, name, result);
+ });
+
+ schema[name].forEach(function (currentType) {
+ result.forEach(function (previousType, previousIndex) {
+ if (previousType.tag === currentType.tag && previousType.kind === currentType.kind) {
+ exclude.push(previousIndex);
+ }
+ });
+
+ result.push(currentType);
+ });
+
+ return result.filter(function (type, index) {
+ return exclude.indexOf(index) === -1;
+ });
+}
+
+
+function compileMap(/* lists... */) {
+ var result = {
+ scalar: {},
+ sequence: {},
+ mapping: {},
+ fallback: {}
+ }, index, length;
+
+ function collectType(type) {
+ result[type.kind][type.tag] = result['fallback'][type.tag] = type;
+ }
+
+ for (index = 0, length = arguments.length; index < length; index += 1) {
+ arguments[index].forEach(collectType);
+ }
+ return result;
+}
+
+
+function Schema(definition) {
+ this.include = definition.include || [];
+ this.implicit = definition.implicit || [];
+ this.explicit = definition.explicit || [];
+
+ this.implicit.forEach(function (type) {
+ if (type.loadKind && type.loadKind !== 'scalar') {
+ throw new YAMLException('There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.');
+ }
+ });
+
+ this.compiledImplicit = compileList(this, 'implicit', []);
+ this.compiledExplicit = compileList(this, 'explicit', []);
+ this.compiledTypeMap = compileMap(this.compiledImplicit, this.compiledExplicit);
+}
+
+
+Schema.DEFAULT = null;
+
+
+Schema.create = function createSchema() {
+ var schemas, types;
+
+ switch (arguments.length) {
+ case 1:
+ schemas = Schema.DEFAULT;
+ types = arguments[0];
+ break;
+
+ case 2:
+ schemas = arguments[0];
+ types = arguments[1];
+ break;
+
+ default:
+ throw new YAMLException('Wrong number of arguments for Schema.create function');
+ }
+
+ schemas = common.toArray(schemas);
+ types = common.toArray(types);
+
+ if (!schemas.every(function (schema) { return schema instanceof Schema; })) {
+ throw new YAMLException('Specified list of super schemas (or a single Schema object) contains a non-Schema object.');
+ }
+
+ if (!types.every(function (type) { return type instanceof Type; })) {
+ throw new YAMLException('Specified list of YAML types (or a single Type object) contains a non-Type object.');
+ }
+
+ return new Schema({
+ include: schemas,
+ explicit: types
+ });
+};
+
+
+module.exports = Schema;
+
+},{"./common":2,"./exception":4,"./type":13}],8:[function(require,module,exports){
+// Standard YAML's Core schema.
+// http://www.yaml.org/spec/1.2/spec.html#id2804923
+//
+// NOTE: JS-YAML does not support schema-specific tag resolution restrictions.
+// So, Core schema has no distinctions from JSON schema is JS-YAML.
+
+
+'use strict';
+
+
+var Schema = require('../schema');
+
+
+module.exports = new Schema({
+ include: [
+ require('./json')
+ ]
+});
+
+},{"../schema":7,"./json":12}],9:[function(require,module,exports){
+// JS-YAML's default schema for `load` function.
+// It is not described in the YAML specification.
+//
+// This schema is based on JS-YAML's default safe schema and includes
+// JavaScript-specific types: !!js/undefined, !!js/regexp and !!js/function.
+//
+// Also this schema is used as default base schema at `Schema.create` function.
+
+
+'use strict';
+
+
+var Schema = require('../schema');
+
+
+module.exports = Schema.DEFAULT = new Schema({
+ include: [
+ require('./default_safe')
+ ],
+ explicit: [
+ require('../type/js/undefined'),
+ require('../type/js/regexp'),
+ require('../type/js/function')
+ ]
+});
+
+},{"../schema":7,"../type/js/function":18,"../type/js/regexp":19,"../type/js/undefined":20,"./default_safe":10}],10:[function(require,module,exports){
+// JS-YAML's default schema for `safeLoad` function.
+// It is not described in the YAML specification.
+//
+// This schema is based on standard YAML's Core schema and includes most of
+// extra types described at YAML tag repository. (http://yaml.org/type/)
+
+
+'use strict';
+
+
+var Schema = require('../schema');
+
+
+module.exports = new Schema({
+ include: [
+ require('./core')
+ ],
+ implicit: [
+ require('../type/timestamp'),
+ require('../type/merge')
+ ],
+ explicit: [
+ require('../type/binary'),
+ require('../type/omap'),
+ require('../type/pairs'),
+ require('../type/set')
+ ]
+});
+
+},{"../schema":7,"../type/binary":14,"../type/merge":22,"../type/omap":24,"../type/pairs":25,"../type/set":27,"../type/timestamp":29,"./core":8}],11:[function(require,module,exports){
+// Standard YAML's Failsafe schema.
+// http://www.yaml.org/spec/1.2/spec.html#id2802346
+
+
+'use strict';
+
+
+var Schema = require('../schema');
+
+
+module.exports = new Schema({
+ explicit: [
+ require('../type/str'),
+ require('../type/seq'),
+ require('../type/map')
+ ]
+});
+
+},{"../schema":7,"../type/map":21,"../type/seq":26,"../type/str":28}],12:[function(require,module,exports){
+// Standard YAML's JSON schema.
+// http://www.yaml.org/spec/1.2/spec.html#id2803231
+//
+// NOTE: JS-YAML does not support schema-specific tag resolution restrictions.
+// So, this schema is not such strict as defined in the YAML specification.
+// It allows numbers in binary notaion, use `Null` and `NULL` as `null`, etc.
+
+
+'use strict';
+
+
+var Schema = require('../schema');
+
+
+module.exports = new Schema({
+ include: [
+ require('./failsafe')
+ ],
+ implicit: [
+ require('../type/null'),
+ require('../type/bool'),
+ require('../type/int'),
+ require('../type/float')
+ ]
+});
+
+},{"../schema":7,"../type/bool":15,"../type/float":16,"../type/int":17,"../type/null":23,"./failsafe":11}],13:[function(require,module,exports){
+'use strict';
+
+var YAMLException = require('./exception');
+
+var TYPE_CONSTRUCTOR_OPTIONS = [
+ 'kind',
+ 'resolve',
+ 'construct',
+ 'instanceOf',
+ 'predicate',
+ 'represent',
+ 'defaultStyle',
+ 'styleAliases'
+];
+
+var YAML_NODE_KINDS = [
+ 'scalar',
+ 'sequence',
+ 'mapping'
+];
+
+function compileStyleAliases(map) {
+ var result = {};
+
+ if (map !== null) {
+ Object.keys(map).forEach(function (style) {
+ map[style].forEach(function (alias) {
+ result[String(alias)] = style;
+ });
+ });
+ }
+
+ return result;
+}
+
+function Type(tag, options) {
+ options = options || {};
+
+ Object.keys(options).forEach(function (name) {
+ if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) {
+ throw new YAMLException('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.');
+ }
+ });
+
+ // TODO: Add tag format check.
+ this.tag = tag;
+ this.kind = options['kind'] || null;
+ this.resolve = options['resolve'] || function () { return true; };
+ this.construct = options['construct'] || function (data) { return data; };
+ this.instanceOf = options['instanceOf'] || null;
+ this.predicate = options['predicate'] || null;
+ this.represent = options['represent'] || null;
+ this.defaultStyle = options['defaultStyle'] || null;
+ this.styleAliases = compileStyleAliases(options['styleAliases'] || null);
+
+ if (YAML_NODE_KINDS.indexOf(this.kind) === -1) {
+ throw new YAMLException('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.');
+ }
+}
+
+module.exports = Type;
+
+},{"./exception":4}],14:[function(require,module,exports){
+'use strict';
+
+/*eslint-disable no-bitwise*/
+
+var NodeBuffer;
+
+try {
+ // A trick for browserified version, to not include `Buffer` shim
+ var _require = require;
+ NodeBuffer = _require('buffer').Buffer;
+} catch (__) {}
+
+var Type = require('../type');
+
+
+// [ 64, 65, 66 ] -> [ padding, CR, LF ]
+var BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r';
+
+
+function resolveYamlBinary(data) {
+ if (data === null) return false;
+
+ var code, idx, bitlen = 0, max = data.length, map = BASE64_MAP;
+
+ // Convert one by one.
+ for (idx = 0; idx < max; idx++) {
+ code = map.indexOf(data.charAt(idx));
+
+ // Skip CR/LF
+ if (code > 64) continue;
+
+ // Fail on illegal characters
+ if (code < 0) return false;
+
+ bitlen += 6;
+ }
+
+ // If there are any bits left, source was corrupted
+ return (bitlen % 8) === 0;
+}
+
+function constructYamlBinary(data) {
+ var idx, tailbits,
+ input = data.replace(/[\r\n=]/g, ''), // remove CR/LF & padding to simplify scan
+ max = input.length,
+ map = BASE64_MAP,
+ bits = 0,
+ result = [];
+
+ // Collect by 6*4 bits (3 bytes)
+
+ for (idx = 0; idx < max; idx++) {
+ if ((idx % 4 === 0) && idx) {
+ result.push((bits >> 16) & 0xFF);
+ result.push((bits >> 8) & 0xFF);
+ result.push(bits & 0xFF);
+ }
+
+ bits = (bits << 6) | map.indexOf(input.charAt(idx));
+ }
+
+ // Dump tail
+
+ tailbits = (max % 4) * 6;
+
+ if (tailbits === 0) {
+ result.push((bits >> 16) & 0xFF);
+ result.push((bits >> 8) & 0xFF);
+ result.push(bits & 0xFF);
+ } else if (tailbits === 18) {
+ result.push((bits >> 10) & 0xFF);
+ result.push((bits >> 2) & 0xFF);
+ } else if (tailbits === 12) {
+ result.push((bits >> 4) & 0xFF);
+ }
+
+ // Wrap into Buffer for NodeJS and leave Array for browser
+ if (NodeBuffer) {
+ // Support node 6.+ Buffer API when available
+ return NodeBuffer.from ? NodeBuffer.from(result) : new NodeBuffer(result);
+ }
+
+ return result;
+}
+
+function representYamlBinary(object /*, style*/) {
+ var result = '', bits = 0, idx, tail,
+ max = object.length,
+ map = BASE64_MAP;
+
+ // Convert every three bytes to 4 ASCII characters.
+
+ for (idx = 0; idx < max; idx++) {
+ if ((idx % 3 === 0) && idx) {
+ result += map[(bits >> 18) & 0x3F];
+ result += map[(bits >> 12) & 0x3F];
+ result += map[(bits >> 6) & 0x3F];
+ result += map[bits & 0x3F];
+ }
+
+ bits = (bits << 8) + object[idx];
+ }
+
+ // Dump tail
+
+ tail = max % 3;
+
+ if (tail === 0) {
+ result += map[(bits >> 18) & 0x3F];
+ result += map[(bits >> 12) & 0x3F];
+ result += map[(bits >> 6) & 0x3F];
+ result += map[bits & 0x3F];
+ } else if (tail === 2) {
+ result += map[(bits >> 10) & 0x3F];
+ result += map[(bits >> 4) & 0x3F];
+ result += map[(bits << 2) & 0x3F];
+ result += map[64];
+ } else if (tail === 1) {
+ result += map[(bits >> 2) & 0x3F];
+ result += map[(bits << 4) & 0x3F];
+ result += map[64];
+ result += map[64];
+ }
+
+ return result;
+}
+
+function isBinary(object) {
+ return NodeBuffer && NodeBuffer.isBuffer(object);
+}
+
+module.exports = new Type('tag:yaml.org,2002:binary', {
+ kind: 'scalar',
+ resolve: resolveYamlBinary,
+ construct: constructYamlBinary,
+ predicate: isBinary,
+ represent: representYamlBinary
+});
+
+},{"../type":13}],15:[function(require,module,exports){
+'use strict';
+
+var Type = require('../type');
+
+function resolveYamlBoolean(data) {
+ if (data === null) return false;
+
+ var max = data.length;
+
+ return (max === 4 && (data === 'true' || data === 'True' || data === 'TRUE')) ||
+ (max === 5 && (data === 'false' || data === 'False' || data === 'FALSE'));
+}
+
+function constructYamlBoolean(data) {
+ return data === 'true' ||
+ data === 'True' ||
+ data === 'TRUE';
+}
+
+function isBoolean(object) {
+ return Object.prototype.toString.call(object) === '[object Boolean]';
+}
+
+module.exports = new Type('tag:yaml.org,2002:bool', {
+ kind: 'scalar',
+ resolve: resolveYamlBoolean,
+ construct: constructYamlBoolean,
+ predicate: isBoolean,
+ represent: {
+ lowercase: function (object) { return object ? 'true' : 'false'; },
+ uppercase: function (object) { return object ? 'TRUE' : 'FALSE'; },
+ camelcase: function (object) { return object ? 'True' : 'False'; }
+ },
+ defaultStyle: 'lowercase'
+});
+
+},{"../type":13}],16:[function(require,module,exports){
+'use strict';
+
+var common = require('../common');
+var Type = require('../type');
+
+var YAML_FLOAT_PATTERN = new RegExp(
+ // 2.5e4, 2.5 and integers
+ '^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?' +
+ // .2e4, .2
+ // special case, seems not from spec
+ '|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?' +
+ // 20:59
+ '|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*' +
+ // .inf
+ '|[-+]?\\.(?:inf|Inf|INF)' +
+ // .nan
+ '|\\.(?:nan|NaN|NAN))$');
+
+function resolveYamlFloat(data) {
+ if (data === null) return false;
+
+ if (!YAML_FLOAT_PATTERN.test(data) ||
+ // Quick hack to not allow integers end with `_`
+ // Probably should update regexp & check speed
+ data[data.length - 1] === '_') {
+ return false;
+ }
+
+ return true;
+}
+
+function constructYamlFloat(data) {
+ var value, sign, base, digits;
+
+ value = data.replace(/_/g, '').toLowerCase();
+ sign = value[0] === '-' ? -1 : 1;
+ digits = [];
+
+ if ('+-'.indexOf(value[0]) >= 0) {
+ value = value.slice(1);
+ }
+
+ if (value === '.inf') {
+ return (sign === 1) ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
+
+ } else if (value === '.nan') {
+ return NaN;
+
+ } else if (value.indexOf(':') >= 0) {
+ value.split(':').forEach(function (v) {
+ digits.unshift(parseFloat(v, 10));
+ });
+
+ value = 0.0;
+ base = 1;
+
+ digits.forEach(function (d) {
+ value += d * base;
+ base *= 60;
+ });
+
+ return sign * value;
+
+ }
+ return sign * parseFloat(value, 10);
+}
+
+
+var SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/;
+
+function representYamlFloat(object, style) {
+ var res;
+
+ if (isNaN(object)) {
+ switch (style) {
+ case 'lowercase': return '.nan';
+ case 'uppercase': return '.NAN';
+ case 'camelcase': return '.NaN';
+ }
+ } else if (Number.POSITIVE_INFINITY === object) {
+ switch (style) {
+ case 'lowercase': return '.inf';
+ case 'uppercase': return '.INF';
+ case 'camelcase': return '.Inf';
+ }
+ } else if (Number.NEGATIVE_INFINITY === object) {
+ switch (style) {
+ case 'lowercase': return '-.inf';
+ case 'uppercase': return '-.INF';
+ case 'camelcase': return '-.Inf';
+ }
+ } else if (common.isNegativeZero(object)) {
+ return '-0.0';
+ }
+
+ res = object.toString(10);
+
+ // JS stringifier can build scientific format without dots: 5e-100,
+ // while YAML requres dot: 5.e-100. Fix it with simple hack
+
+ return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace('e', '.e') : res;
+}
+
+function isFloat(object) {
+ return (Object.prototype.toString.call(object) === '[object Number]') &&
+ (object % 1 !== 0 || common.isNegativeZero(object));
+}
+
+module.exports = new Type('tag:yaml.org,2002:float', {
+ kind: 'scalar',
+ resolve: resolveYamlFloat,
+ construct: constructYamlFloat,
+ predicate: isFloat,
+ represent: representYamlFloat,
+ defaultStyle: 'lowercase'
+});
+
+},{"../common":2,"../type":13}],17:[function(require,module,exports){
+'use strict';
+
+var common = require('../common');
+var Type = require('../type');
+
+function isHexCode(c) {
+ return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) ||
+ ((0x41/* A */ <= c) && (c <= 0x46/* F */)) ||
+ ((0x61/* a */ <= c) && (c <= 0x66/* f */));
+}
+
+function isOctCode(c) {
+ return ((0x30/* 0 */ <= c) && (c <= 0x37/* 7 */));
+}
+
+function isDecCode(c) {
+ return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */));
+}
+
+function resolveYamlInteger(data) {
+ if (data === null) return false;
+
+ var max = data.length,
+ index = 0,
+ hasDigits = false,
+ ch;
+
+ if (!max) return false;
+
+ ch = data[index];
+
+ // sign
+ if (ch === '-' || ch === '+') {
+ ch = data[++index];
+ }
+
+ if (ch === '0') {
+ // 0
+ if (index + 1 === max) return true;
+ ch = data[++index];
+
+ // base 2, base 8, base 16
+
+ if (ch === 'b') {
+ // base 2
+ index++;
+
+ for (; index < max; index++) {
+ ch = data[index];
+ if (ch === '_') continue;
+ if (ch !== '0' && ch !== '1') return false;
+ hasDigits = true;
+ }
+ return hasDigits && ch !== '_';
+ }
+
+
+ if (ch === 'x') {
+ // base 16
+ index++;
+
+ for (; index < max; index++) {
+ ch = data[index];
+ if (ch === '_') continue;
+ if (!isHexCode(data.charCodeAt(index))) return false;
+ hasDigits = true;
+ }
+ return hasDigits && ch !== '_';
+ }
+
+ // base 8
+ for (; index < max; index++) {
+ ch = data[index];
+ if (ch === '_') continue;
+ if (!isOctCode(data.charCodeAt(index))) return false;
+ hasDigits = true;
+ }
+ return hasDigits && ch !== '_';
+ }
+
+ // base 10 (except 0) or base 60
+
+ // value should not start with `_`;
+ if (ch === '_') return false;
+
+ for (; index < max; index++) {
+ ch = data[index];
+ if (ch === '_') continue;
+ if (ch === ':') break;
+ if (!isDecCode(data.charCodeAt(index))) {
+ return false;
+ }
+ hasDigits = true;
+ }
+
+ // Should have digits and should not end with `_`
+ if (!hasDigits || ch === '_') return false;
+
+ // if !base60 - done;
+ if (ch !== ':') return true;
+
+ // base60 almost not used, no needs to optimize
+ return /^(:[0-5]?[0-9])+$/.test(data.slice(index));
+}
+
+function constructYamlInteger(data) {
+ var value = data, sign = 1, ch, base, digits = [];
+
+ if (value.indexOf('_') !== -1) {
+ value = value.replace(/_/g, '');
+ }
+
+ ch = value[0];
+
+ if (ch === '-' || ch === '+') {
+ if (ch === '-') sign = -1;
+ value = value.slice(1);
+ ch = value[0];
+ }
+
+ if (value === '0') return 0;
+
+ if (ch === '0') {
+ if (value[1] === 'b') return sign * parseInt(value.slice(2), 2);
+ if (value[1] === 'x') return sign * parseInt(value, 16);
+ return sign * parseInt(value, 8);
+ }
+
+ if (value.indexOf(':') !== -1) {
+ value.split(':').forEach(function (v) {
+ digits.unshift(parseInt(v, 10));
+ });
+
+ value = 0;
+ base = 1;
+
+ digits.forEach(function (d) {
+ value += (d * base);
+ base *= 60;
+ });
+
+ return sign * value;
+
+ }
+
+ return sign * parseInt(value, 10);
+}
+
+function isInteger(object) {
+ return (Object.prototype.toString.call(object)) === '[object Number]' &&
+ (object % 1 === 0 && !common.isNegativeZero(object));
+}
+
+module.exports = new Type('tag:yaml.org,2002:int', {
+ kind: 'scalar',
+ resolve: resolveYamlInteger,
+ construct: constructYamlInteger,
+ predicate: isInteger,
+ represent: {
+ binary: function (obj) { return obj >= 0 ? '0b' + obj.toString(2) : '-0b' + obj.toString(2).slice(1); },
+ octal: function (obj) { return obj >= 0 ? '0' + obj.toString(8) : '-0' + obj.toString(8).slice(1); },
+ decimal: function (obj) { return obj.toString(10); },
+ /* eslint-disable max-len */
+ hexadecimal: function (obj) { return obj >= 0 ? '0x' + obj.toString(16).toUpperCase() : '-0x' + obj.toString(16).toUpperCase().slice(1); }
+ },
+ defaultStyle: 'decimal',
+ styleAliases: {
+ binary: [ 2, 'bin' ],
+ octal: [ 8, 'oct' ],
+ decimal: [ 10, 'dec' ],
+ hexadecimal: [ 16, 'hex' ]
+ }
+});
+
+},{"../common":2,"../type":13}],18:[function(require,module,exports){
+'use strict';
+
+var esprima;
+
+// Browserified version does not have esprima
+//
+// 1. For node.js just require module as deps
+// 2. For browser try to require mudule via external AMD system.
+// If not found - try to fallback to window.esprima. If not
+// found too - then fail to parse.
+//
+try {
+ // workaround to exclude package from browserify list.
+ var _require = require;
+ esprima = _require('esprima');
+} catch (_) {
+ /*global window */
+ if (typeof window !== 'undefined') esprima = window.esprima;
+}
+
+var Type = require('../../type');
+
+function resolveJavascriptFunction(data) {
+ if (data === null) return false;
+
+ try {
+ var source = '(' + data + ')',
+ ast = esprima.parse(source, { range: true });
+
+ if (ast.type !== 'Program' ||
+ ast.body.length !== 1 ||
+ ast.body[0].type !== 'ExpressionStatement' ||
+ (ast.body[0].expression.type !== 'ArrowFunctionExpression' &&
+ ast.body[0].expression.type !== 'FunctionExpression')) {
+ return false;
+ }
+
+ return true;
+ } catch (err) {
+ return false;
+ }
+}
+
+function constructJavascriptFunction(data) {
+ /*jslint evil:true*/
+
+ var source = '(' + data + ')',
+ ast = esprima.parse(source, { range: true }),
+ params = [],
+ body;
+
+ if (ast.type !== 'Program' ||
+ ast.body.length !== 1 ||
+ ast.body[0].type !== 'ExpressionStatement' ||
+ (ast.body[0].expression.type !== 'ArrowFunctionExpression' &&
+ ast.body[0].expression.type !== 'FunctionExpression')) {
+ throw new Error('Failed to resolve function');
+ }
+
+ ast.body[0].expression.params.forEach(function (param) {
+ params.push(param.name);
+ });
+
+ body = ast.body[0].expression.body.range;
+
+ // Esprima's ranges include the first '{' and the last '}' characters on
+ // function expressions. So cut them out.
+ if (ast.body[0].expression.body.type === 'BlockStatement') {
+ /*eslint-disable no-new-func*/
+ return new Function(params, source.slice(body[0] + 1, body[1] - 1));
+ }
+ // ES6 arrow functions can omit the BlockStatement. In that case, just return
+ // the body.
+ /*eslint-disable no-new-func*/
+ return new Function(params, 'return ' + source.slice(body[0], body[1]));
+}
+
+function representJavascriptFunction(object /*, style*/) {
+ return object.toString();
+}
+
+function isFunction(object) {
+ return Object.prototype.toString.call(object) === '[object Function]';
+}
+
+module.exports = new Type('tag:yaml.org,2002:js/function', {
+ kind: 'scalar',
+ resolve: resolveJavascriptFunction,
+ construct: constructJavascriptFunction,
+ predicate: isFunction,
+ represent: representJavascriptFunction
+});
+
+},{"../../type":13}],19:[function(require,module,exports){
+'use strict';
+
+var Type = require('../../type');
+
+function resolveJavascriptRegExp(data) {
+ if (data === null) return false;
+ if (data.length === 0) return false;
+
+ var regexp = data,
+ tail = /\/([gim]*)$/.exec(data),
+ modifiers = '';
+
+ // if regexp starts with '/' it can have modifiers and must be properly closed
+ // `/foo/gim` - modifiers tail can be maximum 3 chars
+ if (regexp[0] === '/') {
+ if (tail) modifiers = tail[1];
+
+ if (modifiers.length > 3) return false;
+ // if expression starts with /, is should be properly terminated
+ if (regexp[regexp.length - modifiers.length - 1] !== '/') return false;
+ }
+
+ return true;
+}
+
+function constructJavascriptRegExp(data) {
+ var regexp = data,
+ tail = /\/([gim]*)$/.exec(data),
+ modifiers = '';
+
+ // `/foo/gim` - tail can be maximum 4 chars
+ if (regexp[0] === '/') {
+ if (tail) modifiers = tail[1];
+ regexp = regexp.slice(1, regexp.length - modifiers.length - 1);
+ }
+
+ return new RegExp(regexp, modifiers);
+}
+
+function representJavascriptRegExp(object /*, style*/) {
+ var result = '/' + object.source + '/';
+
+ if (object.global) result += 'g';
+ if (object.multiline) result += 'm';
+ if (object.ignoreCase) result += 'i';
+
+ return result;
+}
+
+function isRegExp(object) {
+ return Object.prototype.toString.call(object) === '[object RegExp]';
+}
+
+module.exports = new Type('tag:yaml.org,2002:js/regexp', {
+ kind: 'scalar',
+ resolve: resolveJavascriptRegExp,
+ construct: constructJavascriptRegExp,
+ predicate: isRegExp,
+ represent: representJavascriptRegExp
+});
+
+},{"../../type":13}],20:[function(require,module,exports){
+'use strict';
+
+var Type = require('../../type');
+
+function resolveJavascriptUndefined() {
+ return true;
+}
+
+function constructJavascriptUndefined() {
+ /*eslint-disable no-undefined*/
+ return undefined;
+}
+
+function representJavascriptUndefined() {
+ return '';
+}
+
+function isUndefined(object) {
+ return typeof object === 'undefined';
+}
+
+module.exports = new Type('tag:yaml.org,2002:js/undefined', {
+ kind: 'scalar',
+ resolve: resolveJavascriptUndefined,
+ construct: constructJavascriptUndefined,
+ predicate: isUndefined,
+ represent: representJavascriptUndefined
+});
+
+},{"../../type":13}],21:[function(require,module,exports){
+'use strict';
+
+var Type = require('../type');
+
+module.exports = new Type('tag:yaml.org,2002:map', {
+ kind: 'mapping',
+ construct: function (data) { return data !== null ? data : {}; }
+});
+
+},{"../type":13}],22:[function(require,module,exports){
+'use strict';
+
+var Type = require('../type');
+
+function resolveYamlMerge(data) {
+ return data === '<<' || data === null;
+}
+
+module.exports = new Type('tag:yaml.org,2002:merge', {
+ kind: 'scalar',
+ resolve: resolveYamlMerge
+});
+
+},{"../type":13}],23:[function(require,module,exports){
+'use strict';
+
+var Type = require('../type');
+
+function resolveYamlNull(data) {
+ if (data === null) return true;
+
+ var max = data.length;
+
+ return (max === 1 && data === '~') ||
+ (max === 4 && (data === 'null' || data === 'Null' || data === 'NULL'));
+}
+
+function constructYamlNull() {
+ return null;
+}
+
+function isNull(object) {
+ return object === null;
+}
+
+module.exports = new Type('tag:yaml.org,2002:null', {
+ kind: 'scalar',
+ resolve: resolveYamlNull,
+ construct: constructYamlNull,
+ predicate: isNull,
+ represent: {
+ canonical: function () { return '~'; },
+ lowercase: function () { return 'null'; },
+ uppercase: function () { return 'NULL'; },
+ camelcase: function () { return 'Null'; }
+ },
+ defaultStyle: 'lowercase'
+});
+
+},{"../type":13}],24:[function(require,module,exports){
+'use strict';
+
+var Type = require('../type');
+
+var _hasOwnProperty = Object.prototype.hasOwnProperty;
+var _toString = Object.prototype.toString;
+
+function resolveYamlOmap(data) {
+ if (data === null) return true;
+
+ var objectKeys = [], index, length, pair, pairKey, pairHasKey,
+ object = data;
+
+ for (index = 0, length = object.length; index < length; index += 1) {
+ pair = object[index];
+ pairHasKey = false;
+
+ if (_toString.call(pair) !== '[object Object]') return false;
+
+ for (pairKey in pair) {
+ if (_hasOwnProperty.call(pair, pairKey)) {
+ if (!pairHasKey) pairHasKey = true;
+ else return false;
+ }
+ }
+
+ if (!pairHasKey) return false;
+
+ if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey);
+ else return false;
+ }
+
+ return true;
+}
+
+function constructYamlOmap(data) {
+ return data !== null ? data : [];
+}
+
+module.exports = new Type('tag:yaml.org,2002:omap', {
+ kind: 'sequence',
+ resolve: resolveYamlOmap,
+ construct: constructYamlOmap
+});
+
+},{"../type":13}],25:[function(require,module,exports){
+'use strict';
+
+var Type = require('../type');
+
+var _toString = Object.prototype.toString;
+
+function resolveYamlPairs(data) {
+ if (data === null) return true;
+
+ var index, length, pair, keys, result,
+ object = data;
+
+ result = new Array(object.length);
+
+ for (index = 0, length = object.length; index < length; index += 1) {
+ pair = object[index];
+
+ if (_toString.call(pair) !== '[object Object]') return false;
+
+ keys = Object.keys(pair);
+
+ if (keys.length !== 1) return false;
+
+ result[index] = [ keys[0], pair[keys[0]] ];
+ }
+
+ return true;
+}
+
+function constructYamlPairs(data) {
+ if (data === null) return [];
+
+ var index, length, pair, keys, result,
+ object = data;
+
+ result = new Array(object.length);
+
+ for (index = 0, length = object.length; index < length; index += 1) {
+ pair = object[index];
+
+ keys = Object.keys(pair);
+
+ result[index] = [ keys[0], pair[keys[0]] ];
+ }
+
+ return result;
+}
+
+module.exports = new Type('tag:yaml.org,2002:pairs', {
+ kind: 'sequence',
+ resolve: resolveYamlPairs,
+ construct: constructYamlPairs
+});
+
+},{"../type":13}],26:[function(require,module,exports){
+'use strict';
+
+var Type = require('../type');
+
+module.exports = new Type('tag:yaml.org,2002:seq', {
+ kind: 'sequence',
+ construct: function (data) { return data !== null ? data : []; }
+});
+
+},{"../type":13}],27:[function(require,module,exports){
+'use strict';
+
+var Type = require('../type');
+
+var _hasOwnProperty = Object.prototype.hasOwnProperty;
+
+function resolveYamlSet(data) {
+ if (data === null) return true;
+
+ var key, object = data;
+
+ for (key in object) {
+ if (_hasOwnProperty.call(object, key)) {
+ if (object[key] !== null) return false;
+ }
+ }
+
+ return true;
+}
+
+function constructYamlSet(data) {
+ return data !== null ? data : {};
+}
+
+module.exports = new Type('tag:yaml.org,2002:set', {
+ kind: 'mapping',
+ resolve: resolveYamlSet,
+ construct: constructYamlSet
+});
+
+},{"../type":13}],28:[function(require,module,exports){
+'use strict';
+
+var Type = require('../type');
+
+module.exports = new Type('tag:yaml.org,2002:str', {
+ kind: 'scalar',
+ construct: function (data) { return data !== null ? data : ''; }
+});
+
+},{"../type":13}],29:[function(require,module,exports){
+'use strict';
+
+var Type = require('../type');
+
+var YAML_DATE_REGEXP = new RegExp(
+ '^([0-9][0-9][0-9][0-9])' + // [1] year
+ '-([0-9][0-9])' + // [2] month
+ '-([0-9][0-9])$'); // [3] day
+
+var YAML_TIMESTAMP_REGEXP = new RegExp(
+ '^([0-9][0-9][0-9][0-9])' + // [1] year
+ '-([0-9][0-9]?)' + // [2] month
+ '-([0-9][0-9]?)' + // [3] day
+ '(?:[Tt]|[ \\t]+)' + // ...
+ '([0-9][0-9]?)' + // [4] hour
+ ':([0-9][0-9])' + // [5] minute
+ ':([0-9][0-9])' + // [6] second
+ '(?:\\.([0-9]*))?' + // [7] fraction
+ '(?:[ \\t]*(Z|([-+])([0-9][0-9]?)' + // [8] tz [9] tz_sign [10] tz_hour
+ '(?::([0-9][0-9]))?))?$'); // [11] tz_minute
+
+function resolveYamlTimestamp(data) {
+ if (data === null) return false;
+ if (YAML_DATE_REGEXP.exec(data) !== null) return true;
+ if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true;
+ return false;
+}
+
+function constructYamlTimestamp(data) {
+ var match, year, month, day, hour, minute, second, fraction = 0,
+ delta = null, tz_hour, tz_minute, date;
+
+ match = YAML_DATE_REGEXP.exec(data);
+ if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data);
+
+ if (match === null) throw new Error('Date resolve error');
+
+ // match: [1] year [2] month [3] day
+
+ year = +(match[1]);
+ month = +(match[2]) - 1; // JS month starts with 0
+ day = +(match[3]);
+
+ if (!match[4]) { // no hour
+ return new Date(Date.UTC(year, month, day));
+ }
+
+ // match: [4] hour [5] minute [6] second [7] fraction
+
+ hour = +(match[4]);
+ minute = +(match[5]);
+ second = +(match[6]);
+
+ if (match[7]) {
+ fraction = match[7].slice(0, 3);
+ while (fraction.length < 3) { // milli-seconds
+ fraction += '0';
+ }
+ fraction = +fraction;
+ }
+
+ // match: [8] tz [9] tz_sign [10] tz_hour [11] tz_minute
+
+ if (match[9]) {
+ tz_hour = +(match[10]);
+ tz_minute = +(match[11] || 0);
+ delta = (tz_hour * 60 + tz_minute) * 60000; // delta in mili-seconds
+ if (match[9] === '-') delta = -delta;
+ }
+
+ date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction));
+
+ if (delta) date.setTime(date.getTime() - delta);
+
+ return date;
+}
+
+function representYamlTimestamp(object /*, style*/) {
+ return object.toISOString();
+}
+
+module.exports = new Type('tag:yaml.org,2002:timestamp', {
+ kind: 'scalar',
+ resolve: resolveYamlTimestamp,
+ construct: constructYamlTimestamp,
+ instanceOf: Date,
+ represent: representYamlTimestamp
+});
+
+},{"../type":13}],"/":[function(require,module,exports){
+'use strict';
+
+
+var yaml = require('./lib/js-yaml.js');
+
+
+module.exports = yaml;
+
+},{"./lib/js-yaml.js":1}]},{},[])("/")
+});
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/shoreditch/bootstrap.css b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/shoreditch/bootstrap.css
new file mode 100644
index 00000000..8051b1ec
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/shoreditch/bootstrap.css
@@ -0,0 +1 @@
+#bootstrap-theme .editable-wrap{display:inline-block;white-space:nowrap;margin:0}#bootstrap-theme .editable-wrap .editable-controls, #bootstrap-theme .editable-wrap .editable-error{margin-bottom:0}#bootstrap-theme .editable-wrap .editable-controls>input, #bootstrap-theme .editable-wrap .editable-controls>select, #bootstrap-theme .editable-wrap .editable-controls>textarea{margin-bottom:0}#bootstrap-theme .editable-wrap .editable-input{display:inline-block}#bootstrap-theme .editable-buttons{display:inline-block;vertical-align:top}#bootstrap-theme .editable-buttons button{margin-left:5px}#bootstrap-theme .editable-input.editable-has-buttons{width:auto}#bootstrap-theme .editable-bstime .editable-input input[type=text]{width:46px}#bootstrap-theme .editable-bstime .well-small{margin-bottom:0;padding:10px}#bootstrap-theme .editable-range output{display:inline-block;min-width:30px;vertical-align:top;text-align:center}#bootstrap-theme .editable-color input[type=color]{width:50px}#bootstrap-theme .editable-checkbox label span, #bootstrap-theme .editable-checklist label span, #bootstrap-theme .editable-radiolist label span{margin-left:7px;margin-right:10px}#bootstrap-theme .editable-hide{display:none !important}#bootstrap-theme .editable-click, #bootstrap-theme a.editable-click{text-decoration:none;color:#428bca;border-bottom:dashed 1px #428bca}#bootstrap-theme .editable-click:hover, #bootstrap-theme a.editable-click:hover{text-decoration:none;color:#2a6496;border-bottom-color:#2a6496}#bootstrap-theme .editable-empty, #bootstrap-theme .editable-empty:hover, #bootstrap-theme .editable-empty:focus, #bootstrap-theme a.editable-empty, #bootstrap-theme a.editable-empty:hover, #bootstrap-theme a.editable-empty:focus{font-style:italic;color:#D14;text-decoration:none}.ta-hidden-input{width:1px;height:1px;border:none;position:absolute;top:-10000px;left:-10000px;opacity:0;overflow:hidden;margin:0;padding:0}#bootstrap-theme .ta-root.focussed>.ta-scroll-window.form-control{outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);border-color:#66afe9}#bootstrap-theme .ta-editor.ta-html, #bootstrap-theme .ta-scroll-window.form-control{min-height:300px;height:auto;overflow:auto;font-family:inherit;font-size:100%}#bootstrap-theme .ta-scroll-window.form-control{position:relative;padding:0}#bootstrap-theme .ta-scroll-window>.ta-bind{height:auto;min-height:300px;padding:6px 12px}#bootstrap-theme .ta-editor:focus{user-select:text}#bootstrap-theme .ta-resizer-handle-overlay{z-index:100;position:absolute;display:none}#bootstrap-theme .ta-resizer-handle-overlay>.ta-resizer-handle-info{position:absolute;bottom:16px;right:16px;border:1px solid #000;background-color:#FFF;opacity:0.7;padding:0 4px}#bootstrap-theme .ta-resizer-handle-overlay>.ta-resizer-handle-background{position:absolute;bottom:5px;right:5px;left:5px;top:5px;border:1px solid #000;background-color:rgba(0,0,0,0.2)}#bootstrap-theme .ta-resizer-handle-overlay>.ta-resizer-handle-corner{width:10px;height:10px;position:absolute}#bootstrap-theme .ta-resizer-handle-overlay>.ta-resizer-handle-corner-tl{top:0;left:0;border-left:1px solid #000;border-top:1px solid #000}#bootstrap-theme .ta-resizer-handle-overlay>.ta-resizer-handle-corner-tr{top:0;right:0;border-right:1px solid #000;border-top:1px solid #000}#bootstrap-theme .ta-resizer-handle-overlay>.ta-resizer-handle-corner-bl{bottom:0;left:0;border-left:1px solid #000;border-bottom:1px solid #000}#bootstrap-theme .ta-resizer-handle-overlay>.ta-resizer-handle-corner-br{bottom:0;right:0;border:1px solid #000;cursor:se-resize;background-color:#FFF}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);padding:1px}.popover.top{margin-top:-10px}.popover.bottom{margin-top:10px}.popover-title{font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0;margin:0;padding:8px 14px}.popover-content{padding:9px 14px}.popover>.arrow, .popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:rgba(0,0,0,0.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}#bootstrap-theme .ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}#bootstrap-theme .uib-datepicker .uib-title{width:100%}#bootstrap-theme .uib-day button, #bootstrap-theme .uib-month button, #bootstrap-theme .uib-year button{min-width:100%}#bootstrap-theme .uib-left, #bootstrap-theme .uib-right{width:100%}#bootstrap-theme .uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important}#bootstrap-theme .uib-position-scrollbar-measure{position:absolute !important;top:-9999px !important;width:50px !important;height:50px !important;overflow:scroll !important}#bootstrap-theme .uib-position-body-scrollbar-measure{overflow:scroll !important}#bootstrap-theme .uib-datepicker-popup.dropdown-menu{display:block;float:none;margin:0}#bootstrap-theme .uib-button-bar{padding:10px 9px 2px}[uib-tooltip-popup].tooltip.top-left>.tooltip-arrow, [uib-tooltip-popup].tooltip.top-right>.tooltip-arrow, [uib-tooltip-popup].tooltip.bottom-left>.tooltip-arrow, [uib-tooltip-popup].tooltip.bottom-right>.tooltip-arrow, [uib-tooltip-popup].tooltip.left-top>.tooltip-arrow, [uib-tooltip-popup].tooltip.left-bottom>.tooltip-arrow, [uib-tooltip-popup].tooltip.right-top>.tooltip-arrow, [uib-tooltip-popup].tooltip.right-bottom>.tooltip-arrow, [uib-tooltip-html-popup].tooltip.top-left>.tooltip-arrow, [uib-tooltip-html-popup].tooltip.top-right>.tooltip-arrow, [uib-tooltip-html-popup].tooltip.bottom-left>.tooltip-arrow, [uib-tooltip-html-popup].tooltip.bottom-right>.tooltip-arrow, [uib-tooltip-html-popup].tooltip.left-top>.tooltip-arrow, [uib-tooltip-html-popup].tooltip.left-bottom>.tooltip-arrow, [uib-tooltip-html-popup].tooltip.right-top>.tooltip-arrow, [uib-tooltip-html-popup].tooltip.right-bottom>.tooltip-arrow, [uib-tooltip-template-popup].tooltip.top-left>.tooltip-arrow, [uib-tooltip-template-popup].tooltip.top-right>.tooltip-arrow, [uib-tooltip-template-popup].tooltip.bottom-left>.tooltip-arrow, [uib-tooltip-template-popup].tooltip.bottom-right>.tooltip-arrow, [uib-tooltip-template-popup].tooltip.left-top>.tooltip-arrow, [uib-tooltip-template-popup].tooltip.left-bottom>.tooltip-arrow, [uib-tooltip-template-popup].tooltip.right-top>.tooltip-arrow, [uib-tooltip-template-popup].tooltip.right-bottom>.tooltip-arrow, [uib-popover-popup].popover.top-left>.arrow, [uib-popover-popup].popover.top-right>.arrow, [uib-popover-popup].popover.bottom-left>.arrow, [uib-popover-popup].popover.bottom-right>.arrow, [uib-popover-popup].popover.left-top>.arrow, [uib-popover-popup].popover.left-bottom>.arrow, [uib-popover-popup].popover.right-top>.arrow, [uib-popover-popup].popover.right-bottom>.arrow, [uib-popover-html-popup].popover.top-left>.arrow, [uib-popover-html-popup].popover.top-right>.arrow, [uib-popover-html-popup].popover.bottom-left>.arrow, [uib-popover-html-popup].popover.bottom-right>.arrow, [uib-popover-html-popup].popover.left-top>.arrow, [uib-popover-html-popup].popover.left-bottom>.arrow, [uib-popover-html-popup].popover.right-top>.arrow, [uib-popover-html-popup].popover.right-bottom>.arrow, [uib-popover-template-popup].popover.top-left>.arrow, [uib-popover-template-popup].popover.top-right>.arrow, [uib-popover-template-popup].popover.bottom-left>.arrow, [uib-popover-template-popup].popover.bottom-right>.arrow, [uib-popover-template-popup].popover.left-top>.arrow, [uib-popover-template-popup].popover.left-bottom>.arrow, [uib-popover-template-popup].popover.right-top>.arrow, [uib-popover-template-popup].popover.right-bottom>.arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0}[uib-popover-popup].popover, [uib-popover-html-popup].popover, [uib-popover-template-popup].popover{display:block !important}#bootstrap-theme .uib-time input{width:50px}#bootstrap-theme [uib-typeahead-popup].dropdown-menu{display:block}#bootstrap-theme .ui-select-highlight{font-weight:bold}#bootstrap-theme .ui-select-offscreen{clip:rect(0 0 0 0) !important;width:1px !important;height:1px !important;border:0 !important;margin:0 !important;padding:0 !important;overflow:hidden !important;position:absolute !important;outline:0 !important;left:0px !important;top:0px !important}#bootstrap-theme .ui-select-choices-row:hover{background-color:#f5f5f5}#bootstrap-theme .ng-dirty.ng-invalid>a.select2-choice{border-color:#D44950}#bootstrap-theme .select2-result-single{padding-left:0}#bootstrap-theme .select2-locked>.select2-search-choice-close{display:none}#bootstrap-theme .select-locked>.ui-select-match-close{display:none}body#bootstrap-theme>.select2-container.open, #bootstrap-theme>.select2-container.open{z-index:9999}#bootstrap-theme .ui-select-container[theme="select2"].direction-up .ui-select-match, #bootstrap-theme .ui-select-container.select2.direction-up .ui-select-match{border-radius:4px;border-top-left-radius:0;border-top-right-radius:0}#bootstrap-theme .ui-select-container[theme="select2"].direction-up .ui-select-dropdown, #bootstrap-theme .ui-select-container.select2.direction-up .ui-select-dropdown{border-radius:4px;border-bottom-left-radius:0;border-bottom-right-radius:0;border-top-width:1px;border-top-style:solid;box-shadow:0 -4px 8px rgba(0,0,0,0.25);margin-top:-4px}#bootstrap-theme .ui-select-container[theme="select2"].direction-up .ui-select-dropdown .select2-search, #bootstrap-theme .ui-select-container.select2.direction-up .ui-select-dropdown .select2-search{margin-top:4px}#bootstrap-theme .ui-select-container[theme="select2"].direction-up.select2-dropdown-open .ui-select-match, #bootstrap-theme .ui-select-container.select2.direction-up.select2-dropdown-open .ui-select-match{border-bottom-color:#5897fb}#bootstrap-theme .ui-select-container[theme="select2"] .ui-select-dropdown .ui-select-search-hidden, #bootstrap-theme .ui-select-container[theme="select2"] .ui-select-dropdown .ui-select-search-hidden input{opacity:0;height:0;min-height:0;padding:0;margin:0;border:0}#bootstrap-theme .selectize-input.selectize-focus{border-color:#007FBB !important}#bootstrap-theme .selectize-control.single>.selectize-input>input{width:100%}#bootstrap-theme .selectize-control.multi>.selectize-input>input{margin:0 !important}#bootstrap-theme .selectize-control>.selectize-dropdown{width:100%}#bootstrap-theme .ng-dirty.ng-invalid>div.selectize-input{border-color:#D44950}#bootstrap-theme .ui-select-container[theme="selectize"].direction-up .ui-select-dropdown{box-shadow:0 -4px 8px rgba(0,0,0,0.25);margin-top:-2px}#bootstrap-theme .ui-select-container[theme="selectize"] input.ui-select-search-hidden{opacity:0;height:0;min-height:0;padding:0;margin:0;border:0;width:0}#bootstrap-theme .btn-default-focus{color:#333;background-color:#EBEBEB;border-color:#ADADAD;text-decoration:none;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}#bootstrap-theme .ui-select-bootstrap .ui-select-toggle{position:relative}#bootstrap-theme .ui-select-bootstrap .ui-select-toggle>.caret{position:absolute;height:10px;top:50%;right:10px;margin-top:-2px}#bootstrap-theme .input-group>.ui-select-bootstrap.dropdown{position:static}#bootstrap-theme .input-group>.ui-select-bootstrap>input.ui-select-search.form-control{border-radius:4px;border-top-right-radius:0;border-bottom-right-radius:0}#bootstrap-theme .input-group>.ui-select-bootstrap>input.ui-select-search.form-control.direction-up{border-radius:4px !important;border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}#bootstrap-theme .ui-select-bootstrap .ui-select-search-hidden{opacity:0;height:0;min-height:0;padding:0;margin:0;border:0}#bootstrap-theme .ui-select-bootstrap>.ui-select-match>.btn{text-align:left !important}#bootstrap-theme .ui-select-bootstrap>.ui-select-match>.caret{position:absolute;top:45%;right:15px}#bootstrap-theme .ui-select-bootstrap>.ui-select-choices, #bootstrap-theme .ui-select-bootstrap>.ui-select-no-choice{width:100%;height:auto;max-height:200px;overflow-x:hidden;margin-top:-1px}body#bootstrap-theme>.ui-select-bootstrap.open, #bootstrap-theme>.ui-select-bootstrap.open{z-index:1000}#bootstrap-theme .ui-select-multiple.ui-select-bootstrap{height:auto;padding:3px 3px 0 3px}#bootstrap-theme .ui-select-multiple.ui-select-bootstrap input.ui-select-search{background-color:transparent !important;border:none;outline:none;height:1.666666em;margin-bottom:3px}#bootstrap-theme .ui-select-multiple.ui-select-bootstrap .ui-select-match .close{font-size:1.6em;line-height:0.75}#bootstrap-theme .ui-select-multiple.ui-select-bootstrap .ui-select-match-item{outline:0;margin:0 3px 3px 0}#bootstrap-theme .ui-select-multiple .ui-select-match-item{position:relative}#bootstrap-theme .ui-select-multiple .ui-select-match-item.dropping .ui-select-match-close{pointer-events:none}#bootstrap-theme .ui-select-multiple:hover .ui-select-match-item.dropping-before:before{content:"";position:absolute;top:0;right:100%;height:100%;margin-right:2px;border-left:1px solid #428bca}#bootstrap-theme .ui-select-multiple:hover .ui-select-match-item.dropping-after:after{content:"";position:absolute;top:0;left:100%;height:100%;margin-left:2px;border-right:1px solid #428bca}#bootstrap-theme .ui-select-bootstrap .ui-select-choices-row>span{cursor:pointer;display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}#bootstrap-theme .ui-select-bootstrap .ui-select-choices-row>span:hover, #bootstrap-theme .ui-select-bootstrap .ui-select-choices-row>span:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}#bootstrap-theme .ui-select-bootstrap .ui-select-choices-row.active>span{color:#fff;text-decoration:none;outline:0;background-color:#428bca}#bootstrap-theme .ui-select-bootstrap .ui-select-choices-row.disabled>span, #bootstrap-theme .ui-select-bootstrap .ui-select-choices-row.active.disabled>span{color:#777;cursor:not-allowed;background-color:#fff}#bootstrap-theme .ui-select-match.ng-hide-add, #bootstrap-theme .ui-select-search.ng-hide-add{display:none !important}#bootstrap-theme .ui-select-bootstrap.ng-dirty.ng-invalid>button.btn.ui-select-match{border-color:#D44950}#bootstrap-theme .ui-select-container[theme="bootstrap"].direction-up .ui-select-dropdown{box-shadow:0 -4px 8px rgba(0,0,0,0.25)}#bootstrap-theme .ui-select-bootstrap .ui-select-match-text{width:100%;padding-right:1em}#bootstrap-theme .ui-select-bootstrap .ui-select-match-text span{display:inline-block;width:100%;overflow:hidden}#bootstrap-theme .ui-select-bootstrap .ui-select-toggle>a.btn{position:absolute;height:10px;right:10px;margin-top:-2px}#bootstrap-theme .ui-select-refreshing{position:absolute;right:0;padding:8px 27px;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased}@-webkit-keyframes ui-select-spin{#bootstrap-theme 0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}#bootstrap-theme 100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes ui-select-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}#bootstrap-theme .ui-select-spin{-webkit-animation:ui-select-spin 2s infinite linear;animation:ui-select-spin 2s infinite linear}#bootstrap-theme .ui-select-refreshing.ng-animate{-webkit-animation:none 0s}@font-face{font-family:'FontAwesome';src:url("../fonts/font-awesome/fontawesome-webfont.eot?v=4.7.0");src:url("../fonts/font-awesome/fontawesome-webfont.eot?#iefix&v=4.7.0") format("embedded-opentype"),url("../fonts/font-awesome/fontawesome-webfont.woff2?v=4.7.0") format("woff2"),url("../fonts/font-awesome/fontawesome-webfont.woff?v=4.7.0") format("woff"),url("../fonts/font-awesome/fontawesome-webfont.ttf?v=4.7.0") format("truetype"),url("../fonts/font-awesome/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}#bootstrap-theme .fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#bootstrap-theme .fa-lg{font-size:1.3333333333em;line-height:.75em;vertical-align:-15%}#bootstrap-theme .fa-2x{font-size:2em}#bootstrap-theme .fa-3x{font-size:3em}#bootstrap-theme .fa-4x{font-size:4em}#bootstrap-theme .fa-5x{font-size:5em}#bootstrap-theme .fa-fw{width:1.2857142857em;text-align:center}#bootstrap-theme .fa-ul{padding-left:0;margin-left:2.1428571429em;list-style-type:none}#bootstrap-theme .fa-ul>li{position:relative}#bootstrap-theme .fa-li{position:absolute;left:-2.1428571429em;width:2.1428571429em;top:.1428571429em;text-align:center}#bootstrap-theme .fa-li.fa-lg{left:-1.8571428571em}#bootstrap-theme .fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}#bootstrap-theme .fa-pull-left{float:left}#bootstrap-theme .fa-pull-right{float:right}#bootstrap-theme .fa.fa-pull-left{margin-right:.3em}#bootstrap-theme .fa.fa-pull-right{margin-left:.3em}#bootstrap-theme .pull-right{float:right}#bootstrap-theme .pull-left{float:left}#bootstrap-theme .fa.pull-left{margin-right:.3em}#bootstrap-theme .fa.pull-right{margin-left:.3em}#bootstrap-theme .fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}#bootstrap-theme .fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{#bootstrap-theme 0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}#bootstrap-theme 100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}#bootstrap-theme .fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}#bootstrap-theme .fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}#bootstrap-theme .fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}#bootstrap-theme .fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}#bootstrap-theme .fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}#bootstrap-theme :root .fa-rotate-90, #bootstrap-theme :root .fa-rotate-180, #bootstrap-theme :root .fa-rotate-270, #bootstrap-theme :root .fa-flip-horizontal, #bootstrap-theme :root .fa-flip-vertical{filter:none}#bootstrap-theme .fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}#bootstrap-theme .fa-stack-1x, #bootstrap-theme .fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}#bootstrap-theme .fa-stack-1x{line-height:inherit}#bootstrap-theme .fa-stack-2x{font-size:2em}#bootstrap-theme .fa-inverse{color:#fff}#bootstrap-theme .fa-glass:before{content:""}#bootstrap-theme .fa-music:before{content:""}#bootstrap-theme .fa-search:before{content:""}#bootstrap-theme .fa-envelope-o:before{content:""}#bootstrap-theme .fa-heart:before{content:""}#bootstrap-theme .fa-star:before{content:""}#bootstrap-theme .fa-star-o:before{content:""}#bootstrap-theme .fa-user:before{content:""}#bootstrap-theme .fa-film:before{content:""}#bootstrap-theme .fa-th-large:before{content:""}#bootstrap-theme .fa-th:before{content:""}#bootstrap-theme .fa-th-list:before{content:""}#bootstrap-theme .fa-check:before{content:""}#bootstrap-theme .fa-remove:before, #bootstrap-theme .fa-close:before, #bootstrap-theme .fa-times:before{content:""}#bootstrap-theme .fa-search-plus:before{content:""}#bootstrap-theme .fa-search-minus:before{content:""}#bootstrap-theme .fa-power-off:before{content:""}#bootstrap-theme .fa-signal:before{content:""}#bootstrap-theme .fa-gear:before, #bootstrap-theme .fa-cog:before{content:""}#bootstrap-theme .fa-trash-o:before{content:""}#bootstrap-theme .fa-home:before{content:""}#bootstrap-theme .fa-file-o:before{content:""}#bootstrap-theme .fa-clock-o:before{content:""}#bootstrap-theme .fa-road:before{content:""}#bootstrap-theme .fa-download:before{content:""}#bootstrap-theme .fa-arrow-circle-o-down:before{content:""}#bootstrap-theme .fa-arrow-circle-o-up:before{content:""}#bootstrap-theme .fa-inbox:before{content:""}#bootstrap-theme .fa-play-circle-o:before{content:""}#bootstrap-theme .fa-rotate-right:before, #bootstrap-theme .fa-repeat:before{content:""}#bootstrap-theme .fa-refresh:before{content:""}#bootstrap-theme .fa-list-alt:before{content:""}#bootstrap-theme .fa-lock:before{content:""}#bootstrap-theme .fa-flag:before{content:""}#bootstrap-theme .fa-headphones:before{content:""}#bootstrap-theme .fa-volume-off:before{content:""}#bootstrap-theme .fa-volume-down:before{content:""}#bootstrap-theme .fa-volume-up:before{content:""}#bootstrap-theme .fa-qrcode:before{content:""}#bootstrap-theme .fa-barcode:before{content:""}#bootstrap-theme .fa-tag:before{content:""}#bootstrap-theme .fa-tags:before{content:""}#bootstrap-theme .fa-book:before{content:""}#bootstrap-theme .fa-bookmark:before{content:""}#bootstrap-theme .fa-print:before{content:""}#bootstrap-theme .fa-camera:before{content:""}#bootstrap-theme .fa-font:before{content:""}#bootstrap-theme .fa-bold:before{content:""}#bootstrap-theme .fa-italic:before{content:""}#bootstrap-theme .fa-text-height:before{content:""}#bootstrap-theme .fa-text-width:before{content:""}#bootstrap-theme .fa-align-left:before{content:""}#bootstrap-theme .fa-align-center:before{content:""}#bootstrap-theme .fa-align-right:before{content:""}#bootstrap-theme .fa-align-justify:before{content:""}#bootstrap-theme .fa-list:before{content:""}#bootstrap-theme .fa-dedent:before, #bootstrap-theme .fa-outdent:before{content:""}#bootstrap-theme .fa-indent:before{content:""}#bootstrap-theme .fa-video-camera:before{content:""}#bootstrap-theme .fa-photo:before, #bootstrap-theme .fa-image:before, #bootstrap-theme .fa-picture-o:before{content:""}#bootstrap-theme .fa-pencil:before{content:""}#bootstrap-theme .fa-map-marker:before{content:""}#bootstrap-theme .fa-adjust:before{content:""}#bootstrap-theme .fa-tint:before{content:""}#bootstrap-theme .fa-edit:before, #bootstrap-theme .fa-pencil-square-o:before{content:""}#bootstrap-theme .fa-share-square-o:before{content:""}#bootstrap-theme .fa-check-square-o:before{content:""}#bootstrap-theme .fa-arrows:before{content:""}#bootstrap-theme .fa-step-backward:before{content:""}#bootstrap-theme .fa-fast-backward:before{content:""}#bootstrap-theme .fa-backward:before{content:""}#bootstrap-theme .fa-play:before{content:""}#bootstrap-theme .fa-pause:before{content:""}#bootstrap-theme .fa-stop:before{content:""}#bootstrap-theme .fa-forward:before{content:""}#bootstrap-theme .fa-fast-forward:before{content:""}#bootstrap-theme .fa-step-forward:before{content:""}#bootstrap-theme .fa-eject:before{content:""}#bootstrap-theme .fa-chevron-left:before{content:""}#bootstrap-theme .fa-chevron-right:before{content:""}#bootstrap-theme .fa-plus-circle:before{content:""}#bootstrap-theme .fa-minus-circle:before{content:""}#bootstrap-theme .fa-times-circle:before{content:""}#bootstrap-theme .fa-check-circle:before{content:""}#bootstrap-theme .fa-question-circle:before{content:""}#bootstrap-theme .fa-info-circle:before{content:""}#bootstrap-theme .fa-crosshairs:before{content:""}#bootstrap-theme .fa-times-circle-o:before{content:""}#bootstrap-theme .fa-check-circle-o:before{content:""}#bootstrap-theme .fa-ban:before{content:""}#bootstrap-theme .fa-arrow-left:before{content:""}#bootstrap-theme .fa-arrow-right:before{content:""}#bootstrap-theme .fa-arrow-up:before{content:""}#bootstrap-theme .fa-arrow-down:before{content:""}#bootstrap-theme .fa-mail-forward:before, #bootstrap-theme .fa-share:before{content:""}#bootstrap-theme .fa-expand:before{content:""}#bootstrap-theme .fa-compress:before{content:""}#bootstrap-theme .fa-plus:before{content:""}#bootstrap-theme .fa-minus:before{content:""}#bootstrap-theme .fa-asterisk:before{content:""}#bootstrap-theme .fa-exclamation-circle:before{content:""}#bootstrap-theme .fa-gift:before{content:""}#bootstrap-theme .fa-leaf:before{content:""}#bootstrap-theme .fa-fire:before{content:""}#bootstrap-theme .fa-eye:before{content:""}#bootstrap-theme .fa-eye-slash:before{content:""}#bootstrap-theme .fa-warning:before, #bootstrap-theme .fa-exclamation-triangle:before{content:""}#bootstrap-theme .fa-plane:before{content:""}#bootstrap-theme .fa-calendar:before{content:""}#bootstrap-theme .fa-random:before{content:""}#bootstrap-theme .fa-comment:before{content:""}#bootstrap-theme .fa-magnet:before{content:""}#bootstrap-theme .fa-chevron-up:before{content:""}#bootstrap-theme .fa-chevron-down:before{content:""}#bootstrap-theme .fa-retweet:before{content:""}#bootstrap-theme .fa-shopping-cart:before{content:""}#bootstrap-theme .fa-folder:before{content:""}#bootstrap-theme .fa-folder-open:before{content:""}#bootstrap-theme .fa-arrows-v:before{content:""}#bootstrap-theme .fa-arrows-h:before{content:""}#bootstrap-theme .fa-bar-chart-o:before, #bootstrap-theme .fa-bar-chart:before{content:""}#bootstrap-theme .fa-twitter-square:before{content:""}#bootstrap-theme .fa-facebook-square:before{content:""}#bootstrap-theme .fa-camera-retro:before{content:""}#bootstrap-theme .fa-key:before{content:""}#bootstrap-theme .fa-gears:before, #bootstrap-theme .fa-cogs:before{content:""}#bootstrap-theme .fa-comments:before{content:""}#bootstrap-theme .fa-thumbs-o-up:before{content:""}#bootstrap-theme .fa-thumbs-o-down:before{content:""}#bootstrap-theme .fa-star-half:before{content:""}#bootstrap-theme .fa-heart-o:before{content:""}#bootstrap-theme .fa-sign-out:before{content:""}#bootstrap-theme .fa-linkedin-square:before{content:""}#bootstrap-theme .fa-thumb-tack:before{content:""}#bootstrap-theme .fa-external-link:before{content:""}#bootstrap-theme .fa-sign-in:before{content:""}#bootstrap-theme .fa-trophy:before{content:""}#bootstrap-theme .fa-github-square:before{content:""}#bootstrap-theme .fa-upload:before{content:""}#bootstrap-theme .fa-lemon-o:before{content:""}#bootstrap-theme .fa-phone:before{content:""}#bootstrap-theme .fa-square-o:before{content:""}#bootstrap-theme .fa-bookmark-o:before{content:""}#bootstrap-theme .fa-phone-square:before{content:""}#bootstrap-theme .fa-twitter:before{content:""}#bootstrap-theme .fa-facebook-f:before, #bootstrap-theme .fa-facebook:before{content:""}#bootstrap-theme .fa-github:before{content:""}#bootstrap-theme .fa-unlock:before{content:""}#bootstrap-theme .fa-credit-card:before{content:""}#bootstrap-theme .fa-feed:before, #bootstrap-theme .fa-rss:before{content:""}#bootstrap-theme .fa-hdd-o:before{content:""}#bootstrap-theme .fa-bullhorn:before{content:""}#bootstrap-theme .fa-bell:before{content:""}#bootstrap-theme .fa-certificate:before{content:""}#bootstrap-theme .fa-hand-o-right:before{content:""}#bootstrap-theme .fa-hand-o-left:before{content:""}#bootstrap-theme .fa-hand-o-up:before{content:""}#bootstrap-theme .fa-hand-o-down:before{content:""}#bootstrap-theme .fa-arrow-circle-left:before{content:""}#bootstrap-theme .fa-arrow-circle-right:before{content:""}#bootstrap-theme .fa-arrow-circle-up:before{content:""}#bootstrap-theme .fa-arrow-circle-down:before{content:""}#bootstrap-theme .fa-globe:before{content:""}#bootstrap-theme .fa-wrench:before{content:""}#bootstrap-theme .fa-tasks:before{content:""}#bootstrap-theme .fa-filter:before{content:""}#bootstrap-theme .fa-briefcase:before{content:""}#bootstrap-theme .fa-arrows-alt:before{content:""}#bootstrap-theme .fa-group:before, #bootstrap-theme .fa-users:before{content:""}#bootstrap-theme .fa-chain:before, #bootstrap-theme .fa-link:before{content:""}#bootstrap-theme .fa-cloud:before{content:""}#bootstrap-theme .fa-flask:before{content:""}#bootstrap-theme .fa-cut:before, #bootstrap-theme .fa-scissors:before{content:""}#bootstrap-theme .fa-copy:before, #bootstrap-theme .fa-files-o:before{content:""}#bootstrap-theme .fa-paperclip:before{content:""}#bootstrap-theme .fa-save:before, #bootstrap-theme .fa-floppy-o:before{content:""}#bootstrap-theme .fa-square:before{content:""}#bootstrap-theme .fa-navicon:before, #bootstrap-theme .fa-reorder:before, #bootstrap-theme .fa-bars:before{content:""}#bootstrap-theme .fa-list-ul:before{content:""}#bootstrap-theme .fa-list-ol:before{content:""}#bootstrap-theme .fa-strikethrough:before{content:""}#bootstrap-theme .fa-underline:before{content:""}#bootstrap-theme .fa-table:before{content:""}#bootstrap-theme .fa-magic:before{content:""}#bootstrap-theme .fa-truck:before{content:""}#bootstrap-theme .fa-pinterest:before{content:""}#bootstrap-theme .fa-pinterest-square:before{content:""}#bootstrap-theme .fa-google-plus-square:before{content:""}#bootstrap-theme .fa-google-plus:before{content:""}#bootstrap-theme .fa-money:before{content:""}#bootstrap-theme .fa-caret-down:before{content:""}#bootstrap-theme .fa-caret-up:before{content:""}#bootstrap-theme .fa-caret-left:before{content:""}#bootstrap-theme .fa-caret-right:before{content:""}#bootstrap-theme .fa-columns:before{content:""}#bootstrap-theme .fa-unsorted:before, #bootstrap-theme .fa-sort:before{content:""}#bootstrap-theme .fa-sort-down:before, #bootstrap-theme .fa-sort-desc:before{content:""}#bootstrap-theme .fa-sort-up:before, #bootstrap-theme .fa-sort-asc:before{content:""}#bootstrap-theme .fa-envelope:before{content:""}#bootstrap-theme .fa-linkedin:before{content:""}#bootstrap-theme .fa-rotate-left:before, #bootstrap-theme .fa-undo:before{content:""}#bootstrap-theme .fa-legal:before, #bootstrap-theme .fa-gavel:before{content:""}#bootstrap-theme .fa-dashboard:before, #bootstrap-theme .fa-tachometer:before{content:""}#bootstrap-theme .fa-comment-o:before{content:""}#bootstrap-theme .fa-comments-o:before{content:""}#bootstrap-theme .fa-flash:before, #bootstrap-theme .fa-bolt:before{content:""}#bootstrap-theme .fa-sitemap:before{content:""}#bootstrap-theme .fa-umbrella:before{content:""}#bootstrap-theme .fa-paste:before, #bootstrap-theme .fa-clipboard:before{content:""}#bootstrap-theme .fa-lightbulb-o:before{content:""}#bootstrap-theme .fa-exchange:before{content:""}#bootstrap-theme .fa-cloud-download:before{content:""}#bootstrap-theme .fa-cloud-upload:before{content:""}#bootstrap-theme .fa-user-md:before{content:""}#bootstrap-theme .fa-stethoscope:before{content:""}#bootstrap-theme .fa-suitcase:before{content:""}#bootstrap-theme .fa-bell-o:before{content:""}#bootstrap-theme .fa-coffee:before{content:""}#bootstrap-theme .fa-cutlery:before{content:""}#bootstrap-theme .fa-file-text-o:before{content:""}#bootstrap-theme .fa-building-o:before{content:""}#bootstrap-theme .fa-hospital-o:before{content:""}#bootstrap-theme .fa-ambulance:before{content:""}#bootstrap-theme .fa-medkit:before{content:""}#bootstrap-theme .fa-fighter-jet:before{content:""}#bootstrap-theme .fa-beer:before{content:""}#bootstrap-theme .fa-h-square:before{content:""}#bootstrap-theme .fa-plus-square:before{content:""}#bootstrap-theme .fa-angle-double-left:before{content:""}#bootstrap-theme .fa-angle-double-right:before{content:""}#bootstrap-theme .fa-angle-double-up:before{content:""}#bootstrap-theme .fa-angle-double-down:before{content:""}#bootstrap-theme .fa-angle-left:before{content:""}#bootstrap-theme .fa-angle-right:before{content:""}#bootstrap-theme .fa-angle-up:before{content:""}#bootstrap-theme .fa-angle-down:before{content:""}#bootstrap-theme .fa-desktop:before{content:""}#bootstrap-theme .fa-laptop:before{content:""}#bootstrap-theme .fa-tablet:before{content:""}#bootstrap-theme .fa-mobile-phone:before, #bootstrap-theme .fa-mobile:before{content:""}#bootstrap-theme .fa-circle-o:before{content:""}#bootstrap-theme .fa-quote-left:before{content:""}#bootstrap-theme .fa-quote-right:before{content:""}#bootstrap-theme .fa-spinner:before{content:""}#bootstrap-theme .fa-circle:before{content:""}#bootstrap-theme .fa-mail-reply:before, #bootstrap-theme .fa-reply:before{content:""}#bootstrap-theme .fa-github-alt:before{content:""}#bootstrap-theme .fa-folder-o:before{content:""}#bootstrap-theme .fa-folder-open-o:before{content:""}#bootstrap-theme .fa-smile-o:before{content:""}#bootstrap-theme .fa-frown-o:before{content:""}#bootstrap-theme .fa-meh-o:before{content:""}#bootstrap-theme .fa-gamepad:before{content:""}#bootstrap-theme .fa-keyboard-o:before{content:""}#bootstrap-theme .fa-flag-o:before{content:""}#bootstrap-theme .fa-flag-checkered:before{content:""}#bootstrap-theme .fa-terminal:before{content:""}#bootstrap-theme .fa-code:before{content:""}#bootstrap-theme .fa-mail-reply-all:before, #bootstrap-theme .fa-reply-all:before{content:""}#bootstrap-theme .fa-star-half-empty:before, #bootstrap-theme .fa-star-half-full:before, #bootstrap-theme .fa-star-half-o:before{content:""}#bootstrap-theme .fa-location-arrow:before{content:""}#bootstrap-theme .fa-crop:before{content:""}#bootstrap-theme .fa-code-fork:before{content:""}#bootstrap-theme .fa-unlink:before, #bootstrap-theme .fa-chain-broken:before{content:""}#bootstrap-theme .fa-question:before{content:""}#bootstrap-theme .fa-info:before{content:""}#bootstrap-theme .fa-exclamation:before{content:""}#bootstrap-theme .fa-superscript:before{content:""}#bootstrap-theme .fa-subscript:before{content:""}#bootstrap-theme .fa-eraser:before{content:""}#bootstrap-theme .fa-puzzle-piece:before{content:""}#bootstrap-theme .fa-microphone:before{content:""}#bootstrap-theme .fa-microphone-slash:before{content:""}#bootstrap-theme .fa-shield:before{content:""}#bootstrap-theme .fa-calendar-o:before{content:""}#bootstrap-theme .fa-fire-extinguisher:before{content:""}#bootstrap-theme .fa-rocket:before{content:""}#bootstrap-theme .fa-maxcdn:before{content:""}#bootstrap-theme .fa-chevron-circle-left:before{content:""}#bootstrap-theme .fa-chevron-circle-right:before{content:""}#bootstrap-theme .fa-chevron-circle-up:before{content:""}#bootstrap-theme .fa-chevron-circle-down:before{content:""}#bootstrap-theme .fa-html5:before{content:""}#bootstrap-theme .fa-css3:before{content:""}#bootstrap-theme .fa-anchor:before{content:""}#bootstrap-theme .fa-unlock-alt:before{content:""}#bootstrap-theme .fa-bullseye:before{content:""}#bootstrap-theme .fa-ellipsis-h:before{content:""}#bootstrap-theme .fa-ellipsis-v:before{content:""}#bootstrap-theme .fa-rss-square:before{content:""}#bootstrap-theme .fa-play-circle:before{content:""}#bootstrap-theme .fa-ticket:before{content:""}#bootstrap-theme .fa-minus-square:before{content:""}#bootstrap-theme .fa-minus-square-o:before{content:""}#bootstrap-theme .fa-level-up:before{content:""}#bootstrap-theme .fa-level-down:before{content:""}#bootstrap-theme .fa-check-square:before{content:""}#bootstrap-theme .fa-pencil-square:before{content:""}#bootstrap-theme .fa-external-link-square:before{content:""}#bootstrap-theme .fa-share-square:before{content:""}#bootstrap-theme .fa-compass:before{content:""}#bootstrap-theme .fa-toggle-down:before, #bootstrap-theme .fa-caret-square-o-down:before{content:""}#bootstrap-theme .fa-toggle-up:before, #bootstrap-theme .fa-caret-square-o-up:before{content:""}#bootstrap-theme .fa-toggle-right:before, #bootstrap-theme .fa-caret-square-o-right:before{content:""}#bootstrap-theme .fa-euro:before, #bootstrap-theme .fa-eur:before{content:""}#bootstrap-theme .fa-gbp:before{content:""}#bootstrap-theme .fa-dollar:before, #bootstrap-theme .fa-usd:before{content:""}#bootstrap-theme .fa-rupee:before, #bootstrap-theme .fa-inr:before{content:""}#bootstrap-theme .fa-cny:before, #bootstrap-theme .fa-rmb:before, #bootstrap-theme .fa-yen:before, #bootstrap-theme .fa-jpy:before{content:""}#bootstrap-theme .fa-ruble:before, #bootstrap-theme .fa-rouble:before, #bootstrap-theme .fa-rub:before{content:""}#bootstrap-theme .fa-won:before, #bootstrap-theme .fa-krw:before{content:""}#bootstrap-theme .fa-bitcoin:before, #bootstrap-theme .fa-btc:before{content:""}#bootstrap-theme .fa-file:before{content:""}#bootstrap-theme .fa-file-text:before{content:""}#bootstrap-theme .fa-sort-alpha-asc:before{content:""}#bootstrap-theme .fa-sort-alpha-desc:before{content:""}#bootstrap-theme .fa-sort-amount-asc:before{content:""}#bootstrap-theme .fa-sort-amount-desc:before{content:""}#bootstrap-theme .fa-sort-numeric-asc:before{content:""}#bootstrap-theme .fa-sort-numeric-desc:before{content:""}#bootstrap-theme .fa-thumbs-up:before{content:""}#bootstrap-theme .fa-thumbs-down:before{content:""}#bootstrap-theme .fa-youtube-square:before{content:""}#bootstrap-theme .fa-youtube:before{content:""}#bootstrap-theme .fa-xing:before{content:""}#bootstrap-theme .fa-xing-square:before{content:""}#bootstrap-theme .fa-youtube-play:before{content:""}#bootstrap-theme .fa-dropbox:before{content:""}#bootstrap-theme .fa-stack-overflow:before{content:""}#bootstrap-theme .fa-instagram:before{content:""}#bootstrap-theme .fa-flickr:before{content:""}#bootstrap-theme .fa-adn:before{content:""}#bootstrap-theme .fa-bitbucket:before{content:""}#bootstrap-theme .fa-bitbucket-square:before{content:""}#bootstrap-theme .fa-tumblr:before{content:""}#bootstrap-theme .fa-tumblr-square:before{content:""}#bootstrap-theme .fa-long-arrow-down:before{content:""}#bootstrap-theme .fa-long-arrow-up:before{content:""}#bootstrap-theme .fa-long-arrow-left:before{content:""}#bootstrap-theme .fa-long-arrow-right:before{content:""}#bootstrap-theme .fa-apple:before{content:""}#bootstrap-theme .fa-windows:before{content:""}#bootstrap-theme .fa-android:before{content:""}#bootstrap-theme .fa-linux:before{content:""}#bootstrap-theme .fa-dribbble:before{content:""}#bootstrap-theme .fa-skype:before{content:""}#bootstrap-theme .fa-foursquare:before{content:""}#bootstrap-theme .fa-trello:before{content:""}#bootstrap-theme .fa-female:before{content:""}#bootstrap-theme .fa-male:before{content:""}#bootstrap-theme .fa-gittip:before, #bootstrap-theme .fa-gratipay:before{content:""}#bootstrap-theme .fa-sun-o:before{content:""}#bootstrap-theme .fa-moon-o:before{content:""}#bootstrap-theme .fa-archive:before{content:""}#bootstrap-theme .fa-bug:before{content:""}#bootstrap-theme .fa-vk:before{content:""}#bootstrap-theme .fa-weibo:before{content:""}#bootstrap-theme .fa-renren:before{content:""}#bootstrap-theme .fa-pagelines:before{content:""}#bootstrap-theme .fa-stack-exchange:before{content:""}#bootstrap-theme .fa-arrow-circle-o-right:before{content:""}#bootstrap-theme .fa-arrow-circle-o-left:before{content:""}#bootstrap-theme .fa-toggle-left:before, #bootstrap-theme .fa-caret-square-o-left:before{content:""}#bootstrap-theme .fa-dot-circle-o:before{content:""}#bootstrap-theme .fa-wheelchair:before{content:""}#bootstrap-theme .fa-vimeo-square:before{content:""}#bootstrap-theme .fa-turkish-lira:before, #bootstrap-theme .fa-try:before{content:""}#bootstrap-theme .fa-plus-square-o:before{content:""}#bootstrap-theme .fa-space-shuttle:before{content:""}#bootstrap-theme .fa-slack:before{content:""}#bootstrap-theme .fa-envelope-square:before{content:""}#bootstrap-theme .fa-wordpress:before{content:""}#bootstrap-theme .fa-openid:before{content:""}#bootstrap-theme .fa-institution:before, #bootstrap-theme .fa-bank:before, #bootstrap-theme .fa-university:before{content:""}#bootstrap-theme .fa-mortar-board:before, #bootstrap-theme .fa-graduation-cap:before{content:""}#bootstrap-theme .fa-yahoo:before{content:""}#bootstrap-theme .fa-google:before{content:""}#bootstrap-theme .fa-reddit:before{content:""}#bootstrap-theme .fa-reddit-square:before{content:""}#bootstrap-theme .fa-stumbleupon-circle:before{content:""}#bootstrap-theme .fa-stumbleupon:before{content:""}#bootstrap-theme .fa-delicious:before{content:""}#bootstrap-theme .fa-digg:before{content:""}#bootstrap-theme .fa-pied-piper-pp:before{content:""}#bootstrap-theme .fa-pied-piper-alt:before{content:""}#bootstrap-theme .fa-drupal:before{content:""}#bootstrap-theme .fa-joomla:before{content:""}#bootstrap-theme .fa-language:before{content:""}#bootstrap-theme .fa-fax:before{content:""}#bootstrap-theme .fa-building:before{content:""}#bootstrap-theme .fa-child:before{content:""}#bootstrap-theme .fa-paw:before{content:""}#bootstrap-theme .fa-spoon:before{content:""}#bootstrap-theme .fa-cube:before{content:""}#bootstrap-theme .fa-cubes:before{content:""}#bootstrap-theme .fa-behance:before{content:""}#bootstrap-theme .fa-behance-square:before{content:""}#bootstrap-theme .fa-steam:before{content:""}#bootstrap-theme .fa-steam-square:before{content:""}#bootstrap-theme .fa-recycle:before{content:""}#bootstrap-theme .fa-automobile:before, #bootstrap-theme .fa-car:before{content:""}#bootstrap-theme .fa-cab:before, #bootstrap-theme .fa-taxi:before{content:""}#bootstrap-theme .fa-tree:before{content:""}#bootstrap-theme .fa-spotify:before{content:""}#bootstrap-theme .fa-deviantart:before{content:""}#bootstrap-theme .fa-soundcloud:before{content:""}#bootstrap-theme .fa-database:before{content:""}#bootstrap-theme .fa-file-pdf-o:before{content:""}#bootstrap-theme .fa-file-word-o:before{content:""}#bootstrap-theme .fa-file-excel-o:before{content:""}#bootstrap-theme .fa-file-powerpoint-o:before{content:""}#bootstrap-theme .fa-file-photo-o:before, #bootstrap-theme .fa-file-picture-o:before, #bootstrap-theme .fa-file-image-o:before{content:""}#bootstrap-theme .fa-file-zip-o:before, #bootstrap-theme .fa-file-archive-o:before{content:""}#bootstrap-theme .fa-file-sound-o:before, #bootstrap-theme .fa-file-audio-o:before{content:""}#bootstrap-theme .fa-file-movie-o:before, #bootstrap-theme .fa-file-video-o:before{content:""}#bootstrap-theme .fa-file-code-o:before{content:""}#bootstrap-theme .fa-vine:before{content:""}#bootstrap-theme .fa-codepen:before{content:""}#bootstrap-theme .fa-jsfiddle:before{content:""}#bootstrap-theme .fa-life-bouy:before, #bootstrap-theme .fa-life-buoy:before, #bootstrap-theme .fa-life-saver:before, #bootstrap-theme .fa-support:before, #bootstrap-theme .fa-life-ring:before{content:""}#bootstrap-theme .fa-circle-o-notch:before{content:""}#bootstrap-theme .fa-ra:before, #bootstrap-theme .fa-resistance:before, #bootstrap-theme .fa-rebel:before{content:""}#bootstrap-theme .fa-ge:before, #bootstrap-theme .fa-empire:before{content:""}#bootstrap-theme .fa-git-square:before{content:""}#bootstrap-theme .fa-git:before{content:""}#bootstrap-theme .fa-y-combinator-square:before, #bootstrap-theme .fa-yc-square:before, #bootstrap-theme .fa-hacker-news:before{content:""}#bootstrap-theme .fa-tencent-weibo:before{content:""}#bootstrap-theme .fa-qq:before{content:""}#bootstrap-theme .fa-wechat:before, #bootstrap-theme .fa-weixin:before{content:""}#bootstrap-theme .fa-send:before, #bootstrap-theme .fa-paper-plane:before{content:""}#bootstrap-theme .fa-send-o:before, #bootstrap-theme .fa-paper-plane-o:before{content:""}#bootstrap-theme .fa-history:before{content:""}#bootstrap-theme .fa-circle-thin:before{content:""}#bootstrap-theme .fa-header:before{content:""}#bootstrap-theme .fa-paragraph:before{content:""}#bootstrap-theme .fa-sliders:before{content:""}#bootstrap-theme .fa-share-alt:before{content:""}#bootstrap-theme .fa-share-alt-square:before{content:""}#bootstrap-theme .fa-bomb:before{content:""}#bootstrap-theme .fa-soccer-ball-o:before, #bootstrap-theme .fa-futbol-o:before{content:""}#bootstrap-theme .fa-tty:before{content:""}#bootstrap-theme .fa-binoculars:before{content:""}#bootstrap-theme .fa-plug:before{content:""}#bootstrap-theme .fa-slideshare:before{content:""}#bootstrap-theme .fa-twitch:before{content:""}#bootstrap-theme .fa-yelp:before{content:""}#bootstrap-theme .fa-newspaper-o:before{content:""}#bootstrap-theme .fa-wifi:before{content:""}#bootstrap-theme .fa-calculator:before{content:""}#bootstrap-theme .fa-paypal:before{content:""}#bootstrap-theme .fa-google-wallet:before{content:""}#bootstrap-theme .fa-cc-visa:before{content:""}#bootstrap-theme .fa-cc-mastercard:before{content:""}#bootstrap-theme .fa-cc-discover:before{content:""}#bootstrap-theme .fa-cc-amex:before{content:""}#bootstrap-theme .fa-cc-paypal:before{content:""}#bootstrap-theme .fa-cc-stripe:before{content:""}#bootstrap-theme .fa-bell-slash:before{content:""}#bootstrap-theme .fa-bell-slash-o:before{content:""}#bootstrap-theme .fa-trash:before{content:""}#bootstrap-theme .fa-copyright:before{content:""}#bootstrap-theme .fa-at:before{content:""}#bootstrap-theme .fa-eyedropper:before{content:""}#bootstrap-theme .fa-paint-brush:before{content:""}#bootstrap-theme .fa-birthday-cake:before{content:""}#bootstrap-theme .fa-area-chart:before{content:""}#bootstrap-theme .fa-pie-chart:before{content:""}#bootstrap-theme .fa-line-chart:before{content:""}#bootstrap-theme .fa-lastfm:before{content:""}#bootstrap-theme .fa-lastfm-square:before{content:""}#bootstrap-theme .fa-toggle-off:before{content:""}#bootstrap-theme .fa-toggle-on:before{content:""}#bootstrap-theme .fa-bicycle:before{content:""}#bootstrap-theme .fa-bus:before{content:""}#bootstrap-theme .fa-ioxhost:before{content:""}#bootstrap-theme .fa-angellist:before{content:""}#bootstrap-theme .fa-cc:before{content:""}#bootstrap-theme .fa-shekel:before, #bootstrap-theme .fa-sheqel:before, #bootstrap-theme .fa-ils:before{content:""}#bootstrap-theme .fa-meanpath:before{content:""}#bootstrap-theme .fa-buysellads:before{content:""}#bootstrap-theme .fa-connectdevelop:before{content:""}#bootstrap-theme .fa-dashcube:before{content:""}#bootstrap-theme .fa-forumbee:before{content:""}#bootstrap-theme .fa-leanpub:before{content:""}#bootstrap-theme .fa-sellsy:before{content:""}#bootstrap-theme .fa-shirtsinbulk:before{content:""}#bootstrap-theme .fa-simplybuilt:before{content:""}#bootstrap-theme .fa-skyatlas:before{content:""}#bootstrap-theme .fa-cart-plus:before{content:""}#bootstrap-theme .fa-cart-arrow-down:before{content:""}#bootstrap-theme .fa-diamond:before{content:""}#bootstrap-theme .fa-ship:before{content:""}#bootstrap-theme .fa-user-secret:before{content:""}#bootstrap-theme .fa-motorcycle:before{content:""}#bootstrap-theme .fa-street-view:before{content:""}#bootstrap-theme .fa-heartbeat:before{content:""}#bootstrap-theme .fa-venus:before{content:""}#bootstrap-theme .fa-mars:before{content:""}#bootstrap-theme .fa-mercury:before{content:""}#bootstrap-theme .fa-intersex:before, #bootstrap-theme .fa-transgender:before{content:""}#bootstrap-theme .fa-transgender-alt:before{content:""}#bootstrap-theme .fa-venus-double:before{content:""}#bootstrap-theme .fa-mars-double:before{content:""}#bootstrap-theme .fa-venus-mars:before{content:""}#bootstrap-theme .fa-mars-stroke:before{content:""}#bootstrap-theme .fa-mars-stroke-v:before{content:""}#bootstrap-theme .fa-mars-stroke-h:before{content:""}#bootstrap-theme .fa-neuter:before{content:""}#bootstrap-theme .fa-genderless:before{content:""}#bootstrap-theme .fa-facebook-official:before{content:""}#bootstrap-theme .fa-pinterest-p:before{content:""}#bootstrap-theme .fa-whatsapp:before{content:""}#bootstrap-theme .fa-server:before{content:""}#bootstrap-theme .fa-user-plus:before{content:""}#bootstrap-theme .fa-user-times:before{content:""}#bootstrap-theme .fa-hotel:before, #bootstrap-theme .fa-bed:before{content:""}#bootstrap-theme .fa-viacoin:before{content:""}#bootstrap-theme .fa-train:before{content:""}#bootstrap-theme .fa-subway:before{content:""}#bootstrap-theme .fa-medium:before{content:""}#bootstrap-theme .fa-yc:before, #bootstrap-theme .fa-y-combinator:before{content:""}#bootstrap-theme .fa-optin-monster:before{content:""}#bootstrap-theme .fa-opencart:before{content:""}#bootstrap-theme .fa-expeditedssl:before{content:""}#bootstrap-theme .fa-battery-4:before, #bootstrap-theme .fa-battery:before, #bootstrap-theme .fa-battery-full:before{content:""}#bootstrap-theme .fa-battery-3:before, #bootstrap-theme .fa-battery-three-quarters:before{content:""}#bootstrap-theme .fa-battery-2:before, #bootstrap-theme .fa-battery-half:before{content:""}#bootstrap-theme .fa-battery-1:before, #bootstrap-theme .fa-battery-quarter:before{content:""}#bootstrap-theme .fa-battery-0:before, #bootstrap-theme .fa-battery-empty:before{content:""}#bootstrap-theme .fa-mouse-pointer:before{content:""}#bootstrap-theme .fa-i-cursor:before{content:""}#bootstrap-theme .fa-object-group:before{content:""}#bootstrap-theme .fa-object-ungroup:before{content:""}#bootstrap-theme .fa-sticky-note:before{content:""}#bootstrap-theme .fa-sticky-note-o:before{content:""}#bootstrap-theme .fa-cc-jcb:before{content:""}#bootstrap-theme .fa-cc-diners-club:before{content:""}#bootstrap-theme .fa-clone:before{content:""}#bootstrap-theme .fa-balance-scale:before{content:""}#bootstrap-theme .fa-hourglass-o:before{content:""}#bootstrap-theme .fa-hourglass-1:before, #bootstrap-theme .fa-hourglass-start:before{content:""}#bootstrap-theme .fa-hourglass-2:before, #bootstrap-theme .fa-hourglass-half:before{content:""}#bootstrap-theme .fa-hourglass-3:before, #bootstrap-theme .fa-hourglass-end:before{content:""}#bootstrap-theme .fa-hourglass:before{content:""}#bootstrap-theme .fa-hand-grab-o:before, #bootstrap-theme .fa-hand-rock-o:before{content:""}#bootstrap-theme .fa-hand-stop-o:before, #bootstrap-theme .fa-hand-paper-o:before{content:""}#bootstrap-theme .fa-hand-scissors-o:before{content:""}#bootstrap-theme .fa-hand-lizard-o:before{content:""}#bootstrap-theme .fa-hand-spock-o:before{content:""}#bootstrap-theme .fa-hand-pointer-o:before{content:""}#bootstrap-theme .fa-hand-peace-o:before{content:""}#bootstrap-theme .fa-trademark:before{content:""}#bootstrap-theme .fa-registered:before{content:""}#bootstrap-theme .fa-creative-commons:before{content:""}#bootstrap-theme .fa-gg:before{content:""}#bootstrap-theme .fa-gg-circle:before{content:""}#bootstrap-theme .fa-tripadvisor:before{content:""}#bootstrap-theme .fa-odnoklassniki:before{content:""}#bootstrap-theme .fa-odnoklassniki-square:before{content:""}#bootstrap-theme .fa-get-pocket:before{content:""}#bootstrap-theme .fa-wikipedia-w:before{content:""}#bootstrap-theme .fa-safari:before{content:""}#bootstrap-theme .fa-chrome:before{content:""}#bootstrap-theme .fa-firefox:before{content:""}#bootstrap-theme .fa-opera:before{content:""}#bootstrap-theme .fa-internet-explorer:before{content:""}#bootstrap-theme .fa-tv:before, #bootstrap-theme .fa-television:before{content:""}#bootstrap-theme .fa-contao:before{content:""}#bootstrap-theme .fa-500px:before{content:""}#bootstrap-theme .fa-amazon:before{content:""}#bootstrap-theme .fa-calendar-plus-o:before{content:""}#bootstrap-theme .fa-calendar-minus-o:before{content:""}#bootstrap-theme .fa-calendar-times-o:before{content:""}#bootstrap-theme .fa-calendar-check-o:before{content:""}#bootstrap-theme .fa-industry:before{content:""}#bootstrap-theme .fa-map-pin:before{content:""}#bootstrap-theme .fa-map-signs:before{content:""}#bootstrap-theme .fa-map-o:before{content:""}#bootstrap-theme .fa-map:before{content:""}#bootstrap-theme .fa-commenting:before{content:""}#bootstrap-theme .fa-commenting-o:before{content:""}#bootstrap-theme .fa-houzz:before{content:""}#bootstrap-theme .fa-vimeo:before{content:""}#bootstrap-theme .fa-black-tie:before{content:""}#bootstrap-theme .fa-fonticons:before{content:""}#bootstrap-theme .fa-reddit-alien:before{content:""}#bootstrap-theme .fa-edge:before{content:""}#bootstrap-theme .fa-credit-card-alt:before{content:""}#bootstrap-theme .fa-codiepie:before{content:""}#bootstrap-theme .fa-modx:before{content:""}#bootstrap-theme .fa-fort-awesome:before{content:""}#bootstrap-theme .fa-usb:before{content:""}#bootstrap-theme .fa-product-hunt:before{content:""}#bootstrap-theme .fa-mixcloud:before{content:""}#bootstrap-theme .fa-scribd:before{content:""}#bootstrap-theme .fa-pause-circle:before{content:""}#bootstrap-theme .fa-pause-circle-o:before{content:""}#bootstrap-theme .fa-stop-circle:before{content:""}#bootstrap-theme .fa-stop-circle-o:before{content:""}#bootstrap-theme .fa-shopping-bag:before{content:""}#bootstrap-theme .fa-shopping-basket:before{content:""}#bootstrap-theme .fa-hashtag:before{content:""}#bootstrap-theme .fa-bluetooth:before{content:""}#bootstrap-theme .fa-bluetooth-b:before{content:""}#bootstrap-theme .fa-percent:before{content:""}#bootstrap-theme .fa-gitlab:before{content:""}#bootstrap-theme .fa-wpbeginner:before{content:""}#bootstrap-theme .fa-wpforms:before{content:""}#bootstrap-theme .fa-envira:before{content:""}#bootstrap-theme .fa-universal-access:before{content:""}#bootstrap-theme .fa-wheelchair-alt:before{content:""}#bootstrap-theme .fa-question-circle-o:before{content:""}#bootstrap-theme .fa-blind:before{content:""}#bootstrap-theme .fa-audio-description:before{content:""}#bootstrap-theme .fa-volume-control-phone:before{content:""}#bootstrap-theme .fa-braille:before{content:""}#bootstrap-theme .fa-assistive-listening-systems:before{content:""}#bootstrap-theme .fa-asl-interpreting:before, #bootstrap-theme .fa-american-sign-language-interpreting:before{content:""}#bootstrap-theme .fa-deafness:before, #bootstrap-theme .fa-hard-of-hearing:before, #bootstrap-theme .fa-deaf:before{content:""}#bootstrap-theme .fa-glide:before{content:""}#bootstrap-theme .fa-glide-g:before{content:""}#bootstrap-theme .fa-signing:before, #bootstrap-theme .fa-sign-language:before{content:""}#bootstrap-theme .fa-low-vision:before{content:""}#bootstrap-theme .fa-viadeo:before{content:""}#bootstrap-theme .fa-viadeo-square:before{content:""}#bootstrap-theme .fa-snapchat:before{content:""}#bootstrap-theme .fa-snapchat-ghost:before{content:""}#bootstrap-theme .fa-snapchat-square:before{content:""}#bootstrap-theme .fa-pied-piper:before{content:""}#bootstrap-theme .fa-first-order:before{content:""}#bootstrap-theme .fa-yoast:before{content:""}#bootstrap-theme .fa-themeisle:before{content:""}#bootstrap-theme .fa-google-plus-circle:before, #bootstrap-theme .fa-google-plus-official:before{content:""}#bootstrap-theme .fa-fa:before, #bootstrap-theme .fa-font-awesome:before{content:""}#bootstrap-theme .fa-handshake-o:before{content:""}#bootstrap-theme .fa-envelope-open:before{content:""}#bootstrap-theme .fa-envelope-open-o:before{content:""}#bootstrap-theme .fa-linode:before{content:""}#bootstrap-theme .fa-address-book:before{content:""}#bootstrap-theme .fa-address-book-o:before{content:""}#bootstrap-theme .fa-vcard:before, #bootstrap-theme .fa-address-card:before{content:""}#bootstrap-theme .fa-vcard-o:before, #bootstrap-theme .fa-address-card-o:before{content:""}#bootstrap-theme .fa-user-circle:before{content:""}#bootstrap-theme .fa-user-circle-o:before{content:""}#bootstrap-theme .fa-user-o:before{content:""}#bootstrap-theme .fa-id-badge:before{content:""}#bootstrap-theme .fa-drivers-license:before, #bootstrap-theme .fa-id-card:before{content:""}#bootstrap-theme .fa-drivers-license-o:before, #bootstrap-theme .fa-id-card-o:before{content:""}#bootstrap-theme .fa-quora:before{content:""}#bootstrap-theme .fa-free-code-camp:before{content:""}#bootstrap-theme .fa-telegram:before{content:""}#bootstrap-theme .fa-thermometer-4:before, #bootstrap-theme .fa-thermometer:before, #bootstrap-theme .fa-thermometer-full:before{content:""}#bootstrap-theme .fa-thermometer-3:before, #bootstrap-theme .fa-thermometer-three-quarters:before{content:""}#bootstrap-theme .fa-thermometer-2:before, #bootstrap-theme .fa-thermometer-half:before{content:""}#bootstrap-theme .fa-thermometer-1:before, #bootstrap-theme .fa-thermometer-quarter:before{content:""}#bootstrap-theme .fa-thermometer-0:before, #bootstrap-theme .fa-thermometer-empty:before{content:""}#bootstrap-theme .fa-shower:before{content:""}#bootstrap-theme .fa-bathtub:before, #bootstrap-theme .fa-s15:before, #bootstrap-theme .fa-bath:before{content:""}#bootstrap-theme .fa-podcast:before{content:""}#bootstrap-theme .fa-window-maximize:before{content:""}#bootstrap-theme .fa-window-minimize:before{content:""}#bootstrap-theme .fa-window-restore:before{content:""}#bootstrap-theme .fa-times-rectangle:before, #bootstrap-theme .fa-window-close:before{content:""}#bootstrap-theme .fa-times-rectangle-o:before, #bootstrap-theme .fa-window-close-o:before{content:""}#bootstrap-theme .fa-bandcamp:before{content:""}#bootstrap-theme .fa-grav:before{content:""}#bootstrap-theme .fa-etsy:before{content:""}#bootstrap-theme .fa-imdb:before{content:""}#bootstrap-theme .fa-ravelry:before{content:""}#bootstrap-theme .fa-eercast:before{content:""}#bootstrap-theme .fa-microchip:before{content:""}#bootstrap-theme .fa-snowflake-o:before{content:""}#bootstrap-theme .fa-superpowers:before{content:""}#bootstrap-theme .fa-wpexplorer:before{content:""}#bootstrap-theme .fa-meetup:before{content:""}#bootstrap-theme .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}#bootstrap-theme .sr-only-focusable:active, #bootstrap-theme .sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}#bootstrap-theme .ps-container{-ms-touch-action:none;touch-action:none;overflow:hidden !important;-ms-overflow-style:none}@supports (-ms-overflow-style: none){#bootstrap-theme .ps-container{overflow:auto !important}}@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none){#bootstrap-theme .ps-container{overflow:auto !important}}#bootstrap-theme .ps-container.ps-active-x>.ps-scrollbar-x-rail, #bootstrap-theme .ps-container.ps-active-y>.ps-scrollbar-y-rail{display:block;background-color:rgba(0,0,0,0)}#bootstrap-theme .ps-container.ps-in-scrolling{pointer-events:none}#bootstrap-theme .ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:.9}#bootstrap-theme .ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999}#bootstrap-theme .ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:.9}#bootstrap-theme .ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999}#bootstrap-theme .ps-container>.ps-scrollbar-x-rail{display:none;position:absolute;border-radius:4px;opacity:0;transition:background-color .2s linear, opacity .2s linear;bottom:3px;height:8px}#bootstrap-theme .ps-container>.ps-scrollbar-x-rail>.ps-scrollbar-x{position:absolute;background-color:#aaa;border-radius:4px;transition:background-color .2s linear;bottom:0;height:8px}#bootstrap-theme .ps-container>.ps-scrollbar-y-rail{display:none;position:absolute;border-radius:4px;opacity:0;transition:background-color .2s linear, opacity .2s linear;right:3px;width:8px}#bootstrap-theme .ps-container>.ps-scrollbar-y-rail>.ps-scrollbar-y{position:absolute;background-color:#aaa;border-radius:4px;transition:background-color .2s linear;right:0;width:8px}#bootstrap-theme .ps-container:hover.ps-in-scrolling{pointer-events:none}#bootstrap-theme .ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:.9}#bootstrap-theme .ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999}#bootstrap-theme .ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:.9}#bootstrap-theme .ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999}#bootstrap-theme .ps-container:hover>.ps-scrollbar-x-rail, #bootstrap-theme .ps-container:hover>.ps-scrollbar-y-rail{opacity:.6}#bootstrap-theme .ps-container:hover>.ps-scrollbar-x-rail:hover{background-color:#eee;opacity:.9}#bootstrap-theme .ps-container:hover>.ps-scrollbar-x-rail:hover>.ps-scrollbar-x{background-color:#999}#bootstrap-theme .ps-container:hover>.ps-scrollbar-y-rail:hover{background-color:#eee;opacity:.9}#bootstrap-theme .ps-container:hover>.ps-scrollbar-y-rail:hover>.ps-scrollbar-y{background-color:#999}#bootstrap-theme .form-control .select2-choice{border:0;border-radius:2px;text-decoration:none !important}#bootstrap-theme .form-control .select2-choice .select2-arrow{border-radius:0 2px 2px 0;background:transparent;border-left:0 none;right:4px;top:2px}#bootstrap-theme .form-control.select2-container{padding:0;height:auto}#bootstrap-theme .form-control.select2-container.select2-dropdown-open{border-color:#5897FB;border-radius:3px 3px 0 0}#bootstrap-theme .form-control .select2-container.select2-dropdown-open .select2-choices{border-radius:3px 3px 0 0}#bootstrap-theme .form-control.select2-container .select2-choices{border:0 !important;border-radius:3px;padding-left:4px;margin-bottom:0}#bootstrap-theme .control-group.warning .select2-container .select2-choice, #bootstrap-theme .control-group.warning .select2-container .select2-choices, #bootstrap-theme .control-group.warning .select2-container-active .select2-choice, #bootstrap-theme .control-group.warning .select2-container-active .select2-choices, #bootstrap-theme .control-group.warning .select2-dropdown-open.select2-drop-above .select2-choice, #bootstrap-theme .control-group.warning .select2-dropdown-open.select2-drop-above .select2-choices, #bootstrap-theme .control-group.warning .select2-container-multi.select2-container-active .select2-choices{border:1px solid #C09853 !important}#bootstrap-theme .control-group.warning .select2-container .select2-choice div{border-left:1px solid #C09853 !important;background:#FCF8E3 !important}#bootstrap-theme .control-group.error .select2-container .select2-choice, #bootstrap-theme .control-group.error .select2-container .select2-choices, #bootstrap-theme .control-group.error .select2-container-active .select2-choice, #bootstrap-theme .control-group.error .select2-container-active .select2-choices, #bootstrap-theme .control-group.error .select2-dropdown-open.select2-drop-above .select2-choice, #bootstrap-theme .control-group.error .select2-dropdown-open.select2-drop-above .select2-choices, #bootstrap-theme .control-group.error .select2-container-multi.select2-container-active .select2-choices{border:1px solid #B94A48 !important}#bootstrap-theme .control-group.error .select2-container .select2-choice div{border-left:1px solid #B94A48 !important;background:#F2DEDE !important}#bootstrap-theme .control-group.info .select2-container .select2-choice, #bootstrap-theme .control-group.info .select2-container .select2-choices, #bootstrap-theme .control-group.info .select2-container-active .select2-choice, #bootstrap-theme .control-group.info .select2-container-active .select2-choices, #bootstrap-theme .control-group.info .select2-dropdown-open.select2-drop-above .select2-choice, #bootstrap-theme .control-group.info .select2-dropdown-open.select2-drop-above .select2-choices, #bootstrap-theme .control-group.info .select2-container-multi.select2-container-active .select2-choices{border:1px solid #3A87AD !important}#bootstrap-theme .control-group.info .select2-container .select2-choice div{border-left:1px solid #3A87AD !important;background:#D9EDF7 !important}#bootstrap-theme .control-group.success .select2-container .select2-choice, #bootstrap-theme .control-group.success .select2-container .select2-choices, #bootstrap-theme .control-group.success .select2-container-active .select2-choice, #bootstrap-theme .control-group.success .select2-container-active .select2-choices, #bootstrap-theme .control-group.success .select2-dropdown-open.select2-drop-above .select2-choice, #bootstrap-theme .control-group.success .select2-dropdown-open.select2-drop-above .select2-choices, #bootstrap-theme .control-group.success .select2-container-multi.select2-container-active .select2-choices{border:1px solid #468847 !important}#bootstrap-theme .control-group.success .select2-container .select2-choice div{border-left:1px solid #468847 !important;background:#DFF0D8 !important}#bootstrap-theme .fa-0_5x{font-size:9px;font-weight:300}#bootstrap-theme .fa-1x{font-size:13px}#bootstrap-theme .fa-color-primary{color:#0071bd}@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;src:url("../fonts/open-sans/OpenSans-Regular-webfont.eot");src:url("../fonts/open-sans/OpenSans-Regular-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/open-sans/OpenSans-Regular-webfont.woff") format("woff"),url("../fonts/open-sans/OpenSans-Regular-webfont.ttf") format("truetype"),url("../fonts/open-sans/OpenSans-Regular-webfont.svg#open_sansregular") format("svg")}@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;src:url("../fonts/open-sans/OpenSans-Semibold-webfont.eot");src:url("../fonts/open-sans/OpenSans-Semibold-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/open-sans/OpenSans-Semibold-webfont.woff") format("woff"),url("../fonts/open-sans/OpenSans-Semibold-webfont.ttf") format("truetype"),url("../fonts/open-sans/OpenSans-Semibold-webfont.svg#open_sanssemibold") format("svg")}@font-face{font-family:'Open Sans';font-style:normal;font-weight:700;src:url("../fonts/open-sans/OpenSans-Bold-webfont.eot");src:url("../fonts/open-sans/OpenSans-Bold-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/open-sans/OpenSans-Bold-webfont.woff") format("woff"),url("../fonts/open-sans/OpenSans-Bold-webfont.ttf") format("truetype"),url("../fonts/open-sans/OpenSans-Bold-webfont.svg#open_sansbold") format("svg")}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;src:url("../fonts/open-sans/OpenSans-Italic-webfont.eot");src:url("../fonts/open-sans/OpenSans-Italic-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/open-sans/OpenSans-Italic-webfont.woff") format("woff"),url("../fonts/open-sans/OpenSans-Italic-webfont.ttf") format("truetype"),url("../fonts/open-sans/OpenSans-Italic-webfont.svg#open_sansitalic") format("svg")}html#bootstrap-theme.overlay-open .navbar-fixed-top, #bootstrap-theme.overlay-open .navbar-fixed-top{z-index:400}html#bootstrap-theme.js fieldset.collapsed, #bootstrap-theme.js fieldset.collapsed{height:auto}html#bootstrap-theme.js input.form-autocomplete, #bootstrap-theme.js input.form-autocomplete{background-image:none}body#bootstrap-theme, #bootstrap-theme{position:relative}body#bootstrap-theme.admin-expanded.admin-vertical.admin-nw .navbar, #bootstrap-theme.admin-expanded.admin-vertical.admin-nw .navbar, body#bootstrap-theme.admin-expanded.admin-vertical.admin-sw .navbar, #bootstrap-theme.admin-expanded.admin-vertical.admin-sw .navbar{margin-left:260px}body#bootstrap-theme.navbar-is-fixed-top, #bootstrap-theme.navbar-is-fixed-top{padding-top:64px !important}body#bootstrap-theme.navbar-is-fixed-bottom, #bootstrap-theme.navbar-is-fixed-bottom{padding-bottom:64px !important}body#bootstrap-theme.toolbar, #bootstrap-theme.toolbar{padding-top:30px !important}body#bootstrap-theme.toolbar .navbar-fixed-top, #bootstrap-theme.toolbar .navbar-fixed-top{top:30px}body#bootstrap-theme.toolbar.navbar-is-fixed-top, #bootstrap-theme.toolbar.navbar-is-fixed-top{padding-top:94px !important}body#bootstrap-theme.toolbar-drawer, #bootstrap-theme.toolbar-drawer{padding-top:64px !important}body#bootstrap-theme.toolbar-drawer .navbar-fixed-top, #bootstrap-theme.toolbar-drawer .navbar-fixed-top{top:64px}body#bootstrap-theme.toolbar-drawer.navbar-is-fixed-top, #bootstrap-theme.toolbar-drawer.navbar-is-fixed-top{padding-top:128px !important}body#bootstrap-theme.admin-menu .navbar-fixed-top, #bootstrap-theme.admin-menu .navbar-fixed-top{top:29px}body#bootstrap-theme.admin-menu.navbar-is-fixed-top, #bootstrap-theme.admin-menu.navbar-is-fixed-top{padding-top:93px !important}body#bootstrap-theme div#admin-toolbar, #bootstrap-theme div#admin-toolbar{z-index:1600}body#bootstrap-theme #toolbar, #bootstrap-theme #toolbar, body#bootstrap-theme #admin-menu, #bootstrap-theme #admin-menu, body#bootstrap-theme #admin-toolbar, #bootstrap-theme #admin-toolbar{-webkit-box-shadow:none;box-shadow:none}body#bootstrap-theme #admin-menu, #bootstrap-theme #admin-menu{margin:0;padding:0;position:fixed;z-index:1600}body#bootstrap-theme #admin-menu .dropdown li, #bootstrap-theme #admin-menu .dropdown li{line-height:normal}#bootstrap-theme dt, #bootstrap-theme dd{margin:0}#bootstrap-theme .navbar.container{margin-top:20px}@media screen and (min-width: screen-sm-min){#bootstrap-theme .navbar.container{max-width:706px}}@media screen and (min-width: screen-md-min){#bootstrap-theme .navbar.container{max-width:926px}}@media screen and (min-width: screen-lg-min){#bootstrap-theme .navbar.container{max-width:1126px}}#bootstrap-theme .navbar.container>.container{margin:0;padding:0;width:auto}#bootstrap-theme #overlay-container, #bootstrap-theme .overlay-modal-background, #bootstrap-theme .overlay-element{z-index:1500}#bootstrap-theme #toolbar{z-index:1600}#bootstrap-theme #toolbar ul{padding:0;margin:0}#bootstrap-theme #toolbar ul a, #bootstrap-theme #toolbar ul a:hover{color:#fff;text-decoration:none}#bootstrap-theme .modal{z-index:1620}#bootstrap-theme .modal-dialog{z-index:1630}#bootstrap-theme .modal-backdrop{z-index:1610}#bootstrap-theme .footer{margin-top:45px;padding-top:35px;padding-bottom:36px;border-top:1px solid #E5E5E5}#bootstrap-theme .element-invisible{margin:0;padding:0;width:1px}#bootstrap-theme .navbar .logo{margin-right:-15px;padding-left:15px;padding-right:15px}@media screen and (min-width: screen-sm-min){#bootstrap-theme .navbar .logo{margin-right:0;padding-left:0}}#bootstrap-theme ul.secondary{float:left}@media screen and (min-width: screen-sm-min){#bootstrap-theme ul.secondary{float:right}}#bootstrap-theme .page-header{margin-top:0}#bootstrap-theme .block:first-child h2.block-title{margin-top:0}#bootstrap-theme p:last-child{margin-bottom:0}#bootstrap-theme .region-help>.glyphicon{font-size:17px;float:left;margin:-0.05em 0.5em 0 0}#bootstrap-theme .region-help .block{overflow:hidden}#bootstrap-theme form#search-block-form{margin:0}#bootstrap-theme .navbar #block-search-form{float:none;margin:5px 0 5px 5px}@media screen and (min-width: screen-md-min){#bootstrap-theme .navbar #block-search-form{float:right}}#bootstrap-theme .navbar-search .control-group{margin-bottom:0px}#bootstrap-theme ul.action-links{margin:10px 0;padding:0}#bootstrap-theme ul.action-links li{display:inline;margin:0;padding:0 4px 0 0}#bootstrap-theme ul.action-links .glyphicon{padding-right:0.5em}#bootstrap-theme input, #bootstrap-theme textarea, #bootstrap-theme select, #bootstrap-theme .uneditable-input{max-width:100%;width:auto}#bootstrap-theme input.error{color:#cf3458;border-color:#cf3458}#bootstrap-theme fieldset legend.panel-heading{font-size:13px;float:left;line-height:1em;margin:0}#bootstrap-theme fieldset .panel-body{clear:both}#bootstrap-theme fieldset .panel-heading a.panel-title{color:inherit;display:block;margin:-10px -15px;padding:10px 15px}#bootstrap-theme fieldset .panel-heading a.panel-title:hover{text-decoration:none}#bootstrap-theme .form-actions{clear:both}#bootstrap-theme .resizable-textarea textarea{border-radius:2px 2px 0 0}#bootstrap-theme .radio:first-child, #bootstrap-theme .checkbox:first-child{margin-top:0}#bootstrap-theme .radio:last-child, #bootstrap-theme .checkbox:last-child{margin-bottom:0}#bootstrap-theme .help-block, #bootstrap-theme .control-group .help-inline{color:#e8eef0;font-size:12px;margin:5px 0 10px;padding:0}#bootstrap-theme .panel-heading{display:block}#bootstrap-theme a.tabledrag-handle .handle{height:auto;width:auto}#bootstrap-theme .error{color:#cf3458}#bootstrap-theme div.error, #bootstrap-theme table tr.error{background-color:#f2dede;color:#cf3458}#bootstrap-theme .control-group.error{background:none}#bootstrap-theme .control-group.error label, #bootstrap-theme .control-group.error .control-label{color:#cf3458;font-weight:600}#bootstrap-theme .control-group.error input, #bootstrap-theme .control-group.error textarea, #bootstrap-theme .control-group.error select, #bootstrap-theme .control-group.error .uneditable-input{color:#464354;border:1px solid #c2cfd8}#bootstrap-theme .control-group.error .help-block, #bootstrap-theme .control-group.error .help-inline{color:#4d4d69}#bootstrap-theme .list-inline>li.first{padding-left:0}#bootstrap-theme .nav-tabs{margin-bottom:10px}#bootstrap-theme ul{margin:initial;padding-left:40px}#bootstrap-theme ul li.collapsed, #bootstrap-theme ul li.expanded, #bootstrap-theme ul li.leaf{list-style:none;list-style-image:none}#bootstrap-theme .tabs--secondary{margin:0 0 10px}#bootstrap-theme .submitted{margin-bottom:1em;font-style:italic;font-weight:normal;color:#777}#bootstrap-theme .password-strength{width:17em;float:right;margin-top:1.4em}#bootstrap-theme .password-strength-title{display:inline}#bootstrap-theme .password-strength-text{float:right;font-weight:bold}#bootstrap-theme .password-indicator{background-color:#8e8ea0;height:0.3em;width:100%}#bootstrap-theme .password-indicator div{height:100%;width:0%;background-color:#9494a5}#bootstrap-theme input.password-confirm, #bootstrap-theme input.password-field{width:16em;margin-bottom:0.4em}#bootstrap-theme div.password-confirm{float:right;margin-top:1.5em;visibility:hidden;width:17em}#bootstrap-theme div.form-item div.password-suggestions{padding:0.2em 0.5em;margin:0.7em 0;width:38.5em;border:1px solid #B4B4B4}#bootstrap-theme div.password-suggestions ul{margin-bottom:0}#bootstrap-theme .confirm-parent, #bootstrap-theme .password-parent{clear:left;margin:0;width:36.3em}#bootstrap-theme .progress-wrapper .progress{margin-bottom:10px}#bootstrap-theme .pagination ul>li>a.progress-disabled{float:left}#bootstrap-theme .form-autocomplete .glyphicon{color:#e8eef0;font-size:120%}#bootstrap-theme .form-autocomplete .glyphicon.glyphicon-spin{color:#0071bd}#bootstrap-theme .form-autocomplete .input-group-addon{background-color:#fff}#bootstrap-theme .ajax-progress .glyphicon{font-size:90%;margin:0 -.25em 0 0.5em}#bootstrap-theme .glyphicon-spin{display:inline-block;-moz-animation:spin 1s infinite linear;-o-animation:spin 1s infinite linear;-webkit-animation:spin 1s infinite linear;animation:spin 1s infinite linear}#bootstrap-theme a .glyphicon-spin{display:inline-block;text-decoration:none}@-moz-keyframes spin{#bootstrap-theme 0%{-moz-transform:rotate(0deg)}#bootstrap-theme 100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{#bootstrap-theme 0%{-webkit-transform:rotate(0deg)}#bootstrap-theme 100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{#bootstrap-theme 0%{-o-transform:rotate(0deg)}#bootstrap-theme 100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{#bootstrap-theme 0%{-ms-transform:rotate(0deg)}#bootstrap-theme 100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}#bootstrap-theme .glyphicon-refresh{-webkit-transform-origin:50% 45%;-moz-transform-origin:50% 45%;-ms-transform-origin:50% 45%;-o-transform-origin:50% 45%;transform-origin:50% 45%}#bootstrap-theme .tabbable{margin-bottom:20px}#bootstrap-theme .tabs-below>.nav-tabs, #bootstrap-theme .tabs-left>.nav-tabs, #bootstrap-theme .tabs-right>.nav-tabs{border-bottom:0}#bootstrap-theme .tabs-below>.nav-tabs .summary, #bootstrap-theme .tabs-left>.nav-tabs .summary, #bootstrap-theme .tabs-right>.nav-tabs .summary{color:#bcbcc8;font-size:12px}#bootstrap-theme .tab-pane>.panel-heading{display:none}#bootstrap-theme .tab-content>.active{display:block}#bootstrap-theme .tabs-below>.nav-tabs{border-top:1px solid #d3dee2}#bootstrap-theme .tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}#bootstrap-theme .tabs-below>.nav-tabs>li>a{border-radius:0 0 2px 2px}#bootstrap-theme .tabs-below>.nav-tabs>li>a:hover, #bootstrap-theme .tabs-below>.nav-tabs>li>a:focus{border-top-color:#d3dee2;border-bottom-color:transparent}#bootstrap-theme .tabs-below>.nav-tabs>.active>a, #bootstrap-theme .tabs-below>.nav-tabs>.active>a:hover, #bootstrap-theme .tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #d3dee2 #d3dee2 #d3dee2}#bootstrap-theme .tabs-left>.nav-tabs, #bootstrap-theme .tabs-right>.nav-tabs{padding-bottom:20px;width:220px}#bootstrap-theme .tabs-left>.nav-tabs>li, #bootstrap-theme .tabs-right>.nav-tabs>li{float:none}#bootstrap-theme .tabs-left>.nav-tabs>li:focus, #bootstrap-theme .tabs-right>.nav-tabs>li:focus{outline:0}#bootstrap-theme .tabs-left>.nav-tabs>li>a, #bootstrap-theme .tabs-right>.nav-tabs>li>a{margin-right:0;margin-bottom:3px}#bootstrap-theme .tabs-left>.nav-tabs>li>a:focus, #bootstrap-theme .tabs-right>.nav-tabs>li>a:focus{outline:0}#bootstrap-theme .tabs-left>.tab-content, #bootstrap-theme .tabs-right>.tab-content{border-radius:0 2px 2px 2px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05);border:1px solid #d3dee2;overflow:hidden;padding:10px 15px}#bootstrap-theme .tabs-left>.nav-tabs{float:left;margin-right:-1px}#bootstrap-theme .tabs-left>.nav-tabs>li>a{border-radius:2px 0 0 2px}#bootstrap-theme .tabs-left>.nav-tabs>li>a:hover, #bootstrap-theme .tabs-left>.nav-tabs>li>a:focus{border-color:#f3f6f7 #d3dee2 #f3f6f7 #f3f6f7}#bootstrap-theme .tabs-left>.nav-tabs>.active>a, #bootstrap-theme .tabs-left>.nav-tabs>.active>a:hover, #bootstrap-theme .tabs-left>.nav-tabs>.active>a:focus{border-color:#d3dee2 transparent #d3dee2 #d3dee2;-webkit-box-shadow:-1px 1px 1px rgba(0,0,0,0.05);box-shadow:-1px 1px 1px rgba(0,0,0,0.05)}#bootstrap-theme .tabs-right>.nav-tabs{float:right;margin-left:-1px}#bootstrap-theme .tabs-right>.nav-tabs>li>a{border-radius:0 2px 2px 0}#bootstrap-theme .tabs-right>.nav-tabs>li>a:hover, #bootstrap-theme .tabs-right>.nav-tabs>li>a:focus{border-color:#f3f6f7 #f3f6f7 #f3f6f7 #d3dee2;-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.05);box-shadow:1px 1px 1px rgba(0,0,0,0.05)}#bootstrap-theme .tabs-right>.nav-tabs>.active>a, #bootstrap-theme .tabs-right>.nav-tabs>.active>a:hover, #bootstrap-theme .tabs-right>.nav-tabs>.active>a:focus{border-color:#d3dee2 #d3dee2 #d3dee2 transparent}#bootstrap-theme th.checkbox, #bootstrap-theme td.checkbox, #bootstrap-theme th.radio, #bootstrap-theme td.radio{display:table-cell}#bootstrap-theme .views-display-settings .label{font-size:100%;color:#666666}#bootstrap-theme .views-display-settings .footer{padding:0;margin:4px 0 0 0}#bootstrap-theme table .radio input[type="radio"], #bootstrap-theme table .checkbox input[type="checkbox"]{max-width:inherit}#bootstrap-theme .alert a{font-weight:bold}#bootstrap-theme .alert-success a{color:#2e2c38}#bootstrap-theme .alert-info a{color:#2e2c38}#bootstrap-theme .alert-warning a{color:#2e2c38}#bootstrap-theme .alert-danger a{color:#2e2c38}#bootstrap-theme h3{background-color:rgba(0,0,0,0);background-color:initial;border:0;border:initial;border-radius:0;border-radius:initial;box-shadow:none;box-shadow:initial;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-family:initial;padding:0;padding:initial;margin:0;margin:initial;font-size:16px;font-weight:400}#bootstrap-theme table{border:0;border:initial;margin:0;margin:initial;font-size:inherit}#bootstrap-theme table td{border:0;border:initial;color:inherit}#bootstrap-theme table th{background-color:inherit;background-color:initial;border:0;border:initial;text-transform:none;text-transform:initial;color:inherit;font-size:inherit;font-weight:600}#bootstrap-theme .collapsed{background:none;background:initial;cursor:auto;cursor:initial;padding:0;padding:initial}#bootstrap-theme .panel{position:static;position:initial;padding:0;padding:initial;width:auto;width:initial;z-index:auto;z-index:initial;display:block}#bootstrap-theme .crm-button .crm-i{left:0.6em;top:0.6em}#bootstrap-theme .crm-select2{width:15em}#bootstrap-theme label .crm-error-label{padding:0;padding:initial;font-size:13px}html#bootstrap-theme, #bootstrap-theme{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body#bootstrap-theme, #bootstrap-theme{margin:0}#bootstrap-theme article, #bootstrap-theme aside, #bootstrap-theme details, #bootstrap-theme figcaption, #bootstrap-theme figure, #bootstrap-theme footer, #bootstrap-theme header, #bootstrap-theme hgroup, #bootstrap-theme main, #bootstrap-theme menu, #bootstrap-theme nav, #bootstrap-theme section, #bootstrap-theme summary{display:block}#bootstrap-theme audio, #bootstrap-theme canvas, #bootstrap-theme progress, #bootstrap-theme video{display:inline-block;vertical-align:baseline}#bootstrap-theme audio:not([controls]){display:none;height:0}#bootstrap-theme [hidden], #bootstrap-theme template{display:none}#bootstrap-theme a{background-color:transparent}#bootstrap-theme a:active, #bootstrap-theme a:hover{outline:0}#bootstrap-theme abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}#bootstrap-theme b, #bootstrap-theme strong{font-weight:bold}#bootstrap-theme dfn{font-style:italic}#bootstrap-theme h1{font-size:2em;margin:0.67em 0}#bootstrap-theme mark{background:#ff0;color:#000}#bootstrap-theme small{font-size:80%}#bootstrap-theme sub, #bootstrap-theme sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}#bootstrap-theme sup{top:-0.5em}#bootstrap-theme sub{bottom:-0.25em}#bootstrap-theme img{border:0}#bootstrap-theme svg:not(:root){overflow:hidden}#bootstrap-theme figure{margin:1em 40px}#bootstrap-theme hr{box-sizing:content-box;height:0}#bootstrap-theme pre{overflow:auto}#bootstrap-theme code, #bootstrap-theme kbd, #bootstrap-theme pre, #bootstrap-theme samp{font-family:monospace, monospace;font-size:1em}#bootstrap-theme button, #bootstrap-theme input, #bootstrap-theme optgroup, #bootstrap-theme select, #bootstrap-theme textarea{color:inherit;font:inherit;margin:0}#bootstrap-theme button{overflow:visible}#bootstrap-theme button, #bootstrap-theme select{text-transform:none}#bootstrap-theme button, html#bootstrap-theme input[type="button"], #bootstrap-theme input[type="button"], #bootstrap-theme input[type="reset"], #bootstrap-theme input[type="submit"]{-webkit-appearance:button;cursor:pointer}#bootstrap-theme button[disabled], html#bootstrap-theme input[disabled], #bootstrap-theme input[disabled]{cursor:default}#bootstrap-theme button::-moz-focus-inner, #bootstrap-theme input::-moz-focus-inner{border:0;padding:0}#bootstrap-theme input{line-height:normal}#bootstrap-theme input[type="checkbox"], #bootstrap-theme input[type="radio"]{box-sizing:border-box;padding:0}#bootstrap-theme input[type="number"]::-webkit-inner-spin-button, #bootstrap-theme input[type="number"]::-webkit-outer-spin-button{height:auto}#bootstrap-theme input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}#bootstrap-theme input[type="search"]::-webkit-search-cancel-button, #bootstrap-theme input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}#bootstrap-theme fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}#bootstrap-theme legend{border:0;padding:0}#bootstrap-theme textarea{overflow:auto}#bootstrap-theme optgroup{font-weight:bold}#bootstrap-theme table{border-collapse:collapse;border-spacing:0}#bootstrap-theme td, #bootstrap-theme th{padding:0}@media print{#bootstrap-theme *, #bootstrap-theme *:before, #bootstrap-theme *:after{color:#000 !important;text-shadow:none !important;background:transparent !important;box-shadow:none !important}#bootstrap-theme a, #bootstrap-theme a:visited{text-decoration:underline}#bootstrap-theme a[href]:after{content:" (" attr(href) ")"}#bootstrap-theme abbr[title]:after{content:" (" attr(title) ")"}#bootstrap-theme a[href^="#"]:after, #bootstrap-theme a[href^="javascript:"]:after{content:""}#bootstrap-theme pre, #bootstrap-theme blockquote{border:1px solid #999;page-break-inside:avoid}#bootstrap-theme thead{display:table-header-group}#bootstrap-theme tr, #bootstrap-theme img{page-break-inside:avoid}#bootstrap-theme img{max-width:100% !important}#bootstrap-theme p, #bootstrap-theme h2, #bootstrap-theme h3{orphans:3;widows:3}#bootstrap-theme h2, #bootstrap-theme h3{page-break-after:avoid}#bootstrap-theme .navbar{display:none}#bootstrap-theme .btn>.caret, #bootstrap-theme .dropup>.btn>.caret{border-top-color:#000 !important}#bootstrap-theme .label{border:1px solid #000}#bootstrap-theme .table{border-collapse:collapse !important}#bootstrap-theme .table td, #bootstrap-theme .table th{background-color:#fff !important}#bootstrap-theme .table-bordered th, #bootstrap-theme .table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:"Glyphicons Halflings";src:url("../base/fonts/glyphicons-halflings-regular.eot");src:url("../base/fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("../base/fonts/glyphicons-halflings-regular.woff2") format("woff2"),url("../base/fonts/glyphicons-halflings-regular.woff") format("woff"),url("../base/fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("../base/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")}#bootstrap-theme .glyphicon{position:relative;top:1px;display:inline-block;font-family:"Glyphicons Halflings";font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#bootstrap-theme .glyphicon-asterisk:before{content:"\002a"}#bootstrap-theme .glyphicon-plus:before{content:"\002b"}#bootstrap-theme .glyphicon-euro:before, #bootstrap-theme .glyphicon-eur:before{content:"\20ac"}#bootstrap-theme .glyphicon-minus:before{content:"\2212"}#bootstrap-theme .glyphicon-cloud:before{content:"\2601"}#bootstrap-theme .glyphicon-envelope:before{content:"\2709"}#bootstrap-theme .glyphicon-pencil:before{content:"\270f"}#bootstrap-theme .glyphicon-glass:before{content:"\e001"}#bootstrap-theme .glyphicon-music:before{content:"\e002"}#bootstrap-theme .glyphicon-search:before{content:"\e003"}#bootstrap-theme .glyphicon-heart:before{content:"\e005"}#bootstrap-theme .glyphicon-star:before{content:"\e006"}#bootstrap-theme .glyphicon-star-empty:before{content:"\e007"}#bootstrap-theme .glyphicon-user:before{content:"\e008"}#bootstrap-theme .glyphicon-film:before{content:"\e009"}#bootstrap-theme .glyphicon-th-large:before{content:"\e010"}#bootstrap-theme .glyphicon-th:before{content:"\e011"}#bootstrap-theme .glyphicon-th-list:before{content:"\e012"}#bootstrap-theme .glyphicon-ok:before{content:"\e013"}#bootstrap-theme .glyphicon-remove:before{content:"\e014"}#bootstrap-theme .glyphicon-zoom-in:before{content:"\e015"}#bootstrap-theme .glyphicon-zoom-out:before{content:"\e016"}#bootstrap-theme .glyphicon-off:before{content:"\e017"}#bootstrap-theme .glyphicon-signal:before{content:"\e018"}#bootstrap-theme .glyphicon-cog:before{content:"\e019"}#bootstrap-theme .glyphicon-trash:before{content:"\e020"}#bootstrap-theme .glyphicon-home:before{content:"\e021"}#bootstrap-theme .glyphicon-file:before{content:"\e022"}#bootstrap-theme .glyphicon-time:before{content:"\e023"}#bootstrap-theme .glyphicon-road:before{content:"\e024"}#bootstrap-theme .glyphicon-download-alt:before{content:"\e025"}#bootstrap-theme .glyphicon-download:before{content:"\e026"}#bootstrap-theme .glyphicon-upload:before{content:"\e027"}#bootstrap-theme .glyphicon-inbox:before{content:"\e028"}#bootstrap-theme .glyphicon-play-circle:before{content:"\e029"}#bootstrap-theme .glyphicon-repeat:before{content:"\e030"}#bootstrap-theme .glyphicon-refresh:before{content:"\e031"}#bootstrap-theme .glyphicon-list-alt:before{content:"\e032"}#bootstrap-theme .glyphicon-lock:before{content:"\e033"}#bootstrap-theme .glyphicon-flag:before{content:"\e034"}#bootstrap-theme .glyphicon-headphones:before{content:"\e035"}#bootstrap-theme .glyphicon-volume-off:before{content:"\e036"}#bootstrap-theme .glyphicon-volume-down:before{content:"\e037"}#bootstrap-theme .glyphicon-volume-up:before{content:"\e038"}#bootstrap-theme .glyphicon-qrcode:before{content:"\e039"}#bootstrap-theme .glyphicon-barcode:before{content:"\e040"}#bootstrap-theme .glyphicon-tag:before{content:"\e041"}#bootstrap-theme .glyphicon-tags:before{content:"\e042"}#bootstrap-theme .glyphicon-book:before{content:"\e043"}#bootstrap-theme .glyphicon-bookmark:before{content:"\e044"}#bootstrap-theme .glyphicon-print:before{content:"\e045"}#bootstrap-theme .glyphicon-camera:before{content:"\e046"}#bootstrap-theme .glyphicon-font:before{content:"\e047"}#bootstrap-theme .glyphicon-bold:before{content:"\e048"}#bootstrap-theme .glyphicon-italic:before{content:"\e049"}#bootstrap-theme .glyphicon-text-height:before{content:"\e050"}#bootstrap-theme .glyphicon-text-width:before{content:"\e051"}#bootstrap-theme .glyphicon-align-left:before{content:"\e052"}#bootstrap-theme .glyphicon-align-center:before{content:"\e053"}#bootstrap-theme .glyphicon-align-right:before{content:"\e054"}#bootstrap-theme .glyphicon-align-justify:before{content:"\e055"}#bootstrap-theme .glyphicon-list:before{content:"\e056"}#bootstrap-theme .glyphicon-indent-left:before{content:"\e057"}#bootstrap-theme .glyphicon-indent-right:before{content:"\e058"}#bootstrap-theme .glyphicon-facetime-video:before{content:"\e059"}#bootstrap-theme .glyphicon-picture:before{content:"\e060"}#bootstrap-theme .glyphicon-map-marker:before{content:"\e062"}#bootstrap-theme .glyphicon-adjust:before{content:"\e063"}#bootstrap-theme .glyphicon-tint:before{content:"\e064"}#bootstrap-theme .glyphicon-edit:before{content:"\e065"}#bootstrap-theme .glyphicon-share:before{content:"\e066"}#bootstrap-theme .glyphicon-check:before{content:"\e067"}#bootstrap-theme .glyphicon-move:before{content:"\e068"}#bootstrap-theme .glyphicon-step-backward:before{content:"\e069"}#bootstrap-theme .glyphicon-fast-backward:before{content:"\e070"}#bootstrap-theme .glyphicon-backward:before{content:"\e071"}#bootstrap-theme .glyphicon-play:before{content:"\e072"}#bootstrap-theme .glyphicon-pause:before{content:"\e073"}#bootstrap-theme .glyphicon-stop:before{content:"\e074"}#bootstrap-theme .glyphicon-forward:before{content:"\e075"}#bootstrap-theme .glyphicon-fast-forward:before{content:"\e076"}#bootstrap-theme .glyphicon-step-forward:before{content:"\e077"}#bootstrap-theme .glyphicon-eject:before{content:"\e078"}#bootstrap-theme .glyphicon-chevron-left:before{content:"\e079"}#bootstrap-theme .glyphicon-chevron-right:before{content:"\e080"}#bootstrap-theme .glyphicon-plus-sign:before{content:"\e081"}#bootstrap-theme .glyphicon-minus-sign:before{content:"\e082"}#bootstrap-theme .glyphicon-remove-sign:before{content:"\e083"}#bootstrap-theme .glyphicon-ok-sign:before{content:"\e084"}#bootstrap-theme .glyphicon-question-sign:before{content:"\e085"}#bootstrap-theme .glyphicon-info-sign:before{content:"\e086"}#bootstrap-theme .glyphicon-screenshot:before{content:"\e087"}#bootstrap-theme .glyphicon-remove-circle:before{content:"\e088"}#bootstrap-theme .glyphicon-ok-circle:before{content:"\e089"}#bootstrap-theme .glyphicon-ban-circle:before{content:"\e090"}#bootstrap-theme .glyphicon-arrow-left:before{content:"\e091"}#bootstrap-theme .glyphicon-arrow-right:before{content:"\e092"}#bootstrap-theme .glyphicon-arrow-up:before{content:"\e093"}#bootstrap-theme .glyphicon-arrow-down:before{content:"\e094"}#bootstrap-theme .glyphicon-share-alt:before{content:"\e095"}#bootstrap-theme .glyphicon-resize-full:before{content:"\e096"}#bootstrap-theme .glyphicon-resize-small:before{content:"\e097"}#bootstrap-theme .glyphicon-exclamation-sign:before{content:"\e101"}#bootstrap-theme .glyphicon-gift:before{content:"\e102"}#bootstrap-theme .glyphicon-leaf:before{content:"\e103"}#bootstrap-theme .glyphicon-fire:before{content:"\e104"}#bootstrap-theme .glyphicon-eye-open:before{content:"\e105"}#bootstrap-theme .glyphicon-eye-close:before{content:"\e106"}#bootstrap-theme .glyphicon-warning-sign:before{content:"\e107"}#bootstrap-theme .glyphicon-plane:before{content:"\e108"}#bootstrap-theme .glyphicon-calendar:before{content:"\e109"}#bootstrap-theme .glyphicon-random:before{content:"\e110"}#bootstrap-theme .glyphicon-comment:before{content:"\e111"}#bootstrap-theme .glyphicon-magnet:before{content:"\e112"}#bootstrap-theme .glyphicon-chevron-up:before{content:"\e113"}#bootstrap-theme .glyphicon-chevron-down:before{content:"\e114"}#bootstrap-theme .glyphicon-retweet:before{content:"\e115"}#bootstrap-theme .glyphicon-shopping-cart:before{content:"\e116"}#bootstrap-theme .glyphicon-folder-close:before{content:"\e117"}#bootstrap-theme .glyphicon-folder-open:before{content:"\e118"}#bootstrap-theme .glyphicon-resize-vertical:before{content:"\e119"}#bootstrap-theme .glyphicon-resize-horizontal:before{content:"\e120"}#bootstrap-theme .glyphicon-hdd:before{content:"\e121"}#bootstrap-theme .glyphicon-bullhorn:before{content:"\e122"}#bootstrap-theme .glyphicon-bell:before{content:"\e123"}#bootstrap-theme .glyphicon-certificate:before{content:"\e124"}#bootstrap-theme .glyphicon-thumbs-up:before{content:"\e125"}#bootstrap-theme .glyphicon-thumbs-down:before{content:"\e126"}#bootstrap-theme .glyphicon-hand-right:before{content:"\e127"}#bootstrap-theme .glyphicon-hand-left:before{content:"\e128"}#bootstrap-theme .glyphicon-hand-up:before{content:"\e129"}#bootstrap-theme .glyphicon-hand-down:before{content:"\e130"}#bootstrap-theme .glyphicon-circle-arrow-right:before{content:"\e131"}#bootstrap-theme .glyphicon-circle-arrow-left:before{content:"\e132"}#bootstrap-theme .glyphicon-circle-arrow-up:before{content:"\e133"}#bootstrap-theme .glyphicon-circle-arrow-down:before{content:"\e134"}#bootstrap-theme .glyphicon-globe:before{content:"\e135"}#bootstrap-theme .glyphicon-wrench:before{content:"\e136"}#bootstrap-theme .glyphicon-tasks:before{content:"\e137"}#bootstrap-theme .glyphicon-filter:before{content:"\e138"}#bootstrap-theme .glyphicon-briefcase:before{content:"\e139"}#bootstrap-theme .glyphicon-fullscreen:before{content:"\e140"}#bootstrap-theme .glyphicon-dashboard:before{content:"\e141"}#bootstrap-theme .glyphicon-paperclip:before{content:"\e142"}#bootstrap-theme .glyphicon-heart-empty:before{content:"\e143"}#bootstrap-theme .glyphicon-link:before{content:"\e144"}#bootstrap-theme .glyphicon-phone:before{content:"\e145"}#bootstrap-theme .glyphicon-pushpin:before{content:"\e146"}#bootstrap-theme .glyphicon-usd:before{content:"\e148"}#bootstrap-theme .glyphicon-gbp:before{content:"\e149"}#bootstrap-theme .glyphicon-sort:before{content:"\e150"}#bootstrap-theme .glyphicon-sort-by-alphabet:before{content:"\e151"}#bootstrap-theme .glyphicon-sort-by-alphabet-alt:before{content:"\e152"}#bootstrap-theme .glyphicon-sort-by-order:before{content:"\e153"}#bootstrap-theme .glyphicon-sort-by-order-alt:before{content:"\e154"}#bootstrap-theme .glyphicon-sort-by-attributes:before{content:"\e155"}#bootstrap-theme .glyphicon-sort-by-attributes-alt:before{content:"\e156"}#bootstrap-theme .glyphicon-unchecked:before{content:"\e157"}#bootstrap-theme .glyphicon-expand:before{content:"\e158"}#bootstrap-theme .glyphicon-collapse-down:before{content:"\e159"}#bootstrap-theme .glyphicon-collapse-up:before{content:"\e160"}#bootstrap-theme .glyphicon-log-in:before{content:"\e161"}#bootstrap-theme .glyphicon-flash:before{content:"\e162"}#bootstrap-theme .glyphicon-log-out:before{content:"\e163"}#bootstrap-theme .glyphicon-new-window:before{content:"\e164"}#bootstrap-theme .glyphicon-record:before{content:"\e165"}#bootstrap-theme .glyphicon-save:before{content:"\e166"}#bootstrap-theme .glyphicon-open:before{content:"\e167"}#bootstrap-theme .glyphicon-saved:before{content:"\e168"}#bootstrap-theme .glyphicon-import:before{content:"\e169"}#bootstrap-theme .glyphicon-export:before{content:"\e170"}#bootstrap-theme .glyphicon-send:before{content:"\e171"}#bootstrap-theme .glyphicon-floppy-disk:before{content:"\e172"}#bootstrap-theme .glyphicon-floppy-saved:before{content:"\e173"}#bootstrap-theme .glyphicon-floppy-remove:before{content:"\e174"}#bootstrap-theme .glyphicon-floppy-save:before{content:"\e175"}#bootstrap-theme .glyphicon-floppy-open:before{content:"\e176"}#bootstrap-theme .glyphicon-credit-card:before{content:"\e177"}#bootstrap-theme .glyphicon-transfer:before{content:"\e178"}#bootstrap-theme .glyphicon-cutlery:before{content:"\e179"}#bootstrap-theme .glyphicon-header:before{content:"\e180"}#bootstrap-theme .glyphicon-compressed:before{content:"\e181"}#bootstrap-theme .glyphicon-earphone:before{content:"\e182"}#bootstrap-theme .glyphicon-phone-alt:before{content:"\e183"}#bootstrap-theme .glyphicon-tower:before{content:"\e184"}#bootstrap-theme .glyphicon-stats:before{content:"\e185"}#bootstrap-theme .glyphicon-sd-video:before{content:"\e186"}#bootstrap-theme .glyphicon-hd-video:before{content:"\e187"}#bootstrap-theme .glyphicon-subtitles:before{content:"\e188"}#bootstrap-theme .glyphicon-sound-stereo:before{content:"\e189"}#bootstrap-theme .glyphicon-sound-dolby:before{content:"\e190"}#bootstrap-theme .glyphicon-sound-5-1:before{content:"\e191"}#bootstrap-theme .glyphicon-sound-6-1:before{content:"\e192"}#bootstrap-theme .glyphicon-sound-7-1:before{content:"\e193"}#bootstrap-theme .glyphicon-copyright-mark:before{content:"\e194"}#bootstrap-theme .glyphicon-registration-mark:before{content:"\e195"}#bootstrap-theme .glyphicon-cloud-download:before{content:"\e197"}#bootstrap-theme .glyphicon-cloud-upload:before{content:"\e198"}#bootstrap-theme .glyphicon-tree-conifer:before{content:"\e199"}#bootstrap-theme .glyphicon-tree-deciduous:before{content:"\e200"}#bootstrap-theme .glyphicon-cd:before{content:"\e201"}#bootstrap-theme .glyphicon-save-file:before{content:"\e202"}#bootstrap-theme .glyphicon-open-file:before{content:"\e203"}#bootstrap-theme .glyphicon-level-up:before{content:"\e204"}#bootstrap-theme .glyphicon-copy:before{content:"\e205"}#bootstrap-theme .glyphicon-paste:before{content:"\e206"}#bootstrap-theme .glyphicon-alert:before{content:"\e209"}#bootstrap-theme .glyphicon-equalizer:before{content:"\e210"}#bootstrap-theme .glyphicon-king:before{content:"\e211"}#bootstrap-theme .glyphicon-queen:before{content:"\e212"}#bootstrap-theme .glyphicon-pawn:before{content:"\e213"}#bootstrap-theme .glyphicon-bishop:before{content:"\e214"}#bootstrap-theme .glyphicon-knight:before{content:"\e215"}#bootstrap-theme .glyphicon-baby-formula:before{content:"\e216"}#bootstrap-theme .glyphicon-tent:before{content:"\26fa"}#bootstrap-theme .glyphicon-blackboard:before{content:"\e218"}#bootstrap-theme .glyphicon-bed:before{content:"\e219"}#bootstrap-theme .glyphicon-apple:before{content:"\f8ff"}#bootstrap-theme .glyphicon-erase:before{content:"\e221"}#bootstrap-theme .glyphicon-hourglass:before{content:"\231b"}#bootstrap-theme .glyphicon-lamp:before{content:"\e223"}#bootstrap-theme .glyphicon-duplicate:before{content:"\e224"}#bootstrap-theme .glyphicon-piggy-bank:before{content:"\e225"}#bootstrap-theme .glyphicon-scissors:before{content:"\e226"}#bootstrap-theme .glyphicon-bitcoin:before{content:"\e227"}#bootstrap-theme .glyphicon-btc:before{content:"\e227"}#bootstrap-theme .glyphicon-xbt:before{content:"\e227"}#bootstrap-theme .glyphicon-yen:before{content:"\00a5"}#bootstrap-theme .glyphicon-jpy:before{content:"\00a5"}#bootstrap-theme .glyphicon-ruble:before{content:"\20bd"}#bootstrap-theme .glyphicon-rub:before{content:"\20bd"}#bootstrap-theme .glyphicon-scale:before{content:"\e230"}#bootstrap-theme .glyphicon-ice-lolly:before{content:"\e231"}#bootstrap-theme .glyphicon-ice-lolly-tasted:before{content:"\e232"}#bootstrap-theme .glyphicon-education:before{content:"\e233"}#bootstrap-theme .glyphicon-option-horizontal:before{content:"\e234"}#bootstrap-theme .glyphicon-option-vertical:before{content:"\e235"}#bootstrap-theme .glyphicon-menu-hamburger:before{content:"\e236"}#bootstrap-theme .glyphicon-modal-window:before{content:"\e237"}#bootstrap-theme .glyphicon-oil:before{content:"\e238"}#bootstrap-theme .glyphicon-grain:before{content:"\e239"}#bootstrap-theme .glyphicon-sunglasses:before{content:"\e240"}#bootstrap-theme .glyphicon-text-size:before{content:"\e241"}#bootstrap-theme .glyphicon-text-color:before{content:"\e242"}#bootstrap-theme .glyphicon-text-background:before{content:"\e243"}#bootstrap-theme .glyphicon-object-align-top:before{content:"\e244"}#bootstrap-theme .glyphicon-object-align-bottom:before{content:"\e245"}#bootstrap-theme .glyphicon-object-align-horizontal:before{content:"\e246"}#bootstrap-theme .glyphicon-object-align-left:before{content:"\e247"}#bootstrap-theme .glyphicon-object-align-vertical:before{content:"\e248"}#bootstrap-theme .glyphicon-object-align-right:before{content:"\e249"}#bootstrap-theme .glyphicon-triangle-right:before{content:"\e250"}#bootstrap-theme .glyphicon-triangle-left:before{content:"\e251"}#bootstrap-theme .glyphicon-triangle-bottom:before{content:"\e252"}#bootstrap-theme .glyphicon-triangle-top:before{content:"\e253"}#bootstrap-theme .glyphicon-console:before{content:"\e254"}#bootstrap-theme .glyphicon-superscript:before{content:"\e255"}#bootstrap-theme .glyphicon-subscript:before{content:"\e256"}#bootstrap-theme .glyphicon-menu-left:before{content:"\e257"}#bootstrap-theme .glyphicon-menu-right:before{content:"\e258"}#bootstrap-theme .glyphicon-menu-down:before{content:"\e259"}#bootstrap-theme .glyphicon-menu-up:before{content:"\e260"}#bootstrap-theme *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}#bootstrap-theme *:before, #bootstrap-theme *:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html#bootstrap-theme, #bootstrap-theme{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body#bootstrap-theme, #bootstrap-theme{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:1.5384615385;color:#4d4d69;background-color:#e8eef0}#bootstrap-theme input, #bootstrap-theme button, #bootstrap-theme select, #bootstrap-theme textarea{font-family:inherit;font-size:inherit;line-height:inherit}#bootstrap-theme a{color:#0071bd;text-decoration:none}#bootstrap-theme a:hover, #bootstrap-theme a:focus{color:#004371;text-decoration:underline}#bootstrap-theme a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}#bootstrap-theme figure{margin:0}#bootstrap-theme img{vertical-align:middle}#bootstrap-theme .img-responsive{display:block;max-width:100%;height:auto}#bootstrap-theme .img-rounded{border-radius:6px}#bootstrap-theme .img-thumbnail{padding:4px;line-height:1.5384615385;background-color:#e8eef0;border:1px solid #ddd;border-radius:2px;-webkit-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto}#bootstrap-theme .img-circle{border-radius:50%}#bootstrap-theme hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #d3dee2}#bootstrap-theme .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}#bootstrap-theme .sr-only-focusable:active, #bootstrap-theme .sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}#bootstrap-theme [role="button"]{cursor:pointer}#bootstrap-theme h1, #bootstrap-theme h2, #bootstrap-theme h3, #bootstrap-theme h4, #bootstrap-theme h5, #bootstrap-theme h6, #bootstrap-theme .h1, #bootstrap-theme .h2, #bootstrap-theme .h3, #bootstrap-theme .h4, #bootstrap-theme .h5, #bootstrap-theme .h6{font-family:inherit;font-weight:500;line-height:1.1;color:#464354}#bootstrap-theme h1 small, #bootstrap-theme h1 .small, #bootstrap-theme h2 small, #bootstrap-theme h2 .small, #bootstrap-theme h3 small, #bootstrap-theme h3 .small, #bootstrap-theme h4 small, #bootstrap-theme h4 .small, #bootstrap-theme h5 small, #bootstrap-theme h5 .small, #bootstrap-theme h6 small, #bootstrap-theme h6 .small, #bootstrap-theme .h1 small, #bootstrap-theme .h1 .small, #bootstrap-theme .h2 small, #bootstrap-theme .h2 .small, #bootstrap-theme .h3 small, #bootstrap-theme .h3 .small, #bootstrap-theme .h4 small, #bootstrap-theme .h4 .small, #bootstrap-theme .h5 small, #bootstrap-theme .h5 .small, #bootstrap-theme .h6 small, #bootstrap-theme .h6 .small{font-weight:400;line-height:1;color:#e8eef0}#bootstrap-theme h1, #bootstrap-theme .h1, #bootstrap-theme h2, #bootstrap-theme .h2, #bootstrap-theme h3, #bootstrap-theme .h3{margin-top:20px;margin-bottom:10px}#bootstrap-theme h1 small, #bootstrap-theme h1 .small, #bootstrap-theme .h1 small, #bootstrap-theme .h1 .small, #bootstrap-theme h2 small, #bootstrap-theme h2 .small, #bootstrap-theme .h2 small, #bootstrap-theme .h2 .small, #bootstrap-theme h3 small, #bootstrap-theme h3 .small, #bootstrap-theme .h3 small, #bootstrap-theme .h3 .small{font-size:65%}#bootstrap-theme h4, #bootstrap-theme .h4, #bootstrap-theme h5, #bootstrap-theme .h5, #bootstrap-theme h6, #bootstrap-theme .h6{margin-top:10px;margin-bottom:10px}#bootstrap-theme h4 small, #bootstrap-theme h4 .small, #bootstrap-theme .h4 small, #bootstrap-theme .h4 .small, #bootstrap-theme h5 small, #bootstrap-theme h5 .small, #bootstrap-theme .h5 small, #bootstrap-theme .h5 .small, #bootstrap-theme h6 small, #bootstrap-theme h6 .small, #bootstrap-theme .h6 small, #bootstrap-theme .h6 .small{font-size:75%}#bootstrap-theme h1, #bootstrap-theme .h1{font-size:24px}#bootstrap-theme h2, #bootstrap-theme .h2{font-size:18px}#bootstrap-theme h3, #bootstrap-theme .h3{font-size:16px}#bootstrap-theme h4, #bootstrap-theme .h4{font-size:14px}#bootstrap-theme h5, #bootstrap-theme .h5{font-size:13px}#bootstrap-theme h6, #bootstrap-theme .h6{font-size:12px}#bootstrap-theme p{margin:0 0 10px}#bootstrap-theme .lead{margin-bottom:20px;font-size:14px;font-weight:300;line-height:1.4}@media (min-width: 768px){#bootstrap-theme .lead{font-size:19.5px}}#bootstrap-theme small, #bootstrap-theme .small{font-size:92%}#bootstrap-theme mark, #bootstrap-theme .mark{padding:.2em;background-color:#fcf8e3}#bootstrap-theme .text-left{text-align:left}#bootstrap-theme .text-right{text-align:right}#bootstrap-theme .text-center{text-align:center}#bootstrap-theme .text-justify{text-align:justify}#bootstrap-theme .text-nowrap{white-space:nowrap}#bootstrap-theme .text-lowercase{text-transform:lowercase}#bootstrap-theme .text-uppercase, #bootstrap-theme .initialism{text-transform:uppercase}#bootstrap-theme .text-capitalize{text-transform:capitalize}#bootstrap-theme .text-muted{color:#aab2b9}#bootstrap-theme .text-primary{color:#0071bd}#bootstrap-theme a.text-primary:hover, #bootstrap-theme a.text-primary:focus{color:#00538a}#bootstrap-theme .text-success{color:#4d994d}#bootstrap-theme a.text-success:hover, #bootstrap-theme a.text-success:focus{color:#3c773c}#bootstrap-theme .text-info{color:#0071bd}#bootstrap-theme a.text-info:hover, #bootstrap-theme a.text-info:focus{color:#00538a}#bootstrap-theme .text-warning{color:#bf5900}#bootstrap-theme a.text-warning:hover, #bootstrap-theme a.text-warning:focus{color:#8c4100}#bootstrap-theme .text-danger{color:#cf3458}#bootstrap-theme a.text-danger:hover, #bootstrap-theme a.text-danger:focus{color:#a82846}#bootstrap-theme .bg-primary{color:#fff}#bootstrap-theme .bg-primary{background-color:#0071bd}#bootstrap-theme a.bg-primary:hover, #bootstrap-theme a.bg-primary:focus{background-color:#00538a}#bootstrap-theme .bg-success{background-color:#dff0d8}#bootstrap-theme a.bg-success:hover, #bootstrap-theme a.bg-success:focus{background-color:#c1e2b3}#bootstrap-theme .bg-info{background-color:#d9edf7}#bootstrap-theme a.bg-info:hover, #bootstrap-theme a.bg-info:focus{background-color:#afd9ee}#bootstrap-theme .bg-warning{background-color:#fcf8e3}#bootstrap-theme a.bg-warning:hover, #bootstrap-theme a.bg-warning:focus{background-color:#f7ecb5}#bootstrap-theme .bg-danger{background-color:#f2dede}#bootstrap-theme a.bg-danger:hover, #bootstrap-theme a.bg-danger:focus{background-color:#e4b9b9}#bootstrap-theme .page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #f3f6f7}#bootstrap-theme ul, #bootstrap-theme ol{margin-top:0;margin-bottom:10px}#bootstrap-theme ul ul, #bootstrap-theme ul ol, #bootstrap-theme ol ul, #bootstrap-theme ol ol{margin-bottom:0}#bootstrap-theme .list-unstyled{padding-left:0;list-style:none}#bootstrap-theme .list-inline{padding-left:0;list-style:none;margin-left:-5px}#bootstrap-theme .list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}#bootstrap-theme dl{margin-top:0;margin-bottom:20px}#bootstrap-theme dt, #bootstrap-theme dd{line-height:1.5384615385}#bootstrap-theme dt{font-weight:700}#bootstrap-theme dd{margin-left:0}#bootstrap-theme .dl-horizontal dd:before, #bootstrap-theme .dl-horizontal dd:after{display:table;content:" "}#bootstrap-theme .dl-horizontal dd:after{clear:both}@media (min-width: 768px){#bootstrap-theme .dl-horizontal dt{float:left;width:230px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#bootstrap-theme .dl-horizontal dd{margin-left:250px}}#bootstrap-theme abbr[title], #bootstrap-theme abbr[data-original-title]{cursor:help}#bootstrap-theme .initialism{font-size:90%}#bootstrap-theme blockquote{padding:10px 20px;margin:0 0 20px;font-size:16.25px;border-left:5px solid #0071bd}#bootstrap-theme blockquote p:last-child, #bootstrap-theme blockquote ul:last-child, #bootstrap-theme blockquote ol:last-child{margin-bottom:0}#bootstrap-theme blockquote footer, #bootstrap-theme blockquote small, #bootstrap-theme blockquote .small{display:block;font-size:80%;line-height:1.5384615385;color:#4d4d69}#bootstrap-theme blockquote footer:before, #bootstrap-theme blockquote small:before, #bootstrap-theme blockquote .small:before{content:"\2014 \00A0"}#bootstrap-theme .blockquote-reverse, #bootstrap-theme blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #0071bd;border-left:0}#bootstrap-theme .blockquote-reverse footer:before, #bootstrap-theme .blockquote-reverse small:before, #bootstrap-theme .blockquote-reverse .small:before, #bootstrap-theme blockquote.pull-right footer:before, #bootstrap-theme blockquote.pull-right small:before, #bootstrap-theme blockquote.pull-right .small:before{content:""}#bootstrap-theme .blockquote-reverse footer:after, #bootstrap-theme .blockquote-reverse small:after, #bootstrap-theme .blockquote-reverse .small:after, #bootstrap-theme blockquote.pull-right footer:after, #bootstrap-theme blockquote.pull-right small:after, #bootstrap-theme blockquote.pull-right .small:after{content:"\00A0 \2014"}#bootstrap-theme address{margin-bottom:20px;font-style:normal;line-height:1.5384615385}#bootstrap-theme code, #bootstrap-theme kbd, #bootstrap-theme pre, #bootstrap-theme samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}#bootstrap-theme code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:2px}#bootstrap-theme kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:2px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}#bootstrap-theme kbd kbd{padding:0;font-size:100%;font-weight:700;box-shadow:none}#bootstrap-theme pre{display:block;padding:9.5px;margin:0 0 10px;font-size:12px;line-height:1.5384615385;color:#4d4d69;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:2px}#bootstrap-theme pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}#bootstrap-theme .pre-scrollable{max-height:340px;overflow-y:scroll}#bootstrap-theme .container{padding-right:8px;padding-left:8px;margin-right:auto;margin-left:auto}#bootstrap-theme .container:before, #bootstrap-theme .container:after{display:table;content:" "}#bootstrap-theme .container:after{clear:both}@media (min-width: 768px){#bootstrap-theme .container{width:736px}}@media (min-width: 992px){#bootstrap-theme .container{width:956px}}@media (min-width: 1200px){#bootstrap-theme .container{width:1156px}}#bootstrap-theme .container-fluid{padding-right:8px;padding-left:8px;margin-right:auto;margin-left:auto}#bootstrap-theme .container-fluid:before, #bootstrap-theme .container-fluid:after{display:table;content:" "}#bootstrap-theme .container-fluid:after{clear:both}#bootstrap-theme .row{margin-right:-8px;margin-left:-8px}#bootstrap-theme .row:before, #bootstrap-theme .row:after{display:table;content:" "}#bootstrap-theme .row:after{clear:both}#bootstrap-theme .row-no-gutters{margin-right:0;margin-left:0}#bootstrap-theme .row-no-gutters [class*="col-"]{padding-right:0;padding-left:0}#bootstrap-theme .col-xs-1, #bootstrap-theme .col-sm-1, #bootstrap-theme .col-md-1, #bootstrap-theme .col-lg-1, #bootstrap-theme .col-xs-2, #bootstrap-theme .col-sm-2, #bootstrap-theme .col-md-2, #bootstrap-theme .col-lg-2, #bootstrap-theme .col-xs-3, #bootstrap-theme .col-sm-3, #bootstrap-theme .col-md-3, #bootstrap-theme .col-lg-3, #bootstrap-theme .col-xs-4, #bootstrap-theme .col-sm-4, #bootstrap-theme .col-md-4, #bootstrap-theme .col-lg-4, #bootstrap-theme .col-xs-5, #bootstrap-theme .col-sm-5, #bootstrap-theme .col-md-5, #bootstrap-theme .col-lg-5, #bootstrap-theme .col-xs-6, #bootstrap-theme .col-sm-6, #bootstrap-theme .col-md-6, #bootstrap-theme .col-lg-6, #bootstrap-theme .col-xs-7, #bootstrap-theme .col-sm-7, #bootstrap-theme .col-md-7, #bootstrap-theme .col-lg-7, #bootstrap-theme .col-xs-8, #bootstrap-theme .col-sm-8, #bootstrap-theme .col-md-8, #bootstrap-theme .col-lg-8, #bootstrap-theme .col-xs-9, #bootstrap-theme .col-sm-9, #bootstrap-theme .col-md-9, #bootstrap-theme .col-lg-9, #bootstrap-theme .col-xs-10, #bootstrap-theme .col-sm-10, #bootstrap-theme .col-md-10, #bootstrap-theme .col-lg-10, #bootstrap-theme .col-xs-11, #bootstrap-theme .col-sm-11, #bootstrap-theme .col-md-11, #bootstrap-theme .col-lg-11, #bootstrap-theme .col-xs-12, #bootstrap-theme .col-sm-12, #bootstrap-theme .col-md-12, #bootstrap-theme .col-lg-12{position:relative;min-height:1px;padding-right:8px;padding-left:8px}#bootstrap-theme .col-xs-1, #bootstrap-theme .col-xs-2, #bootstrap-theme .col-xs-3, #bootstrap-theme .col-xs-4, #bootstrap-theme .col-xs-5, #bootstrap-theme .col-xs-6, #bootstrap-theme .col-xs-7, #bootstrap-theme .col-xs-8, #bootstrap-theme .col-xs-9, #bootstrap-theme .col-xs-10, #bootstrap-theme .col-xs-11, #bootstrap-theme .col-xs-12{float:left}#bootstrap-theme .col-xs-1{width:8.3333333333%}#bootstrap-theme .col-xs-2{width:16.6666666667%}#bootstrap-theme .col-xs-3{width:25%}#bootstrap-theme .col-xs-4{width:33.3333333333%}#bootstrap-theme .col-xs-5{width:41.6666666667%}#bootstrap-theme .col-xs-6{width:50%}#bootstrap-theme .col-xs-7{width:58.3333333333%}#bootstrap-theme .col-xs-8{width:66.6666666667%}#bootstrap-theme .col-xs-9{width:75%}#bootstrap-theme .col-xs-10{width:83.3333333333%}#bootstrap-theme .col-xs-11{width:91.6666666667%}#bootstrap-theme .col-xs-12{width:100%}#bootstrap-theme .col-xs-pull-0{right:auto}#bootstrap-theme .col-xs-pull-1{right:8.3333333333%}#bootstrap-theme .col-xs-pull-2{right:16.6666666667%}#bootstrap-theme .col-xs-pull-3{right:25%}#bootstrap-theme .col-xs-pull-4{right:33.3333333333%}#bootstrap-theme .col-xs-pull-5{right:41.6666666667%}#bootstrap-theme .col-xs-pull-6{right:50%}#bootstrap-theme .col-xs-pull-7{right:58.3333333333%}#bootstrap-theme .col-xs-pull-8{right:66.6666666667%}#bootstrap-theme .col-xs-pull-9{right:75%}#bootstrap-theme .col-xs-pull-10{right:83.3333333333%}#bootstrap-theme .col-xs-pull-11{right:91.6666666667%}#bootstrap-theme .col-xs-pull-12{right:100%}#bootstrap-theme .col-xs-push-0{left:auto}#bootstrap-theme .col-xs-push-1{left:8.3333333333%}#bootstrap-theme .col-xs-push-2{left:16.6666666667%}#bootstrap-theme .col-xs-push-3{left:25%}#bootstrap-theme .col-xs-push-4{left:33.3333333333%}#bootstrap-theme .col-xs-push-5{left:41.6666666667%}#bootstrap-theme .col-xs-push-6{left:50%}#bootstrap-theme .col-xs-push-7{left:58.3333333333%}#bootstrap-theme .col-xs-push-8{left:66.6666666667%}#bootstrap-theme .col-xs-push-9{left:75%}#bootstrap-theme .col-xs-push-10{left:83.3333333333%}#bootstrap-theme .col-xs-push-11{left:91.6666666667%}#bootstrap-theme .col-xs-push-12{left:100%}#bootstrap-theme .col-xs-offset-0{margin-left:0%}#bootstrap-theme .col-xs-offset-1{margin-left:8.3333333333%}#bootstrap-theme .col-xs-offset-2{margin-left:16.6666666667%}#bootstrap-theme .col-xs-offset-3{margin-left:25%}#bootstrap-theme .col-xs-offset-4{margin-left:33.3333333333%}#bootstrap-theme .col-xs-offset-5{margin-left:41.6666666667%}#bootstrap-theme .col-xs-offset-6{margin-left:50%}#bootstrap-theme .col-xs-offset-7{margin-left:58.3333333333%}#bootstrap-theme .col-xs-offset-8{margin-left:66.6666666667%}#bootstrap-theme .col-xs-offset-9{margin-left:75%}#bootstrap-theme .col-xs-offset-10{margin-left:83.3333333333%}#bootstrap-theme .col-xs-offset-11{margin-left:91.6666666667%}#bootstrap-theme .col-xs-offset-12{margin-left:100%}@media (min-width: 768px){#bootstrap-theme .col-sm-1, #bootstrap-theme .col-sm-2, #bootstrap-theme .col-sm-3, #bootstrap-theme .col-sm-4, #bootstrap-theme .col-sm-5, #bootstrap-theme .col-sm-6, #bootstrap-theme .col-sm-7, #bootstrap-theme .col-sm-8, #bootstrap-theme .col-sm-9, #bootstrap-theme .col-sm-10, #bootstrap-theme .col-sm-11, #bootstrap-theme .col-sm-12{float:left}#bootstrap-theme .col-sm-1{width:8.3333333333%}#bootstrap-theme .col-sm-2{width:16.6666666667%}#bootstrap-theme .col-sm-3{width:25%}#bootstrap-theme .col-sm-4{width:33.3333333333%}#bootstrap-theme .col-sm-5{width:41.6666666667%}#bootstrap-theme .col-sm-6{width:50%}#bootstrap-theme .col-sm-7{width:58.3333333333%}#bootstrap-theme .col-sm-8{width:66.6666666667%}#bootstrap-theme .col-sm-9{width:75%}#bootstrap-theme .col-sm-10{width:83.3333333333%}#bootstrap-theme .col-sm-11{width:91.6666666667%}#bootstrap-theme .col-sm-12{width:100%}#bootstrap-theme .col-sm-pull-0{right:auto}#bootstrap-theme .col-sm-pull-1{right:8.3333333333%}#bootstrap-theme .col-sm-pull-2{right:16.6666666667%}#bootstrap-theme .col-sm-pull-3{right:25%}#bootstrap-theme .col-sm-pull-4{right:33.3333333333%}#bootstrap-theme .col-sm-pull-5{right:41.6666666667%}#bootstrap-theme .col-sm-pull-6{right:50%}#bootstrap-theme .col-sm-pull-7{right:58.3333333333%}#bootstrap-theme .col-sm-pull-8{right:66.6666666667%}#bootstrap-theme .col-sm-pull-9{right:75%}#bootstrap-theme .col-sm-pull-10{right:83.3333333333%}#bootstrap-theme .col-sm-pull-11{right:91.6666666667%}#bootstrap-theme .col-sm-pull-12{right:100%}#bootstrap-theme .col-sm-push-0{left:auto}#bootstrap-theme .col-sm-push-1{left:8.3333333333%}#bootstrap-theme .col-sm-push-2{left:16.6666666667%}#bootstrap-theme .col-sm-push-3{left:25%}#bootstrap-theme .col-sm-push-4{left:33.3333333333%}#bootstrap-theme .col-sm-push-5{left:41.6666666667%}#bootstrap-theme .col-sm-push-6{left:50%}#bootstrap-theme .col-sm-push-7{left:58.3333333333%}#bootstrap-theme .col-sm-push-8{left:66.6666666667%}#bootstrap-theme .col-sm-push-9{left:75%}#bootstrap-theme .col-sm-push-10{left:83.3333333333%}#bootstrap-theme .col-sm-push-11{left:91.6666666667%}#bootstrap-theme .col-sm-push-12{left:100%}#bootstrap-theme .col-sm-offset-0{margin-left:0%}#bootstrap-theme .col-sm-offset-1{margin-left:8.3333333333%}#bootstrap-theme .col-sm-offset-2{margin-left:16.6666666667%}#bootstrap-theme .col-sm-offset-3{margin-left:25%}#bootstrap-theme .col-sm-offset-4{margin-left:33.3333333333%}#bootstrap-theme .col-sm-offset-5{margin-left:41.6666666667%}#bootstrap-theme .col-sm-offset-6{margin-left:50%}#bootstrap-theme .col-sm-offset-7{margin-left:58.3333333333%}#bootstrap-theme .col-sm-offset-8{margin-left:66.6666666667%}#bootstrap-theme .col-sm-offset-9{margin-left:75%}#bootstrap-theme .col-sm-offset-10{margin-left:83.3333333333%}#bootstrap-theme .col-sm-offset-11{margin-left:91.6666666667%}#bootstrap-theme .col-sm-offset-12{margin-left:100%}}@media (min-width: 992px){#bootstrap-theme .col-md-1, #bootstrap-theme .col-md-2, #bootstrap-theme .col-md-3, #bootstrap-theme .col-md-4, #bootstrap-theme .col-md-5, #bootstrap-theme .col-md-6, #bootstrap-theme .col-md-7, #bootstrap-theme .col-md-8, #bootstrap-theme .col-md-9, #bootstrap-theme .col-md-10, #bootstrap-theme .col-md-11, #bootstrap-theme .col-md-12{float:left}#bootstrap-theme .col-md-1{width:8.3333333333%}#bootstrap-theme .col-md-2{width:16.6666666667%}#bootstrap-theme .col-md-3{width:25%}#bootstrap-theme .col-md-4{width:33.3333333333%}#bootstrap-theme .col-md-5{width:41.6666666667%}#bootstrap-theme .col-md-6{width:50%}#bootstrap-theme .col-md-7{width:58.3333333333%}#bootstrap-theme .col-md-8{width:66.6666666667%}#bootstrap-theme .col-md-9{width:75%}#bootstrap-theme .col-md-10{width:83.3333333333%}#bootstrap-theme .col-md-11{width:91.6666666667%}#bootstrap-theme .col-md-12{width:100%}#bootstrap-theme .col-md-pull-0{right:auto}#bootstrap-theme .col-md-pull-1{right:8.3333333333%}#bootstrap-theme .col-md-pull-2{right:16.6666666667%}#bootstrap-theme .col-md-pull-3{right:25%}#bootstrap-theme .col-md-pull-4{right:33.3333333333%}#bootstrap-theme .col-md-pull-5{right:41.6666666667%}#bootstrap-theme .col-md-pull-6{right:50%}#bootstrap-theme .col-md-pull-7{right:58.3333333333%}#bootstrap-theme .col-md-pull-8{right:66.6666666667%}#bootstrap-theme .col-md-pull-9{right:75%}#bootstrap-theme .col-md-pull-10{right:83.3333333333%}#bootstrap-theme .col-md-pull-11{right:91.6666666667%}#bootstrap-theme .col-md-pull-12{right:100%}#bootstrap-theme .col-md-push-0{left:auto}#bootstrap-theme .col-md-push-1{left:8.3333333333%}#bootstrap-theme .col-md-push-2{left:16.6666666667%}#bootstrap-theme .col-md-push-3{left:25%}#bootstrap-theme .col-md-push-4{left:33.3333333333%}#bootstrap-theme .col-md-push-5{left:41.6666666667%}#bootstrap-theme .col-md-push-6{left:50%}#bootstrap-theme .col-md-push-7{left:58.3333333333%}#bootstrap-theme .col-md-push-8{left:66.6666666667%}#bootstrap-theme .col-md-push-9{left:75%}#bootstrap-theme .col-md-push-10{left:83.3333333333%}#bootstrap-theme .col-md-push-11{left:91.6666666667%}#bootstrap-theme .col-md-push-12{left:100%}#bootstrap-theme .col-md-offset-0{margin-left:0%}#bootstrap-theme .col-md-offset-1{margin-left:8.3333333333%}#bootstrap-theme .col-md-offset-2{margin-left:16.6666666667%}#bootstrap-theme .col-md-offset-3{margin-left:25%}#bootstrap-theme .col-md-offset-4{margin-left:33.3333333333%}#bootstrap-theme .col-md-offset-5{margin-left:41.6666666667%}#bootstrap-theme .col-md-offset-6{margin-left:50%}#bootstrap-theme .col-md-offset-7{margin-left:58.3333333333%}#bootstrap-theme .col-md-offset-8{margin-left:66.6666666667%}#bootstrap-theme .col-md-offset-9{margin-left:75%}#bootstrap-theme .col-md-offset-10{margin-left:83.3333333333%}#bootstrap-theme .col-md-offset-11{margin-left:91.6666666667%}#bootstrap-theme .col-md-offset-12{margin-left:100%}}@media (min-width: 1200px){#bootstrap-theme .col-lg-1, #bootstrap-theme .col-lg-2, #bootstrap-theme .col-lg-3, #bootstrap-theme .col-lg-4, #bootstrap-theme .col-lg-5, #bootstrap-theme .col-lg-6, #bootstrap-theme .col-lg-7, #bootstrap-theme .col-lg-8, #bootstrap-theme .col-lg-9, #bootstrap-theme .col-lg-10, #bootstrap-theme .col-lg-11, #bootstrap-theme .col-lg-12{float:left}#bootstrap-theme .col-lg-1{width:8.3333333333%}#bootstrap-theme .col-lg-2{width:16.6666666667%}#bootstrap-theme .col-lg-3{width:25%}#bootstrap-theme .col-lg-4{width:33.3333333333%}#bootstrap-theme .col-lg-5{width:41.6666666667%}#bootstrap-theme .col-lg-6{width:50%}#bootstrap-theme .col-lg-7{width:58.3333333333%}#bootstrap-theme .col-lg-8{width:66.6666666667%}#bootstrap-theme .col-lg-9{width:75%}#bootstrap-theme .col-lg-10{width:83.3333333333%}#bootstrap-theme .col-lg-11{width:91.6666666667%}#bootstrap-theme .col-lg-12{width:100%}#bootstrap-theme .col-lg-pull-0{right:auto}#bootstrap-theme .col-lg-pull-1{right:8.3333333333%}#bootstrap-theme .col-lg-pull-2{right:16.6666666667%}#bootstrap-theme .col-lg-pull-3{right:25%}#bootstrap-theme .col-lg-pull-4{right:33.3333333333%}#bootstrap-theme .col-lg-pull-5{right:41.6666666667%}#bootstrap-theme .col-lg-pull-6{right:50%}#bootstrap-theme .col-lg-pull-7{right:58.3333333333%}#bootstrap-theme .col-lg-pull-8{right:66.6666666667%}#bootstrap-theme .col-lg-pull-9{right:75%}#bootstrap-theme .col-lg-pull-10{right:83.3333333333%}#bootstrap-theme .col-lg-pull-11{right:91.6666666667%}#bootstrap-theme .col-lg-pull-12{right:100%}#bootstrap-theme .col-lg-push-0{left:auto}#bootstrap-theme .col-lg-push-1{left:8.3333333333%}#bootstrap-theme .col-lg-push-2{left:16.6666666667%}#bootstrap-theme .col-lg-push-3{left:25%}#bootstrap-theme .col-lg-push-4{left:33.3333333333%}#bootstrap-theme .col-lg-push-5{left:41.6666666667%}#bootstrap-theme .col-lg-push-6{left:50%}#bootstrap-theme .col-lg-push-7{left:58.3333333333%}#bootstrap-theme .col-lg-push-8{left:66.6666666667%}#bootstrap-theme .col-lg-push-9{left:75%}#bootstrap-theme .col-lg-push-10{left:83.3333333333%}#bootstrap-theme .col-lg-push-11{left:91.6666666667%}#bootstrap-theme .col-lg-push-12{left:100%}#bootstrap-theme .col-lg-offset-0{margin-left:0%}#bootstrap-theme .col-lg-offset-1{margin-left:8.3333333333%}#bootstrap-theme .col-lg-offset-2{margin-left:16.6666666667%}#bootstrap-theme .col-lg-offset-3{margin-left:25%}#bootstrap-theme .col-lg-offset-4{margin-left:33.3333333333%}#bootstrap-theme .col-lg-offset-5{margin-left:41.6666666667%}#bootstrap-theme .col-lg-offset-6{margin-left:50%}#bootstrap-theme .col-lg-offset-7{margin-left:58.3333333333%}#bootstrap-theme .col-lg-offset-8{margin-left:66.6666666667%}#bootstrap-theme .col-lg-offset-9{margin-left:75%}#bootstrap-theme .col-lg-offset-10{margin-left:83.3333333333%}#bootstrap-theme .col-lg-offset-11{margin-left:91.6666666667%}#bootstrap-theme .col-lg-offset-12{margin-left:100%}}#bootstrap-theme table{background-color:#fff}#bootstrap-theme table col[class*="col-"]{position:static;display:table-column;float:none}#bootstrap-theme table td[class*="col-"], #bootstrap-theme table th[class*="col-"]{position:static;display:table-cell;float:none}#bootstrap-theme caption{padding-top:10px 20px;padding-bottom:10px 20px;color:#aab2b9;text-align:left}#bootstrap-theme th{text-align:left}#bootstrap-theme .table{width:100%;max-width:100%;margin-bottom:20px}#bootstrap-theme .table>thead>tr>th, #bootstrap-theme .table>thead>tr>td, #bootstrap-theme .table>tbody>tr>th, #bootstrap-theme .table>tbody>tr>td, #bootstrap-theme .table>tfoot>tr>th, #bootstrap-theme .table>tfoot>tr>td{padding:10px 20px;line-height:1.5384615385;vertical-align:top;border-top:1px solid #e8eef0}#bootstrap-theme .table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #e8eef0}#bootstrap-theme .table>caption+thead>tr:first-child>th, #bootstrap-theme .table>caption+thead>tr:first-child>td, #bootstrap-theme .table>colgroup+thead>tr:first-child>th, #bootstrap-theme .table>colgroup+thead>tr:first-child>td, #bootstrap-theme .table>thead:first-child>tr:first-child>th, #bootstrap-theme .table>thead:first-child>tr:first-child>td{border-top:0}#bootstrap-theme .table>tbody+tbody{border-top:2px solid #e8eef0}#bootstrap-theme .table .table{background-color:#e8eef0}#bootstrap-theme .table-condensed>thead>tr>th, #bootstrap-theme .table-condensed>thead>tr>td, #bootstrap-theme .table-condensed>tbody>tr>th, #bootstrap-theme .table-condensed>tbody>tr>td, #bootstrap-theme .table-condensed>tfoot>tr>th, #bootstrap-theme .table-condensed>tfoot>tr>td{padding:5px}#bootstrap-theme .table-bordered{border:1px solid #e8eef0}#bootstrap-theme .table-bordered>thead>tr>th, #bootstrap-theme .table-bordered>thead>tr>td, #bootstrap-theme .table-bordered>tbody>tr>th, #bootstrap-theme .table-bordered>tbody>tr>td, #bootstrap-theme .table-bordered>tfoot>tr>th, #bootstrap-theme .table-bordered>tfoot>tr>td{border:1px solid #e8eef0}#bootstrap-theme .table-bordered>thead>tr>th, #bootstrap-theme .table-bordered>thead>tr>td{border-bottom-width:2px}#bootstrap-theme .table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}#bootstrap-theme .table-hover>tbody>tr:hover{background-color:#f5f5f5}#bootstrap-theme .table>thead>tr>td.active, #bootstrap-theme .table>thead>tr>th.active, #bootstrap-theme .table>thead>tr.active>td, #bootstrap-theme .table>thead>tr.active>th, #bootstrap-theme .table>tbody>tr>td.active, #bootstrap-theme .table>tbody>tr>th.active, #bootstrap-theme .table>tbody>tr.active>td, #bootstrap-theme .table>tbody>tr.active>th, #bootstrap-theme .table>tfoot>tr>td.active, #bootstrap-theme .table>tfoot>tr>th.active, #bootstrap-theme .table>tfoot>tr.active>td, #bootstrap-theme .table>tfoot>tr.active>th{background-color:#f5f5f5}#bootstrap-theme .table-hover>tbody>tr>td.active:hover, #bootstrap-theme .table-hover>tbody>tr>th.active:hover, #bootstrap-theme .table-hover>tbody>tr.active:hover>td, #bootstrap-theme .table-hover>tbody>tr:hover>.active, #bootstrap-theme .table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}#bootstrap-theme .table>thead>tr>td.success, #bootstrap-theme .table>thead>tr>th.success, #bootstrap-theme .table>thead>tr.success>td, #bootstrap-theme .table>thead>tr.success>th, #bootstrap-theme .table>tbody>tr>td.success, #bootstrap-theme .table>tbody>tr>th.success, #bootstrap-theme .table>tbody>tr.success>td, #bootstrap-theme .table>tbody>tr.success>th, #bootstrap-theme .table>tfoot>tr>td.success, #bootstrap-theme .table>tfoot>tr>th.success, #bootstrap-theme .table>tfoot>tr.success>td, #bootstrap-theme .table>tfoot>tr.success>th{background-color:#dff0d8}#bootstrap-theme .table-hover>tbody>tr>td.success:hover, #bootstrap-theme .table-hover>tbody>tr>th.success:hover, #bootstrap-theme .table-hover>tbody>tr.success:hover>td, #bootstrap-theme .table-hover>tbody>tr:hover>.success, #bootstrap-theme .table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}#bootstrap-theme .table>thead>tr>td.info, #bootstrap-theme .table>thead>tr>th.info, #bootstrap-theme .table>thead>tr.info>td, #bootstrap-theme .table>thead>tr.info>th, #bootstrap-theme .table>tbody>tr>td.info, #bootstrap-theme .table>tbody>tr>th.info, #bootstrap-theme .table>tbody>tr.info>td, #bootstrap-theme .table>tbody>tr.info>th, #bootstrap-theme .table>tfoot>tr>td.info, #bootstrap-theme .table>tfoot>tr>th.info, #bootstrap-theme .table>tfoot>tr.info>td, #bootstrap-theme .table>tfoot>tr.info>th{background-color:#d9edf7}#bootstrap-theme .table-hover>tbody>tr>td.info:hover, #bootstrap-theme .table-hover>tbody>tr>th.info:hover, #bootstrap-theme .table-hover>tbody>tr.info:hover>td, #bootstrap-theme .table-hover>tbody>tr:hover>.info, #bootstrap-theme .table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}#bootstrap-theme .table>thead>tr>td.warning, #bootstrap-theme .table>thead>tr>th.warning, #bootstrap-theme .table>thead>tr.warning>td, #bootstrap-theme .table>thead>tr.warning>th, #bootstrap-theme .table>tbody>tr>td.warning, #bootstrap-theme .table>tbody>tr>th.warning, #bootstrap-theme .table>tbody>tr.warning>td, #bootstrap-theme .table>tbody>tr.warning>th, #bootstrap-theme .table>tfoot>tr>td.warning, #bootstrap-theme .table>tfoot>tr>th.warning, #bootstrap-theme .table>tfoot>tr.warning>td, #bootstrap-theme .table>tfoot>tr.warning>th{background-color:#fcf8e3}#bootstrap-theme .table-hover>tbody>tr>td.warning:hover, #bootstrap-theme .table-hover>tbody>tr>th.warning:hover, #bootstrap-theme .table-hover>tbody>tr.warning:hover>td, #bootstrap-theme .table-hover>tbody>tr:hover>.warning, #bootstrap-theme .table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}#bootstrap-theme .table>thead>tr>td.danger, #bootstrap-theme .table>thead>tr>th.danger, #bootstrap-theme .table>thead>tr.danger>td, #bootstrap-theme .table>thead>tr.danger>th, #bootstrap-theme .table>tbody>tr>td.danger, #bootstrap-theme .table>tbody>tr>th.danger, #bootstrap-theme .table>tbody>tr.danger>td, #bootstrap-theme .table>tbody>tr.danger>th, #bootstrap-theme .table>tfoot>tr>td.danger, #bootstrap-theme .table>tfoot>tr>th.danger, #bootstrap-theme .table>tfoot>tr.danger>td, #bootstrap-theme .table>tfoot>tr.danger>th{background-color:#f2dede}#bootstrap-theme .table-hover>tbody>tr>td.danger:hover, #bootstrap-theme .table-hover>tbody>tr>th.danger:hover, #bootstrap-theme .table-hover>tbody>tr.danger:hover>td, #bootstrap-theme .table-hover>tbody>tr:hover>.danger, #bootstrap-theme .table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}#bootstrap-theme .table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width: 767px){#bootstrap-theme .table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #e8eef0}#bootstrap-theme .table-responsive>.table{margin-bottom:0}#bootstrap-theme .table-responsive>.table>thead>tr>th, #bootstrap-theme .table-responsive>.table>thead>tr>td, #bootstrap-theme .table-responsive>.table>tbody>tr>th, #bootstrap-theme .table-responsive>.table>tbody>tr>td, #bootstrap-theme .table-responsive>.table>tfoot>tr>th, #bootstrap-theme .table-responsive>.table>tfoot>tr>td{white-space:nowrap}#bootstrap-theme .table-responsive>.table-bordered{border:0}#bootstrap-theme .table-responsive>.table-bordered>thead>tr>th:first-child, #bootstrap-theme .table-responsive>.table-bordered>thead>tr>td:first-child, #bootstrap-theme .table-responsive>.table-bordered>tbody>tr>th:first-child, #bootstrap-theme .table-responsive>.table-bordered>tbody>tr>td:first-child, #bootstrap-theme .table-responsive>.table-bordered>tfoot>tr>th:first-child, #bootstrap-theme .table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}#bootstrap-theme .table-responsive>.table-bordered>thead>tr>th:last-child, #bootstrap-theme .table-responsive>.table-bordered>thead>tr>td:last-child, #bootstrap-theme .table-responsive>.table-bordered>tbody>tr>th:last-child, #bootstrap-theme .table-responsive>.table-bordered>tbody>tr>td:last-child, #bootstrap-theme .table-responsive>.table-bordered>tfoot>tr>th:last-child, #bootstrap-theme .table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}#bootstrap-theme .table-responsive>.table-bordered>tbody>tr:last-child>th, #bootstrap-theme .table-responsive>.table-bordered>tbody>tr:last-child>td, #bootstrap-theme .table-responsive>.table-bordered>tfoot>tr:last-child>th, #bootstrap-theme .table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}#bootstrap-theme fieldset{min-width:0;padding:0;margin:0;border:0}#bootstrap-theme legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:19.5px;line-height:inherit;color:#4d4d69;border:0;border-bottom:1px solid #e5e5e5}#bootstrap-theme label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}#bootstrap-theme input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;appearance:none}#bootstrap-theme input[type="radio"], #bootstrap-theme input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}#bootstrap-theme input[type="radio"][disabled], #bootstrap-theme input[type="radio"].disabled, #bootstrap-theme fieldset[disabled] input[type="radio"], #bootstrap-theme input[type="checkbox"][disabled], #bootstrap-theme input[type="checkbox"].disabled, #bootstrap-theme fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}#bootstrap-theme input[type="file"]{display:block}#bootstrap-theme input[type="range"]{display:block;width:100%}#bootstrap-theme select[multiple], #bootstrap-theme select[size]{height:auto}#bootstrap-theme input[type="file"]:focus, #bootstrap-theme input[type="radio"]:focus, #bootstrap-theme input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}#bootstrap-theme output{display:block;padding-top:5px;font-size:13px;line-height:1.5384615385;color:#464354}#bootstrap-theme .form-control{display:block;width:100%;height:30px;padding:4px 10px;font-size:13px;line-height:1.5384615385;color:#464354;background-color:#fff;background-image:none;border:1px solid #c2cfd8;border-radius:2px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;-o-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s}#bootstrap-theme .form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}#bootstrap-theme .form-control::-moz-placeholder{color:#9494a5;opacity:1}#bootstrap-theme .form-control:-ms-input-placeholder{color:#9494a5}#bootstrap-theme .form-control::-webkit-input-placeholder{color:#9494a5}#bootstrap-theme .form-control::-ms-expand{background-color:transparent;border:0}#bootstrap-theme .form-control[disabled], #bootstrap-theme .form-control[readonly], #bootstrap-theme fieldset[disabled] .form-control{background-color:#f3f6f7;opacity:1}#bootstrap-theme .form-control[disabled], #bootstrap-theme fieldset[disabled] .form-control{cursor:not-allowed}#bootstrap-theme textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio: 0){#bootstrap-theme input[type="date"].form-control, #bootstrap-theme input[type="time"].form-control, #bootstrap-theme input[type="datetime-local"].form-control, #bootstrap-theme input[type="month"].form-control{line-height:30px}#bootstrap-theme input[type="date"].input-sm, #bootstrap-theme .input-group-sm>input.form-control[type="date"], #bootstrap-theme .input-group-sm>input.input-group-addon[type="date"], #bootstrap-theme .input-group-sm>.input-group-btn>input.btn[type="date"], #bootstrap-theme .input-group-sm input[type="date"], #bootstrap-theme input[type="time"].input-sm, #bootstrap-theme .input-group-sm>input.form-control[type="time"], #bootstrap-theme .input-group-sm>input.input-group-addon[type="time"], #bootstrap-theme .input-group-sm>.input-group-btn>input.btn[type="time"], #bootstrap-theme .input-group-sm input[type="time"], #bootstrap-theme input[type="datetime-local"].input-sm, #bootstrap-theme .input-group-sm>input.form-control[type="datetime-local"], #bootstrap-theme .input-group-sm>input.input-group-addon[type="datetime-local"], #bootstrap-theme .input-group-sm>.input-group-btn>input.btn[type="datetime-local"], #bootstrap-theme .input-group-sm input[type="datetime-local"], #bootstrap-theme input[type="month"].input-sm, #bootstrap-theme .input-group-sm>input.form-control[type="month"], #bootstrap-theme .input-group-sm>input.input-group-addon[type="month"], #bootstrap-theme .input-group-sm>.input-group-btn>input.btn[type="month"], #bootstrap-theme .input-group-sm input[type="month"]{line-height:24px}#bootstrap-theme input[type="date"].input-lg, #bootstrap-theme .input-group-lg>input.form-control[type="date"], #bootstrap-theme .input-group-lg>input.input-group-addon[type="date"], #bootstrap-theme .input-group-lg>.input-group-btn>input.btn[type="date"], #bootstrap-theme .input-group-lg input[type="date"], #bootstrap-theme input[type="time"].input-lg, #bootstrap-theme .input-group-lg>input.form-control[type="time"], #bootstrap-theme .input-group-lg>input.input-group-addon[type="time"], #bootstrap-theme .input-group-lg>.input-group-btn>input.btn[type="time"], #bootstrap-theme .input-group-lg input[type="time"], #bootstrap-theme input[type="datetime-local"].input-lg, #bootstrap-theme .input-group-lg>input.form-control[type="datetime-local"], #bootstrap-theme .input-group-lg>input.input-group-addon[type="datetime-local"], #bootstrap-theme .input-group-lg>.input-group-btn>input.btn[type="datetime-local"], #bootstrap-theme .input-group-lg input[type="datetime-local"], #bootstrap-theme input[type="month"].input-lg, #bootstrap-theme .input-group-lg>input.form-control[type="month"], #bootstrap-theme .input-group-lg>input.input-group-addon[type="month"], #bootstrap-theme .input-group-lg>.input-group-btn>input.btn[type="month"], #bootstrap-theme .input-group-lg input[type="month"]{line-height:45px}}#bootstrap-theme .form-group{margin-bottom:15px}#bootstrap-theme .radio, #bootstrap-theme .checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}#bootstrap-theme .radio.disabled label, #bootstrap-theme fieldset[disabled] .radio label, #bootstrap-theme .checkbox.disabled label, #bootstrap-theme fieldset[disabled] .checkbox label{cursor:not-allowed}#bootstrap-theme .radio label, #bootstrap-theme .checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}#bootstrap-theme .radio input[type="radio"], #bootstrap-theme .radio-inline input[type="radio"], #bootstrap-theme .checkbox input[type="checkbox"], #bootstrap-theme .checkbox-inline input[type="checkbox"]{position:absolute;margin-top:4px \9;margin-left:-20px}#bootstrap-theme .radio+.radio, #bootstrap-theme .checkbox+.checkbox{margin-top:-5px}#bootstrap-theme .radio-inline, #bootstrap-theme .checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}#bootstrap-theme .radio-inline.disabled, #bootstrap-theme fieldset[disabled] .radio-inline, #bootstrap-theme .checkbox-inline.disabled, #bootstrap-theme fieldset[disabled] .checkbox-inline{cursor:not-allowed}#bootstrap-theme .radio-inline+.radio-inline, #bootstrap-theme .checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}#bootstrap-theme .form-control-static{min-height:33px;padding-top:5px;padding-bottom:5px;margin-bottom:0}#bootstrap-theme .form-control-static.input-lg, #bootstrap-theme .input-group-lg>.form-control-static.form-control, #bootstrap-theme .input-group-lg>.form-control-static.input-group-addon, #bootstrap-theme .input-group-lg>.input-group-btn>.form-control-static.btn, #bootstrap-theme .form-control-static.input-sm, #bootstrap-theme .input-group-sm>.form-control-static.form-control, #bootstrap-theme .input-group-sm>.form-control-static.input-group-addon, #bootstrap-theme .input-group-sm>.input-group-btn>.form-control-static.btn{padding-right:0;padding-left:0}#bootstrap-theme .input-sm, #bootstrap-theme .input-group-sm>.form-control, #bootstrap-theme .input-group-sm>.input-group-addon, #bootstrap-theme .input-group-sm>.input-group-btn>.btn{height:24px;padding:2px 10px;font-size:12px;line-height:1.5;border-radius:2px}#bootstrap-theme select.input-sm, #bootstrap-theme .input-group-sm>select.form-control, #bootstrap-theme .input-group-sm>select.input-group-addon, #bootstrap-theme .input-group-sm>.input-group-btn>select.btn{height:24px;line-height:24px}#bootstrap-theme textarea.input-sm, #bootstrap-theme .input-group-sm>textarea.form-control, #bootstrap-theme .input-group-sm>textarea.input-group-addon, #bootstrap-theme .input-group-sm>.input-group-btn>textarea.btn, #bootstrap-theme select[multiple].input-sm, #bootstrap-theme .input-group-sm>select.form-control[multiple], #bootstrap-theme .input-group-sm>select.input-group-addon[multiple], #bootstrap-theme .input-group-sm>.input-group-btn>select.btn[multiple]{height:auto}#bootstrap-theme .form-group-sm .form-control{height:24px;padding:2px 10px;font-size:12px;line-height:1.5;border-radius:2px}#bootstrap-theme .form-group-sm select.form-control{height:24px;line-height:24px}#bootstrap-theme .form-group-sm textarea.form-control, #bootstrap-theme .form-group-sm select[multiple].form-control{height:auto}#bootstrap-theme .form-group-sm .form-control-static{height:24px;min-height:32px;padding:3px 10px;font-size:12px;line-height:1.5}#bootstrap-theme .input-lg, #bootstrap-theme .input-group-lg>.form-control, #bootstrap-theme .input-group-lg>.input-group-addon, #bootstrap-theme .input-group-lg>.input-group-btn>.btn{height:45px;padding:10px 16px;font-size:17px;line-height:1.3333333;border-radius:2px}#bootstrap-theme select.input-lg, #bootstrap-theme .input-group-lg>select.form-control, #bootstrap-theme .input-group-lg>select.input-group-addon, #bootstrap-theme .input-group-lg>.input-group-btn>select.btn{height:45px;line-height:45px}#bootstrap-theme textarea.input-lg, #bootstrap-theme .input-group-lg>textarea.form-control, #bootstrap-theme .input-group-lg>textarea.input-group-addon, #bootstrap-theme .input-group-lg>.input-group-btn>textarea.btn, #bootstrap-theme select[multiple].input-lg, #bootstrap-theme .input-group-lg>select.form-control[multiple], #bootstrap-theme .input-group-lg>select.input-group-addon[multiple], #bootstrap-theme .input-group-lg>.input-group-btn>select.btn[multiple]{height:auto}#bootstrap-theme .form-group-lg .form-control{height:45px;padding:10px 16px;font-size:17px;line-height:1.3333333;border-radius:2px}#bootstrap-theme .form-group-lg select.form-control{height:45px;line-height:45px}#bootstrap-theme .form-group-lg textarea.form-control, #bootstrap-theme .form-group-lg select[multiple].form-control{height:auto}#bootstrap-theme .form-group-lg .form-control-static{height:45px;min-height:37px;padding:11px 16px;font-size:17px;line-height:1.3333333}#bootstrap-theme .has-feedback{position:relative}#bootstrap-theme .has-feedback .form-control{padding-right:37.5px}#bootstrap-theme .form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:30px;height:30px;line-height:30px;text-align:center;pointer-events:none}#bootstrap-theme .input-lg+.form-control-feedback, #bootstrap-theme .input-group-lg>.form-control+.form-control-feedback, #bootstrap-theme .input-group-lg>.input-group-addon+.form-control-feedback, #bootstrap-theme .input-group-lg>.input-group-btn>.btn+.form-control-feedback, #bootstrap-theme .input-group-lg+.form-control-feedback, #bootstrap-theme .form-group-lg .form-control+.form-control-feedback{width:45px;height:45px;line-height:45px}#bootstrap-theme .input-sm+.form-control-feedback, #bootstrap-theme .input-group-sm>.form-control+.form-control-feedback, #bootstrap-theme .input-group-sm>.input-group-addon+.form-control-feedback, #bootstrap-theme .input-group-sm>.input-group-btn>.btn+.form-control-feedback, #bootstrap-theme .input-group-sm+.form-control-feedback, #bootstrap-theme .form-group-sm .form-control+.form-control-feedback{width:24px;height:24px;line-height:24px}#bootstrap-theme .has-success .help-block, #bootstrap-theme .has-success .control-label, #bootstrap-theme .has-success .radio, #bootstrap-theme .has-success .checkbox, #bootstrap-theme .has-success .radio-inline, #bootstrap-theme .has-success .checkbox-inline, #bootstrap-theme .has-success.radio label, #bootstrap-theme .has-success.checkbox label, #bootstrap-theme .has-success.radio-inline label, #bootstrap-theme .has-success.checkbox-inline label{color:#4d994d}#bootstrap-theme .has-success .form-control{border-color:#4d994d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}#bootstrap-theme .has-success .form-control:focus{border-color:#3c773c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #89c389;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #89c389}#bootstrap-theme .has-success .input-group-addon{color:#4d994d;background-color:#dff0d8;border-color:#4d994d}#bootstrap-theme .has-success .form-control-feedback{color:#4d994d}#bootstrap-theme .has-warning .help-block, #bootstrap-theme .has-warning .control-label, #bootstrap-theme .has-warning .radio, #bootstrap-theme .has-warning .checkbox, #bootstrap-theme .has-warning .radio-inline, #bootstrap-theme .has-warning .checkbox-inline, #bootstrap-theme .has-warning.radio label, #bootstrap-theme .has-warning.checkbox label, #bootstrap-theme .has-warning.radio-inline label, #bootstrap-theme .has-warning.checkbox-inline label{color:#bf5900}#bootstrap-theme .has-warning .form-control{border-color:#bf5900;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}#bootstrap-theme .has-warning .form-control:focus{border-color:#8c4100;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ff8b26;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ff8b26}#bootstrap-theme .has-warning .input-group-addon{color:#bf5900;background-color:#fcf8e3;border-color:#bf5900}#bootstrap-theme .has-warning .form-control-feedback{color:#bf5900}#bootstrap-theme .has-error .help-block, #bootstrap-theme .has-error .control-label, #bootstrap-theme .has-error .radio, #bootstrap-theme .has-error .checkbox, #bootstrap-theme .has-error .radio-inline, #bootstrap-theme .has-error .checkbox-inline, #bootstrap-theme .has-error.radio label, #bootstrap-theme .has-error.checkbox label, #bootstrap-theme .has-error.radio-inline label, #bootstrap-theme .has-error.checkbox-inline label{color:#cf3458}#bootstrap-theme .has-error .form-control{border-color:#cf3458;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}#bootstrap-theme .has-error .form-control:focus{border-color:#a82846;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #e3869c;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #e3869c}#bootstrap-theme .has-error .input-group-addon{color:#cf3458;background-color:#f2dede;border-color:#cf3458}#bootstrap-theme .has-error .form-control-feedback{color:#cf3458}#bootstrap-theme .has-feedback label ~ .form-control-feedback{top:25px}#bootstrap-theme .has-feedback label.sr-only ~ .form-control-feedback{top:0}#bootstrap-theme .help-block{display:block;margin-top:5px;margin-bottom:10px;color:#8b8baa}@media (min-width: 768px){#bootstrap-theme .form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}#bootstrap-theme .form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}#bootstrap-theme .form-inline .form-control-static{display:inline-block}#bootstrap-theme .form-inline .input-group{display:inline-table;vertical-align:middle}#bootstrap-theme .form-inline .input-group .input-group-addon, #bootstrap-theme .form-inline .input-group .input-group-btn, #bootstrap-theme .form-inline .input-group .form-control{width:auto}#bootstrap-theme .form-inline .input-group>.form-control{width:100%}#bootstrap-theme .form-inline .control-label{margin-bottom:0;vertical-align:middle}#bootstrap-theme .form-inline .radio, #bootstrap-theme .form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}#bootstrap-theme .form-inline .radio label, #bootstrap-theme .form-inline .checkbox label{padding-left:0}#bootstrap-theme .form-inline .radio input[type="radio"], #bootstrap-theme .form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}#bootstrap-theme .form-inline .has-feedback .form-control-feedback{top:0}}#bootstrap-theme .form-horizontal .radio, #bootstrap-theme .form-horizontal .checkbox, #bootstrap-theme .form-horizontal .radio-inline, #bootstrap-theme .form-horizontal .checkbox-inline{padding-top:5px;margin-top:0;margin-bottom:0}#bootstrap-theme .form-horizontal .radio, #bootstrap-theme .form-horizontal .checkbox{min-height:25px}#bootstrap-theme .form-horizontal .form-group{margin-right:-8px;margin-left:-8px}#bootstrap-theme .form-horizontal .form-group:before, #bootstrap-theme .form-horizontal .form-group:after{display:table;content:" "}#bootstrap-theme .form-horizontal .form-group:after{clear:both}@media (min-width: 768px){#bootstrap-theme .form-horizontal .control-label{padding-top:5px;margin-bottom:0;text-align:right}}#bootstrap-theme .form-horizontal .has-feedback .form-control-feedback{right:8px}@media (min-width: 768px){#bootstrap-theme .form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:17px}}@media (min-width: 768px){#bootstrap-theme .form-horizontal .form-group-sm .control-label{padding-top:3px;font-size:12px}}#bootstrap-theme .btn{display:inline-block;margin-bottom:0;font-weight:500;text-align:center;white-space:nowrap;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:4px 10px;font-size:13px;line-height:1.5384615385;border-radius:2px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#bootstrap-theme .btn:focus, #bootstrap-theme .btn.focus, #bootstrap-theme .btn:active:focus, #bootstrap-theme .btn:active.focus, #bootstrap-theme .btn.active:focus, #bootstrap-theme .btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}#bootstrap-theme .btn:hover, #bootstrap-theme .btn:focus, #bootstrap-theme .btn.focus{color:#464354;text-decoration:none}#bootstrap-theme .btn:active, #bootstrap-theme .btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}#bootstrap-theme .btn.disabled, #bootstrap-theme .btn[disabled], #bootstrap-theme fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:.65;-webkit-box-shadow:none;box-shadow:none}#bootstrap-theme a.btn.disabled, #bootstrap-theme fieldset[disabled] a.btn{pointer-events:none}#bootstrap-theme .btn-default{color:#464354;background-color:#fff;border-color:#fff}#bootstrap-theme .btn-default:focus, #bootstrap-theme .btn-default.focus{color:#464354;background-color:#e6e5e5;border-color:#bfbfbf}#bootstrap-theme .btn-default:hover{color:#464354;background-color:#e6e5e5;border-color:#e0e0e0}#bootstrap-theme .btn-default:active, #bootstrap-theme .btn-default.active, #bootstrap-theme .open>.btn-default.dropdown-toggle{color:#464354;background-color:#e6e5e5;background-image:none;border-color:#e0e0e0}#bootstrap-theme .btn-default:active:hover, #bootstrap-theme .btn-default:active:focus, #bootstrap-theme .btn-default:active.focus, #bootstrap-theme .btn-default.active:hover, #bootstrap-theme .btn-default.active:focus, #bootstrap-theme .btn-default.active.focus, #bootstrap-theme .open>.btn-default.dropdown-toggle:hover, #bootstrap-theme .open>.btn-default.dropdown-toggle:focus, #bootstrap-theme .open>.btn-default.dropdown-toggle.focus{color:#464354;background-color:#d4d4d4;border-color:#bfbfbf}#bootstrap-theme .btn-default.disabled:hover, #bootstrap-theme .btn-default.disabled:focus, #bootstrap-theme .btn-default.disabled.focus, #bootstrap-theme .btn-default[disabled]:hover, #bootstrap-theme .btn-default[disabled]:focus, #bootstrap-theme .btn-default[disabled].focus, #bootstrap-theme fieldset[disabled] .btn-default:hover, #bootstrap-theme fieldset[disabled] .btn-default:focus, #bootstrap-theme fieldset[disabled] .btn-default.focus{background-color:#fff;border-color:#fff}#bootstrap-theme .btn-default .badge{color:#fff;background-color:#464354}#bootstrap-theme .btn-primary{color:#fff;background-color:#0071bd;border-color:#0062a4}#bootstrap-theme .btn-primary:focus, #bootstrap-theme .btn-primary.focus{color:#fff;background-color:#00538a;border-color:#001624}#bootstrap-theme .btn-primary:hover{color:#fff;background-color:#00538a;border-color:#003d66}#bootstrap-theme .btn-primary:active, #bootstrap-theme .btn-primary.active, #bootstrap-theme .open>.btn-primary.dropdown-toggle{color:#fff;background-color:#00538a;background-image:none;border-color:#003d66}#bootstrap-theme .btn-primary:active:hover, #bootstrap-theme .btn-primary:active:focus, #bootstrap-theme .btn-primary:active.focus, #bootstrap-theme .btn-primary.active:hover, #bootstrap-theme .btn-primary.active:focus, #bootstrap-theme .btn-primary.active.focus, #bootstrap-theme .open>.btn-primary.dropdown-toggle:hover, #bootstrap-theme .open>.btn-primary.dropdown-toggle:focus, #bootstrap-theme .open>.btn-primary.dropdown-toggle.focus{color:#fff;background-color:#003d66;border-color:#001624}#bootstrap-theme .btn-primary.disabled:hover, #bootstrap-theme .btn-primary.disabled:focus, #bootstrap-theme .btn-primary.disabled.focus, #bootstrap-theme .btn-primary[disabled]:hover, #bootstrap-theme .btn-primary[disabled]:focus, #bootstrap-theme .btn-primary[disabled].focus, #bootstrap-theme fieldset[disabled] .btn-primary:hover, #bootstrap-theme fieldset[disabled] .btn-primary:focus, #bootstrap-theme fieldset[disabled] .btn-primary.focus{background-color:#0071bd;border-color:#0062a4}#bootstrap-theme .btn-primary .badge{color:#0071bd;background-color:#fff}#bootstrap-theme .btn-success{color:#464354;background-color:#44cb7e;border-color:#35c071}#bootstrap-theme .btn-success:focus, #bootstrap-theme .btn-success.focus{color:#464354;background-color:#30ac65;border-color:#1a5c36}#bootstrap-theme .btn-success:hover{color:#464354;background-color:#30ac65;border-color:#289055}#bootstrap-theme .btn-success:active, #bootstrap-theme .btn-success.active, #bootstrap-theme .open>.btn-success.dropdown-toggle{color:#464354;background-color:#30ac65;background-image:none;border-color:#289055}#bootstrap-theme .btn-success:active:hover, #bootstrap-theme .btn-success:active:focus, #bootstrap-theme .btn-success:active.focus, #bootstrap-theme .btn-success.active:hover, #bootstrap-theme .btn-success.active:focus, #bootstrap-theme .btn-success.active.focus, #bootstrap-theme .open>.btn-success.dropdown-toggle:hover, #bootstrap-theme .open>.btn-success.dropdown-toggle:focus, #bootstrap-theme .open>.btn-success.dropdown-toggle.focus{color:#464354;background-color:#289055;border-color:#1a5c36}#bootstrap-theme .btn-success.disabled:hover, #bootstrap-theme .btn-success.disabled:focus, #bootstrap-theme .btn-success.disabled.focus, #bootstrap-theme .btn-success[disabled]:hover, #bootstrap-theme .btn-success[disabled]:focus, #bootstrap-theme .btn-success[disabled].focus, #bootstrap-theme fieldset[disabled] .btn-success:hover, #bootstrap-theme fieldset[disabled] .btn-success:focus, #bootstrap-theme fieldset[disabled] .btn-success.focus{background-color:#44cb7e;border-color:#35c071}#bootstrap-theme .btn-success .badge{color:#44cb7e;background-color:#464354}#bootstrap-theme .btn-info{color:#fff;background-color:#0071bd;border-color:#0062a4}#bootstrap-theme .btn-info:focus, #bootstrap-theme .btn-info.focus{color:#fff;background-color:#00538a;border-color:#001624}#bootstrap-theme .btn-info:hover{color:#fff;background-color:#00538a;border-color:#003d66}#bootstrap-theme .btn-info:active, #bootstrap-theme .btn-info.active, #bootstrap-theme .open>.btn-info.dropdown-toggle{color:#fff;background-color:#00538a;background-image:none;border-color:#003d66}#bootstrap-theme .btn-info:active:hover, #bootstrap-theme .btn-info:active:focus, #bootstrap-theme .btn-info:active.focus, #bootstrap-theme .btn-info.active:hover, #bootstrap-theme .btn-info.active:focus, #bootstrap-theme .btn-info.active.focus, #bootstrap-theme .open>.btn-info.dropdown-toggle:hover, #bootstrap-theme .open>.btn-info.dropdown-toggle:focus, #bootstrap-theme .open>.btn-info.dropdown-toggle.focus{color:#fff;background-color:#003d66;border-color:#001624}#bootstrap-theme .btn-info.disabled:hover, #bootstrap-theme .btn-info.disabled:focus, #bootstrap-theme .btn-info.disabled.focus, #bootstrap-theme .btn-info[disabled]:hover, #bootstrap-theme .btn-info[disabled]:focus, #bootstrap-theme .btn-info[disabled].focus, #bootstrap-theme fieldset[disabled] .btn-info:hover, #bootstrap-theme fieldset[disabled] .btn-info:focus, #bootstrap-theme fieldset[disabled] .btn-info.focus{background-color:#0071bd;border-color:#0062a4}#bootstrap-theme .btn-info .badge{color:#0071bd;background-color:#fff}#bootstrap-theme .btn-warning{color:#464354;background-color:#e6ab5e;border-color:#e39f48}#bootstrap-theme .btn-warning:focus, #bootstrap-theme .btn-warning.focus{color:#464354;background-color:#df9432;border-color:#945e17}#bootstrap-theme .btn-warning:hover{color:#464354;background-color:#df9432;border-color:#cd8220}#bootstrap-theme .btn-warning:active, #bootstrap-theme .btn-warning.active, #bootstrap-theme .open>.btn-warning.dropdown-toggle{color:#464354;background-color:#df9432;background-image:none;border-color:#cd8220}#bootstrap-theme .btn-warning:active:hover, #bootstrap-theme .btn-warning:active:focus, #bootstrap-theme .btn-warning:active.focus, #bootstrap-theme .btn-warning.active:hover, #bootstrap-theme .btn-warning.active:focus, #bootstrap-theme .btn-warning.active.focus, #bootstrap-theme .open>.btn-warning.dropdown-toggle:hover, #bootstrap-theme .open>.btn-warning.dropdown-toggle:focus, #bootstrap-theme .open>.btn-warning.dropdown-toggle.focus{color:#464354;background-color:#cd8220;border-color:#945e17}#bootstrap-theme .btn-warning.disabled:hover, #bootstrap-theme .btn-warning.disabled:focus, #bootstrap-theme .btn-warning.disabled.focus, #bootstrap-theme .btn-warning[disabled]:hover, #bootstrap-theme .btn-warning[disabled]:focus, #bootstrap-theme .btn-warning[disabled].focus, #bootstrap-theme fieldset[disabled] .btn-warning:hover, #bootstrap-theme fieldset[disabled] .btn-warning:focus, #bootstrap-theme fieldset[disabled] .btn-warning.focus{background-color:#e6ab5e;border-color:#e39f48}#bootstrap-theme .btn-warning .badge{color:#e6ab5e;background-color:#464354}#bootstrap-theme .btn-danger{color:#fff;background-color:#cf3458;border-color:#bd2d4e}#bootstrap-theme .btn-danger:focus, #bootstrap-theme .btn-danger.focus{color:#fff;background-color:#a82846;border-color:#561423}#bootstrap-theme .btn-danger:hover{color:#fff;background-color:#a82846;border-color:#8b213a}#bootstrap-theme .btn-danger:active, #bootstrap-theme .btn-danger.active, #bootstrap-theme .open>.btn-danger.dropdown-toggle{color:#fff;background-color:#a82846;background-image:none;border-color:#8b213a}#bootstrap-theme .btn-danger:active:hover, #bootstrap-theme .btn-danger:active:focus, #bootstrap-theme .btn-danger:active.focus, #bootstrap-theme .btn-danger.active:hover, #bootstrap-theme .btn-danger.active:focus, #bootstrap-theme .btn-danger.active.focus, #bootstrap-theme .open>.btn-danger.dropdown-toggle:hover, #bootstrap-theme .open>.btn-danger.dropdown-toggle:focus, #bootstrap-theme .open>.btn-danger.dropdown-toggle.focus{color:#fff;background-color:#8b213a;border-color:#561423}#bootstrap-theme .btn-danger.disabled:hover, #bootstrap-theme .btn-danger.disabled:focus, #bootstrap-theme .btn-danger.disabled.focus, #bootstrap-theme .btn-danger[disabled]:hover, #bootstrap-theme .btn-danger[disabled]:focus, #bootstrap-theme .btn-danger[disabled].focus, #bootstrap-theme fieldset[disabled] .btn-danger:hover, #bootstrap-theme fieldset[disabled] .btn-danger:focus, #bootstrap-theme fieldset[disabled] .btn-danger.focus{background-color:#cf3458;border-color:#bd2d4e}#bootstrap-theme .btn-danger .badge{color:#cf3458;background-color:#fff}#bootstrap-theme .btn-link{font-weight:400;color:#0071bd;border-radius:0}#bootstrap-theme .btn-link, #bootstrap-theme .btn-link:active, #bootstrap-theme .btn-link.active, #bootstrap-theme .btn-link[disabled], #bootstrap-theme fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}#bootstrap-theme .btn-link, #bootstrap-theme .btn-link:hover, #bootstrap-theme .btn-link:focus, #bootstrap-theme .btn-link:active{border-color:transparent}#bootstrap-theme .btn-link:hover, #bootstrap-theme .btn-link:focus{color:#004371;text-decoration:underline;background-color:transparent}#bootstrap-theme .btn-link[disabled]:hover, #bootstrap-theme .btn-link[disabled]:focus, #bootstrap-theme fieldset[disabled] .btn-link:hover, #bootstrap-theme fieldset[disabled] .btn-link:focus{color:#e8eef0;text-decoration:none}#bootstrap-theme .btn-lg, #bootstrap-theme .btn-group-lg>.btn{padding:10px 16px;font-size:17px;line-height:1.3333333;border-radius:6px}#bootstrap-theme .btn-sm, #bootstrap-theme .btn-group-sm>.btn{padding:2px 10px;font-size:12px;line-height:1.5;border-radius:2px}#bootstrap-theme .btn-xs, #bootstrap-theme .btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:2px}#bootstrap-theme .btn-block{display:block;width:100%}#bootstrap-theme .btn-block+.btn-block{margin-top:5px}#bootstrap-theme input[type="submit"].btn-block, #bootstrap-theme input[type="reset"].btn-block, #bootstrap-theme input[type="button"].btn-block{width:100%}#bootstrap-theme .fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}#bootstrap-theme .fade.in{opacity:1}#bootstrap-theme .collapse{display:none}#bootstrap-theme .collapse.in{display:block}#bootstrap-theme tr.collapse.in{display:table-row}#bootstrap-theme tbody.collapse.in{display:table-row-group}#bootstrap-theme .collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease}#bootstrap-theme .caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}#bootstrap-theme .dropup, #bootstrap-theme .dropdown{position:relative}#bootstrap-theme .dropdown-toggle:focus{outline:0}#bootstrap-theme .dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:13px;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:2px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175)}#bootstrap-theme .dropdown-menu.pull-right{right:0;left:auto}#bootstrap-theme .dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}#bootstrap-theme .dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.5384615385;color:#4d4d69;white-space:nowrap}#bootstrap-theme .dropdown-menu>li>a:hover, #bootstrap-theme .dropdown-menu>li>a:focus{color:#42425a;text-decoration:none;background-color:#f3f6f7}#bootstrap-theme .dropdown-menu>.active>a, #bootstrap-theme .dropdown-menu>.active>a:hover, #bootstrap-theme .dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#0071bd;outline:0}#bootstrap-theme .dropdown-menu>.disabled>a, #bootstrap-theme .dropdown-menu>.disabled>a:hover, #bootstrap-theme .dropdown-menu>.disabled>a:focus{color:#e8eef0}#bootstrap-theme .dropdown-menu>.disabled>a:hover, #bootstrap-theme .dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}#bootstrap-theme .open>.dropdown-menu{display:block}#bootstrap-theme .open>a{outline:0}#bootstrap-theme .dropdown-menu-right{right:0;left:auto}#bootstrap-theme .dropdown-menu-left{right:auto;left:0}#bootstrap-theme .dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.5384615385;color:#464354;white-space:nowrap}#bootstrap-theme .dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}#bootstrap-theme .pull-right>.dropdown-menu{right:0;left:auto}#bootstrap-theme .dropup .caret, #bootstrap-theme .navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9}#bootstrap-theme .dropup .dropdown-menu, #bootstrap-theme .navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width: 768px){#bootstrap-theme .navbar-right .dropdown-menu{right:0;left:auto}#bootstrap-theme .navbar-right .dropdown-menu-left{left:0;right:auto}}#bootstrap-theme .btn-group, #bootstrap-theme .btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}#bootstrap-theme .btn-group>.btn, #bootstrap-theme .btn-group-vertical>.btn{position:relative;float:left}#bootstrap-theme .btn-group>.btn:hover, #bootstrap-theme .btn-group>.btn:focus, #bootstrap-theme .btn-group>.btn:active, #bootstrap-theme .btn-group>.btn.active, #bootstrap-theme .btn-group-vertical>.btn:hover, #bootstrap-theme .btn-group-vertical>.btn:focus, #bootstrap-theme .btn-group-vertical>.btn:active, #bootstrap-theme .btn-group-vertical>.btn.active{z-index:2}#bootstrap-theme .btn-group .btn+.btn, #bootstrap-theme .btn-group .btn+.btn-group, #bootstrap-theme .btn-group .btn-group+.btn, #bootstrap-theme .btn-group .btn-group+.btn-group{margin-left:-1px}#bootstrap-theme .btn-toolbar{margin-left:-5px}#bootstrap-theme .btn-toolbar:before, #bootstrap-theme .btn-toolbar:after{display:table;content:" "}#bootstrap-theme .btn-toolbar:after{clear:both}#bootstrap-theme .btn-toolbar .btn, #bootstrap-theme .btn-toolbar .btn-group, #bootstrap-theme .btn-toolbar .input-group{float:left}#bootstrap-theme .btn-toolbar>.btn, #bootstrap-theme .btn-toolbar>.btn-group, #bootstrap-theme .btn-toolbar>.input-group{margin-left:5px}#bootstrap-theme .btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}#bootstrap-theme .btn-group>.btn:first-child{margin-left:0}#bootstrap-theme .btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}#bootstrap-theme .btn-group>.btn:last-child:not(:first-child), #bootstrap-theme .btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}#bootstrap-theme .btn-group>.btn-group{float:left}#bootstrap-theme .btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}#bootstrap-theme .btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child, #bootstrap-theme .btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}#bootstrap-theme .btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}#bootstrap-theme .btn-group .dropdown-toggle:active, #bootstrap-theme .btn-group.open .dropdown-toggle{outline:0}#bootstrap-theme .btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}#bootstrap-theme .btn-group>.btn-lg+.dropdown-toggle, #bootstrap-theme .btn-group-lg.btn-group>.btn+.dropdown-toggle{padding-right:12px;padding-left:12px}#bootstrap-theme .btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}#bootstrap-theme .btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}#bootstrap-theme .btn .caret{margin-left:0}#bootstrap-theme .btn-lg .caret, #bootstrap-theme .btn-group-lg>.btn .caret{border-width:5px 5px 0;border-bottom-width:0}#bootstrap-theme .dropup .btn-lg .caret, #bootstrap-theme .dropup .btn-group-lg>.btn .caret{border-width:0 5px 5px}#bootstrap-theme .btn-group-vertical>.btn, #bootstrap-theme .btn-group-vertical>.btn-group, #bootstrap-theme .btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}#bootstrap-theme .btn-group-vertical>.btn-group:before, #bootstrap-theme .btn-group-vertical>.btn-group:after{display:table;content:" "}#bootstrap-theme .btn-group-vertical>.btn-group:after{clear:both}#bootstrap-theme .btn-group-vertical>.btn-group>.btn{float:none}#bootstrap-theme .btn-group-vertical>.btn+.btn, #bootstrap-theme .btn-group-vertical>.btn+.btn-group, #bootstrap-theme .btn-group-vertical>.btn-group+.btn, #bootstrap-theme .btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}#bootstrap-theme .btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}#bootstrap-theme .btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-right-radius:0;border-bottom-left-radius:0}#bootstrap-theme .btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:2px;border-bottom-left-radius:2px}#bootstrap-theme .btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}#bootstrap-theme .btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child, #bootstrap-theme .btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}#bootstrap-theme .btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}#bootstrap-theme .btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}#bootstrap-theme .btn-group-justified>.btn, #bootstrap-theme .btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}#bootstrap-theme .btn-group-justified>.btn-group .btn{width:100%}#bootstrap-theme .btn-group-justified>.btn-group .dropdown-menu{left:auto}#bootstrap-theme [data-toggle="buttons"]>.btn input[type="radio"], #bootstrap-theme [data-toggle="buttons"]>.btn input[type="checkbox"], #bootstrap-theme [data-toggle="buttons"]>.btn-group>.btn input[type="radio"], #bootstrap-theme [data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}#bootstrap-theme .input-group{position:relative;display:table;border-collapse:separate}#bootstrap-theme .input-group[class*="col-"]{float:none;padding-right:0;padding-left:0}#bootstrap-theme .input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}#bootstrap-theme .input-group .form-control:focus{z-index:3}#bootstrap-theme .input-group-addon, #bootstrap-theme .input-group-btn, #bootstrap-theme .input-group .form-control{display:table-cell}#bootstrap-theme .input-group-addon:not(:first-child):not(:last-child), #bootstrap-theme .input-group-btn:not(:first-child):not(:last-child), #bootstrap-theme .input-group .form-control:not(:first-child):not(:last-child){border-radius:0}#bootstrap-theme .input-group-addon, #bootstrap-theme .input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}#bootstrap-theme .input-group-addon{padding:4px 10px;font-size:13px;font-weight:400;line-height:1;color:#464354;text-align:center;background-color:#f3f6f7;border:1px solid #c2cfd8;border-radius:2px}#bootstrap-theme .input-group-addon.input-sm, #bootstrap-theme .input-group-sm>.input-group-addon, #bootstrap-theme .input-group-sm>.input-group-btn>.input-group-addon.btn{padding:2px 10px;font-size:12px;border-radius:2px}#bootstrap-theme .input-group-addon.input-lg, #bootstrap-theme .input-group-lg>.input-group-addon, #bootstrap-theme .input-group-lg>.input-group-btn>.input-group-addon.btn{padding:10px 16px;font-size:17px;border-radius:2px}#bootstrap-theme .input-group-addon input[type="radio"], #bootstrap-theme .input-group-addon input[type="checkbox"]{margin-top:0}#bootstrap-theme .input-group .form-control:first-child, #bootstrap-theme .input-group-addon:first-child, #bootstrap-theme .input-group-btn:first-child>.btn, #bootstrap-theme .input-group-btn:first-child>.btn-group>.btn, #bootstrap-theme .input-group-btn:first-child>.dropdown-toggle, #bootstrap-theme .input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle), #bootstrap-theme .input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}#bootstrap-theme .input-group-addon:first-child{border-right:0}#bootstrap-theme .input-group .form-control:last-child, #bootstrap-theme .input-group-addon:last-child, #bootstrap-theme .input-group-btn:last-child>.btn, #bootstrap-theme .input-group-btn:last-child>.btn-group>.btn, #bootstrap-theme .input-group-btn:last-child>.dropdown-toggle, #bootstrap-theme .input-group-btn:first-child>.btn:not(:first-child), #bootstrap-theme .input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}#bootstrap-theme .input-group-addon:last-child{border-left:0}#bootstrap-theme .input-group-btn{position:relative;font-size:0;white-space:nowrap}#bootstrap-theme .input-group-btn>.btn{position:relative}#bootstrap-theme .input-group-btn>.btn+.btn{margin-left:-1px}#bootstrap-theme .input-group-btn>.btn:hover, #bootstrap-theme .input-group-btn>.btn:focus, #bootstrap-theme .input-group-btn>.btn:active{z-index:2}#bootstrap-theme .input-group-btn:first-child>.btn, #bootstrap-theme .input-group-btn:first-child>.btn-group{margin-right:-1px}#bootstrap-theme .input-group-btn:last-child>.btn, #bootstrap-theme .input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}#bootstrap-theme .nav{padding-left:0;margin-bottom:0;list-style:none}#bootstrap-theme .nav:before, #bootstrap-theme .nav:after{display:table;content:" "}#bootstrap-theme .nav:after{clear:both}#bootstrap-theme .nav>li{position:relative;display:block}#bootstrap-theme .nav>li>a{position:relative;display:block;padding:10px 20px}#bootstrap-theme .nav>li>a:hover, #bootstrap-theme .nav>li>a:focus{text-decoration:none;background-color:#f3f6f7}#bootstrap-theme .nav>li.disabled>a{color:#bcbcc8}#bootstrap-theme .nav>li.disabled>a:hover, #bootstrap-theme .nav>li.disabled>a:focus{color:#bcbcc8;text-decoration:none;cursor:not-allowed;background-color:transparent}#bootstrap-theme .nav .open>a, #bootstrap-theme .nav .open>a:hover, #bootstrap-theme .nav .open>a:focus{background-color:#f3f6f7;border-color:#0071bd}#bootstrap-theme .nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}#bootstrap-theme .nav>li>a>img{max-width:none}#bootstrap-theme .nav-tabs{border-bottom:1px solid #d3dee2}#bootstrap-theme .nav-tabs>li{float:left;margin-bottom:-1px}#bootstrap-theme .nav-tabs>li>a{margin-right:2px;line-height:1.5384615385;border:1px solid transparent;border-radius:2px 2px 0 0}#bootstrap-theme .nav-tabs>li>a:hover{border-color:#f3f6f7 #f3f6f7 #d3dee2}#bootstrap-theme .nav-tabs>li.active>a, #bootstrap-theme .nav-tabs>li.active>a:hover, #bootstrap-theme .nav-tabs>li.active>a:focus{color:#0071bd;cursor:default;background-color:#fff;border:1px solid #d3dee2;border-bottom-color:transparent}#bootstrap-theme .nav-pills>li{float:left}#bootstrap-theme .nav-pills>li>a{border-radius:2px}#bootstrap-theme .nav-pills>li+li{margin-left:2px}#bootstrap-theme .nav-pills>li.active>a, #bootstrap-theme .nav-pills>li.active>a:hover, #bootstrap-theme .nav-pills>li.active>a:focus{color:#fff;background-color:#0071bd}#bootstrap-theme .nav-stacked>li{float:none}#bootstrap-theme .nav-stacked>li+li{margin-top:2px;margin-left:0}#bootstrap-theme .nav-justified, #bootstrap-theme .nav-tabs.nav-justified{width:100%}#bootstrap-theme .nav-justified>li, #bootstrap-theme .nav-tabs.nav-justified>li{float:none}#bootstrap-theme .nav-justified>li>a, #bootstrap-theme .nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}#bootstrap-theme .nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width: 768px){#bootstrap-theme .nav-justified>li, #bootstrap-theme .nav-tabs.nav-justified>li{display:table-cell;width:1%}#bootstrap-theme .nav-justified>li>a, #bootstrap-theme .nav-tabs.nav-justified>li>a{margin-bottom:0}}#bootstrap-theme .nav-tabs-justified, #bootstrap-theme .nav-tabs.nav-justified{border-bottom:0}#bootstrap-theme .nav-tabs-justified>li>a, #bootstrap-theme .nav-tabs.nav-justified>li>a{margin-right:0;border-radius:2px}#bootstrap-theme .nav-tabs-justified>.active>a, #bootstrap-theme .nav-tabs.nav-justified>.active>a, #bootstrap-theme .nav-tabs-justified>.active>a:hover, #bootstrap-theme .nav-tabs.nav-justified>.active>a:hover, #bootstrap-theme .nav-tabs-justified>.active>a:focus, #bootstrap-theme .nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){#bootstrap-theme .nav-tabs-justified>li>a, #bootstrap-theme .nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:2px 2px 0 0}#bootstrap-theme .nav-tabs-justified>.active>a, #bootstrap-theme .nav-tabs.nav-justified>.active>a, #bootstrap-theme .nav-tabs-justified>.active>a:hover, #bootstrap-theme .nav-tabs.nav-justified>.active>a:hover, #bootstrap-theme .nav-tabs-justified>.active>a:focus, #bootstrap-theme .nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#e8eef0}}#bootstrap-theme .tab-content>.tab-pane{display:none}#bootstrap-theme .tab-content>.active{display:block}#bootstrap-theme .nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}#bootstrap-theme .navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}#bootstrap-theme .navbar:before, #bootstrap-theme .navbar:after{display:table;content:" "}#bootstrap-theme .navbar:after{clear:both}@media (min-width: 768px){#bootstrap-theme .navbar{border-radius:2px}}#bootstrap-theme .navbar-header:before, #bootstrap-theme .navbar-header:after{display:table;content:" "}#bootstrap-theme .navbar-header:after{clear:both}@media (min-width: 768px){#bootstrap-theme .navbar-header{float:left}}#bootstrap-theme .navbar-collapse{padding-right:8px;padding-left:8px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}#bootstrap-theme .navbar-collapse:before, #bootstrap-theme .navbar-collapse:after{display:table;content:" "}#bootstrap-theme .navbar-collapse:after{clear:both}#bootstrap-theme .navbar-collapse.in{overflow-y:auto}@media (min-width: 768px){#bootstrap-theme .navbar-collapse{width:auto;border-top:0;box-shadow:none}#bootstrap-theme .navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}#bootstrap-theme .navbar-collapse.in{overflow-y:visible}#bootstrap-theme .navbar-fixed-top .navbar-collapse, #bootstrap-theme .navbar-static-top .navbar-collapse, #bootstrap-theme .navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}#bootstrap-theme .navbar-fixed-top, #bootstrap-theme .navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}#bootstrap-theme .navbar-fixed-top .navbar-collapse, #bootstrap-theme .navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width: 480px) and (orientation: landscape){#bootstrap-theme .navbar-fixed-top .navbar-collapse, #bootstrap-theme .navbar-fixed-bottom .navbar-collapse{max-height:200px}}@media (min-width: 768px){#bootstrap-theme .navbar-fixed-top, #bootstrap-theme .navbar-fixed-bottom{border-radius:0}}#bootstrap-theme .navbar-fixed-top{top:0;border-width:0 0 1px}#bootstrap-theme .navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}#bootstrap-theme .container>.navbar-header, #bootstrap-theme .container>.navbar-collapse, #bootstrap-theme .container-fluid>.navbar-header, #bootstrap-theme .container-fluid>.navbar-collapse{margin-right:-8px;margin-left:-8px}@media (min-width: 768px){#bootstrap-theme .container>.navbar-header, #bootstrap-theme .container>.navbar-collapse, #bootstrap-theme .container-fluid>.navbar-header, #bootstrap-theme .container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}#bootstrap-theme .navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width: 768px){#bootstrap-theme .navbar-static-top{border-radius:0}}#bootstrap-theme .navbar-brand{float:left;height:50px;padding:15px 8px;font-size:17px;line-height:20px}#bootstrap-theme .navbar-brand:hover, #bootstrap-theme .navbar-brand:focus{text-decoration:none}#bootstrap-theme .navbar-brand>img{display:block}@media (min-width: 768px){#bootstrap-theme .navbar>.container .navbar-brand, #bootstrap-theme .navbar>.container-fluid .navbar-brand{margin-left:-8px}}#bootstrap-theme .navbar-toggle{position:relative;float:right;padding:9px 10px;margin-right:8px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:2px}#bootstrap-theme .navbar-toggle:focus{outline:0}#bootstrap-theme .navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}#bootstrap-theme .navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width: 768px){#bootstrap-theme .navbar-toggle{display:none}}#bootstrap-theme .navbar-nav{margin:7.5px -8px}#bootstrap-theme .navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width: 767px){#bootstrap-theme .navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}#bootstrap-theme .navbar-nav .open .dropdown-menu>li>a, #bootstrap-theme .navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}#bootstrap-theme .navbar-nav .open .dropdown-menu>li>a{line-height:20px}#bootstrap-theme .navbar-nav .open .dropdown-menu>li>a:hover, #bootstrap-theme .navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width: 768px){#bootstrap-theme .navbar-nav{float:left;margin:0}#bootstrap-theme .navbar-nav>li{float:left}#bootstrap-theme .navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}#bootstrap-theme .navbar-form{padding:10px 8px;margin-right:-8px;margin-left:-8px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:10px;margin-bottom:10px}@media (min-width: 768px){#bootstrap-theme .navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}#bootstrap-theme .navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}#bootstrap-theme .navbar-form .form-control-static{display:inline-block}#bootstrap-theme .navbar-form .input-group{display:inline-table;vertical-align:middle}#bootstrap-theme .navbar-form .input-group .input-group-addon, #bootstrap-theme .navbar-form .input-group .input-group-btn, #bootstrap-theme .navbar-form .input-group .form-control{width:auto}#bootstrap-theme .navbar-form .input-group>.form-control{width:100%}#bootstrap-theme .navbar-form .control-label{margin-bottom:0;vertical-align:middle}#bootstrap-theme .navbar-form .radio, #bootstrap-theme .navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}#bootstrap-theme .navbar-form .radio label, #bootstrap-theme .navbar-form .checkbox label{padding-left:0}#bootstrap-theme .navbar-form .radio input[type="radio"], #bootstrap-theme .navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}#bootstrap-theme .navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width: 767px){#bootstrap-theme .navbar-form .form-group{margin-bottom:5px}#bootstrap-theme .navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width: 768px){#bootstrap-theme .navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}#bootstrap-theme .navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}#bootstrap-theme .navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-right-radius:0;border-bottom-left-radius:0}#bootstrap-theme .navbar-btn{margin-top:10px;margin-bottom:10px}#bootstrap-theme .navbar-btn.btn-sm, #bootstrap-theme .btn-group-sm>.navbar-btn.btn{margin-top:13px;margin-bottom:13px}#bootstrap-theme .navbar-btn.btn-xs, #bootstrap-theme .btn-group-xs>.navbar-btn.btn{margin-top:14px;margin-bottom:14px}#bootstrap-theme .navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width: 768px){#bootstrap-theme .navbar-text{float:left;margin-right:8px;margin-left:8px}}@media (min-width: 768px){#bootstrap-theme .navbar-left{float:left !important}#bootstrap-theme .navbar-right{float:right !important;margin-right:-8px}#bootstrap-theme .navbar-right ~ .navbar-right{margin-right:0}}#bootstrap-theme .navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}#bootstrap-theme .navbar-default .navbar-brand{color:#777}#bootstrap-theme .navbar-default .navbar-brand:hover, #bootstrap-theme .navbar-default .navbar-brand:focus{color:#5e5d5d;background-color:rgba(0,0,0,0)}#bootstrap-theme .navbar-default .navbar-text{color:#777}#bootstrap-theme .navbar-default .navbar-nav>li>a{color:#777}#bootstrap-theme .navbar-default .navbar-nav>li>a:hover, #bootstrap-theme .navbar-default .navbar-nav>li>a:focus{color:#333;background-color:rgba(0,0,0,0)}#bootstrap-theme .navbar-default .navbar-nav>.active>a, #bootstrap-theme .navbar-default .navbar-nav>.active>a:hover, #bootstrap-theme .navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}#bootstrap-theme .navbar-default .navbar-nav>.disabled>a, #bootstrap-theme .navbar-default .navbar-nav>.disabled>a:hover, #bootstrap-theme .navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:rgba(0,0,0,0)}#bootstrap-theme .navbar-default .navbar-nav>.open>a, #bootstrap-theme .navbar-default .navbar-nav>.open>a:hover, #bootstrap-theme .navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media (max-width: 767px){#bootstrap-theme .navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}#bootstrap-theme .navbar-default .navbar-nav .open .dropdown-menu>li>a:hover, #bootstrap-theme .navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:rgba(0,0,0,0)}#bootstrap-theme .navbar-default .navbar-nav .open .dropdown-menu>.active>a, #bootstrap-theme .navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover, #bootstrap-theme .navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}#bootstrap-theme .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a, #bootstrap-theme .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover, #bootstrap-theme .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:rgba(0,0,0,0)}}#bootstrap-theme .navbar-default .navbar-toggle{border-color:#ddd}#bootstrap-theme .navbar-default .navbar-toggle:hover, #bootstrap-theme .navbar-default .navbar-toggle:focus{background-color:#ddd}#bootstrap-theme .navbar-default .navbar-toggle .icon-bar{background-color:#888}#bootstrap-theme .navbar-default .navbar-collapse, #bootstrap-theme .navbar-default .navbar-form{border-color:#e7e7e7}#bootstrap-theme .navbar-default .navbar-link{color:#777}#bootstrap-theme .navbar-default .navbar-link:hover{color:#333}#bootstrap-theme .navbar-default .btn-link{color:#777}#bootstrap-theme .navbar-default .btn-link:hover, #bootstrap-theme .navbar-default .btn-link:focus{color:#333}#bootstrap-theme .navbar-default .btn-link[disabled]:hover, #bootstrap-theme .navbar-default .btn-link[disabled]:focus, #bootstrap-theme fieldset[disabled] .navbar-default .btn-link:hover, #bootstrap-theme fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}#bootstrap-theme .navbar-inverse{background-color:#222;border-color:#090808}#bootstrap-theme .navbar-inverse .navbar-brand{color:#fff}#bootstrap-theme .navbar-inverse .navbar-brand:hover, #bootstrap-theme .navbar-inverse .navbar-brand:focus{color:#fff;background-color:rgba(0,0,0,0)}#bootstrap-theme .navbar-inverse .navbar-text{color:#fff}#bootstrap-theme .navbar-inverse .navbar-nav>li>a{color:#fff}#bootstrap-theme .navbar-inverse .navbar-nav>li>a:hover, #bootstrap-theme .navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:rgba(0,0,0,0)}#bootstrap-theme .navbar-inverse .navbar-nav>.active>a, #bootstrap-theme .navbar-inverse .navbar-nav>.active>a:hover, #bootstrap-theme .navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#090808}#bootstrap-theme .navbar-inverse .navbar-nav>.disabled>a, #bootstrap-theme .navbar-inverse .navbar-nav>.disabled>a:hover, #bootstrap-theme .navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:rgba(0,0,0,0)}#bootstrap-theme .navbar-inverse .navbar-nav>.open>a, #bootstrap-theme .navbar-inverse .navbar-nav>.open>a:hover, #bootstrap-theme .navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#090808}@media (max-width: 767px){#bootstrap-theme .navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#090808}#bootstrap-theme .navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#090808}#bootstrap-theme .navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#fff}#bootstrap-theme .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover, #bootstrap-theme .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:rgba(0,0,0,0)}#bootstrap-theme .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a, #bootstrap-theme .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover, #bootstrap-theme .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#090808}#bootstrap-theme .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a, #bootstrap-theme .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover, #bootstrap-theme .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:rgba(0,0,0,0)}}#bootstrap-theme .navbar-inverse .navbar-toggle{border-color:#333}#bootstrap-theme .navbar-inverse .navbar-toggle:hover, #bootstrap-theme .navbar-inverse .navbar-toggle:focus{background-color:#333}#bootstrap-theme .navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}#bootstrap-theme .navbar-inverse .navbar-collapse, #bootstrap-theme .navbar-inverse .navbar-form{border-color:#101010}#bootstrap-theme .navbar-inverse .navbar-link{color:#fff}#bootstrap-theme .navbar-inverse .navbar-link:hover{color:#fff}#bootstrap-theme .navbar-inverse .btn-link{color:#fff}#bootstrap-theme .navbar-inverse .btn-link:hover, #bootstrap-theme .navbar-inverse .btn-link:focus{color:#fff}#bootstrap-theme .navbar-inverse .btn-link[disabled]:hover, #bootstrap-theme .navbar-inverse .btn-link[disabled]:focus, #bootstrap-theme fieldset[disabled] .navbar-inverse .btn-link:hover, #bootstrap-theme fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}#bootstrap-theme .breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:2px}#bootstrap-theme .breadcrumb>li{display:inline-block}#bootstrap-theme .breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"> "}#bootstrap-theme .breadcrumb>.active{color:#e8eef0}#bootstrap-theme .pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:2px}#bootstrap-theme .pagination>li{display:inline}#bootstrap-theme .pagination>li>a, #bootstrap-theme .pagination>li>span{position:relative;float:left;padding:4px 10px;margin-left:-1px;line-height:1.5384615385;color:#4d4d69;text-decoration:none;background-color:rgba(0,0,0,0);border:1px solid #ddd}#bootstrap-theme .pagination>li>a:hover, #bootstrap-theme .pagination>li>a:focus, #bootstrap-theme .pagination>li>span:hover, #bootstrap-theme .pagination>li>span:focus{z-index:2;color:#464354;background-color:rgba(0,0,0,0);border-color:#ddd}#bootstrap-theme .pagination>li:first-child>a, #bootstrap-theme .pagination>li:first-child>span{margin-left:0;border-top-left-radius:2px;border-bottom-left-radius:2px}#bootstrap-theme .pagination>li:last-child>a, #bootstrap-theme .pagination>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}#bootstrap-theme .pagination>.active>a, #bootstrap-theme .pagination>.active>a:hover, #bootstrap-theme .pagination>.active>a:focus, #bootstrap-theme .pagination>.active>span, #bootstrap-theme .pagination>.active>span:hover, #bootstrap-theme .pagination>.active>span:focus{z-index:3;color:#464354;cursor:default;background-color:rgba(0,0,0,0);border-color:#0071bd}#bootstrap-theme .pagination>.disabled>span, #bootstrap-theme .pagination>.disabled>span:hover, #bootstrap-theme .pagination>.disabled>span:focus, #bootstrap-theme .pagination>.disabled>a, #bootstrap-theme .pagination>.disabled>a:hover, #bootstrap-theme .pagination>.disabled>a:focus{color:#e8eef0;cursor:not-allowed;background-color:rgba(0,0,0,0);border-color:#ddd}#bootstrap-theme .pagination-lg>li>a, #bootstrap-theme .pagination-lg>li>span{padding:10px 16px;font-size:17px;line-height:1.3333333}#bootstrap-theme .pagination-lg>li:first-child>a, #bootstrap-theme .pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}#bootstrap-theme .pagination-lg>li:last-child>a, #bootstrap-theme .pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}#bootstrap-theme .pagination-sm>li>a, #bootstrap-theme .pagination-sm>li>span{padding:2px 10px;font-size:12px;line-height:1.5}#bootstrap-theme .pagination-sm>li:first-child>a, #bootstrap-theme .pagination-sm>li:first-child>span{border-top-left-radius:2px;border-bottom-left-radius:2px}#bootstrap-theme .pagination-sm>li:last-child>a, #bootstrap-theme .pagination-sm>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}#bootstrap-theme .pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}#bootstrap-theme .pager:before, #bootstrap-theme .pager:after{display:table;content:" "}#bootstrap-theme .pager:after{clear:both}#bootstrap-theme .pager li{display:inline}#bootstrap-theme .pager li>a, #bootstrap-theme .pager li>span{display:inline-block;padding:5px 14px;background-color:rgba(0,0,0,0);border:1px solid #ddd;border-radius:15px}#bootstrap-theme .pager li>a:hover, #bootstrap-theme .pager li>a:focus{text-decoration:none;background-color:rgba(0,0,0,0)}#bootstrap-theme .pager .next>a, #bootstrap-theme .pager .next>span{float:right}#bootstrap-theme .pager .previous>a, #bootstrap-theme .pager .previous>span{float:left}#bootstrap-theme .pager .disabled>a, #bootstrap-theme .pager .disabled>a:hover, #bootstrap-theme .pager .disabled>a:focus, #bootstrap-theme .pager .disabled>span{color:#e8eef0;cursor:not-allowed;background-color:rgba(0,0,0,0)}#bootstrap-theme .label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}#bootstrap-theme .label:empty{display:none}#bootstrap-theme .btn .label{position:relative;top:-1px}#bootstrap-theme a.label:hover, #bootstrap-theme a.label:focus{color:#fff;text-decoration:none;cursor:pointer}#bootstrap-theme .label-default{background-color:#c2cfd8}#bootstrap-theme .label-default[href]:hover, #bootstrap-theme .label-default[href]:focus{background-color:#a3b7c4}#bootstrap-theme .label-primary{background-color:#0071bd}#bootstrap-theme .label-primary[href]:hover, #bootstrap-theme .label-primary[href]:focus{background-color:#00538a}#bootstrap-theme .label-success{background-color:#44cb7e}#bootstrap-theme .label-success[href]:hover, #bootstrap-theme .label-success[href]:focus{background-color:#30ac65}#bootstrap-theme .label-info{background-color:#0071bd}#bootstrap-theme .label-info[href]:hover, #bootstrap-theme .label-info[href]:focus{background-color:#00538a}#bootstrap-theme .label-warning{background-color:#e6ab5e}#bootstrap-theme .label-warning[href]:hover, #bootstrap-theme .label-warning[href]:focus{background-color:#df9432}#bootstrap-theme .label-danger{background-color:#cf3458}#bootstrap-theme .label-danger[href]:hover, #bootstrap-theme .label-danger[href]:focus{background-color:#a82846}#bootstrap-theme .badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:400;line-height:1.1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#0071bd;border-radius:10px}#bootstrap-theme .badge:empty{display:none}#bootstrap-theme .btn .badge{position:relative;top:-1px}#bootstrap-theme .btn-xs .badge, #bootstrap-theme .btn-group-xs>.btn .badge, #bootstrap-theme .btn-group-xs>.btn .badge{top:0;padding:1px 5px}#bootstrap-theme .list-group-item.active>.badge, #bootstrap-theme .nav-pills>.active>a>.badge{color:#0071bd;background-color:#fff}#bootstrap-theme .list-group-item>.badge{float:right}#bootstrap-theme .list-group-item>.badge+.badge{margin-right:5px}#bootstrap-theme .nav-pills>li>a>.badge{margin-left:3px}#bootstrap-theme a.badge:hover, #bootstrap-theme a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}#bootstrap-theme .jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#f3f6f7}#bootstrap-theme .jumbotron h1, #bootstrap-theme .jumbotron .h1{color:inherit}#bootstrap-theme .jumbotron p{margin-bottom:15px;font-size:20px;font-weight:200}#bootstrap-theme .jumbotron>hr{border-top-color:#d4dfe3}#bootstrap-theme .container .jumbotron, #bootstrap-theme .container-fluid .jumbotron{padding-right:8px;padding-left:8px;border-radius:6px}#bootstrap-theme .jumbotron .container{max-width:100%}@media screen and (min-width: 768px){#bootstrap-theme .jumbotron{padding-top:48px;padding-bottom:48px}#bootstrap-theme .container .jumbotron, #bootstrap-theme .container-fluid .jumbotron{padding-right:60px;padding-left:60px}#bootstrap-theme .jumbotron h1, #bootstrap-theme .jumbotron .h1{font-size:59px}}#bootstrap-theme .thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.5384615385;background-color:#e8eef0;border:1px solid #ddd;border-radius:2px;-webkit-transition:border 0.2s ease-in-out;-o-transition:border 0.2s ease-in-out;transition:border 0.2s ease-in-out}#bootstrap-theme .thumbnail>img, #bootstrap-theme .thumbnail a>img{display:block;max-width:100%;height:auto;margin-right:auto;margin-left:auto}#bootstrap-theme .thumbnail .caption{padding:9px;color:#4d4d69}#bootstrap-theme a.thumbnail:hover, #bootstrap-theme a.thumbnail:focus, #bootstrap-theme a.thumbnail.active{border-color:#0071bd}#bootstrap-theme .alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:2px}#bootstrap-theme .alert h4{margin-top:0;color:inherit}#bootstrap-theme .alert .alert-link{font-weight:bold}#bootstrap-theme .alert>p, #bootstrap-theme .alert>ul{margin-bottom:0}#bootstrap-theme .alert>p+p{margin-top:5px}#bootstrap-theme .alert-dismissable, #bootstrap-theme .alert-dismissible{padding-right:35px}#bootstrap-theme .alert-dismissable .close, #bootstrap-theme .alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}#bootstrap-theme .alert-success{color:#464354;background-color:#bcecd1;border-color:#44cb7e}#bootstrap-theme .alert-success hr{border-top-color:#35c071}#bootstrap-theme .alert-success .alert-link{color:#2e2c38}#bootstrap-theme .alert-info{color:#464354;background-color:#57bbff;border-color:#0071bd}#bootstrap-theme .alert-info hr{border-top-color:#0062a4}#bootstrap-theme .alert-info .alert-link{color:#2e2c38}#bootstrap-theme .alert-warning{color:#464354;background-color:#fbf0e2;border-color:#e6ab5e}#bootstrap-theme .alert-warning hr{border-top-color:#e39f48}#bootstrap-theme .alert-warning .alert-link{color:#2e2c38}#bootstrap-theme .alert-danger{color:#464354;background-color:#ecb0be;border-color:#cf3458}#bootstrap-theme .alert-danger hr{border-top-color:#bd2d4e}#bootstrap-theme .alert-danger .alert-link{color:#2e2c38}@-webkit-keyframes progress-bar-stripes{#bootstrap-theme from{background-position:40px 0}#bootstrap-theme to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}#bootstrap-theme .progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:2px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}#bootstrap-theme .progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#0071bd;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}#bootstrap-theme .progress-striped .progress-bar, #bootstrap-theme .progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}#bootstrap-theme .progress.active .progress-bar, #bootstrap-theme .progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}#bootstrap-theme .progress-bar-success{background-color:#44cb7e}#bootstrap-theme .progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}#bootstrap-theme .progress-bar-info{background-color:#0071bd}#bootstrap-theme .progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}#bootstrap-theme .progress-bar-warning{background-color:#e6ab5e}#bootstrap-theme .progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}#bootstrap-theme .progress-bar-danger{background-color:#cf3458}#bootstrap-theme .progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}#bootstrap-theme .media{margin-top:15px}#bootstrap-theme .media:first-child{margin-top:0}#bootstrap-theme .media, #bootstrap-theme .media-body{overflow:hidden;zoom:1}#bootstrap-theme .media-body{width:10000px}#bootstrap-theme .media-object{display:block}#bootstrap-theme .media-object.img-thumbnail{max-width:none}#bootstrap-theme .media-right, #bootstrap-theme .media>.pull-right{padding-left:10px}#bootstrap-theme .media-left, #bootstrap-theme .media>.pull-left{padding-right:10px}#bootstrap-theme .media-left, #bootstrap-theme .media-right, #bootstrap-theme .media-body{display:table-cell;vertical-align:top}#bootstrap-theme .media-middle{vertical-align:middle}#bootstrap-theme .media-bottom{vertical-align:bottom}#bootstrap-theme .media-heading{margin-top:0;margin-bottom:5px}#bootstrap-theme .media-list{padding-left:0;list-style:none}#bootstrap-theme .list-group{padding-left:0;margin-bottom:20px}#bootstrap-theme .list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}#bootstrap-theme .list-group-item:first-child{border-top-left-radius:2px;border-top-right-radius:2px}#bootstrap-theme .list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:2px;border-bottom-left-radius:2px}#bootstrap-theme .list-group-item.disabled, #bootstrap-theme .list-group-item.disabled:hover, #bootstrap-theme .list-group-item.disabled:focus{color:#e8eef0;cursor:not-allowed;background-color:#f3f6f7}#bootstrap-theme .list-group-item.disabled .list-group-item-heading, #bootstrap-theme .list-group-item.disabled:hover .list-group-item-heading, #bootstrap-theme .list-group-item.disabled:focus .list-group-item-heading{color:inherit}#bootstrap-theme .list-group-item.disabled .list-group-item-text, #bootstrap-theme .list-group-item.disabled:hover .list-group-item-text, #bootstrap-theme .list-group-item.disabled:focus .list-group-item-text{color:#e8eef0}#bootstrap-theme .list-group-item.active, #bootstrap-theme .list-group-item.active:hover, #bootstrap-theme .list-group-item.active:focus{z-index:2;color:#fff;background-color:#0071bd;border-color:#0071bd}#bootstrap-theme .list-group-item.active .list-group-item-heading, #bootstrap-theme .list-group-item.active .list-group-item-heading>small, #bootstrap-theme .list-group-item.active .list-group-item-heading>.small, #bootstrap-theme .list-group-item.active:hover .list-group-item-heading, #bootstrap-theme .list-group-item.active:hover .list-group-item-heading>small, #bootstrap-theme .list-group-item.active:hover .list-group-item-heading>.small, #bootstrap-theme .list-group-item.active:focus .list-group-item-heading, #bootstrap-theme .list-group-item.active:focus .list-group-item-heading>small, #bootstrap-theme .list-group-item.active:focus .list-group-item-heading>.small{color:inherit}#bootstrap-theme .list-group-item.active .list-group-item-text, #bootstrap-theme .list-group-item.active:hover .list-group-item-text, #bootstrap-theme .list-group-item.active:focus .list-group-item-text{color:#8ad0ff}#bootstrap-theme a.list-group-item, #bootstrap-theme button.list-group-item{color:#555}#bootstrap-theme a.list-group-item .list-group-item-heading, #bootstrap-theme button.list-group-item .list-group-item-heading{color:#333}#bootstrap-theme a.list-group-item:hover, #bootstrap-theme a.list-group-item:focus, #bootstrap-theme button.list-group-item:hover, #bootstrap-theme button.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}#bootstrap-theme button.list-group-item{width:100%;text-align:left}#bootstrap-theme .list-group-item-success{color:#4d994d;background-color:#dff0d8}#bootstrap-theme a.list-group-item-success, #bootstrap-theme button.list-group-item-success{color:#4d994d}#bootstrap-theme a.list-group-item-success .list-group-item-heading, #bootstrap-theme button.list-group-item-success .list-group-item-heading{color:inherit}#bootstrap-theme a.list-group-item-success:hover, #bootstrap-theme a.list-group-item-success:focus, #bootstrap-theme button.list-group-item-success:hover, #bootstrap-theme button.list-group-item-success:focus{color:#4d994d;background-color:#d0e9c6}#bootstrap-theme a.list-group-item-success.active, #bootstrap-theme a.list-group-item-success.active:hover, #bootstrap-theme a.list-group-item-success.active:focus, #bootstrap-theme button.list-group-item-success.active, #bootstrap-theme button.list-group-item-success.active:hover, #bootstrap-theme button.list-group-item-success.active:focus{color:#fff;background-color:#4d994d;border-color:#4d994d}#bootstrap-theme .list-group-item-info{color:#0071bd;background-color:#d9edf7}#bootstrap-theme a.list-group-item-info, #bootstrap-theme button.list-group-item-info{color:#0071bd}#bootstrap-theme a.list-group-item-info .list-group-item-heading, #bootstrap-theme button.list-group-item-info .list-group-item-heading{color:inherit}#bootstrap-theme a.list-group-item-info:hover, #bootstrap-theme a.list-group-item-info:focus, #bootstrap-theme button.list-group-item-info:hover, #bootstrap-theme button.list-group-item-info:focus{color:#0071bd;background-color:#c4e3f3}#bootstrap-theme a.list-group-item-info.active, #bootstrap-theme a.list-group-item-info.active:hover, #bootstrap-theme a.list-group-item-info.active:focus, #bootstrap-theme button.list-group-item-info.active, #bootstrap-theme button.list-group-item-info.active:hover, #bootstrap-theme button.list-group-item-info.active:focus{color:#fff;background-color:#0071bd;border-color:#0071bd}#bootstrap-theme .list-group-item-warning{color:#bf5900;background-color:#fcf8e3}#bootstrap-theme a.list-group-item-warning, #bootstrap-theme button.list-group-item-warning{color:#bf5900}#bootstrap-theme a.list-group-item-warning .list-group-item-heading, #bootstrap-theme button.list-group-item-warning .list-group-item-heading{color:inherit}#bootstrap-theme a.list-group-item-warning:hover, #bootstrap-theme a.list-group-item-warning:focus, #bootstrap-theme button.list-group-item-warning:hover, #bootstrap-theme button.list-group-item-warning:focus{color:#bf5900;background-color:#faf2cc}#bootstrap-theme a.list-group-item-warning.active, #bootstrap-theme a.list-group-item-warning.active:hover, #bootstrap-theme a.list-group-item-warning.active:focus, #bootstrap-theme button.list-group-item-warning.active, #bootstrap-theme button.list-group-item-warning.active:hover, #bootstrap-theme button.list-group-item-warning.active:focus{color:#fff;background-color:#bf5900;border-color:#bf5900}#bootstrap-theme .list-group-item-danger{color:#cf3458;background-color:#f2dede}#bootstrap-theme a.list-group-item-danger, #bootstrap-theme button.list-group-item-danger{color:#cf3458}#bootstrap-theme a.list-group-item-danger .list-group-item-heading, #bootstrap-theme button.list-group-item-danger .list-group-item-heading{color:inherit}#bootstrap-theme a.list-group-item-danger:hover, #bootstrap-theme a.list-group-item-danger:focus, #bootstrap-theme button.list-group-item-danger:hover, #bootstrap-theme button.list-group-item-danger:focus{color:#cf3458;background-color:#ebcccc}#bootstrap-theme a.list-group-item-danger.active, #bootstrap-theme a.list-group-item-danger.active:hover, #bootstrap-theme a.list-group-item-danger.active:focus, #bootstrap-theme button.list-group-item-danger.active, #bootstrap-theme button.list-group-item-danger.active:hover, #bootstrap-theme button.list-group-item-danger.active:focus{color:#fff;background-color:#cf3458;border-color:#cf3458}#bootstrap-theme .list-group-item-heading{margin-top:0;margin-bottom:5px}#bootstrap-theme .list-group-item-text{margin-bottom:0;line-height:1.3}#bootstrap-theme .panel{margin-bottom:20px;background-color:rgba(0,0,0,0);border:1px solid transparent;border-radius:3px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}#bootstrap-theme .panel-body{padding:15px 20px}#bootstrap-theme .panel-body:before, #bootstrap-theme .panel-body:after{display:table;content:" "}#bootstrap-theme .panel-body:after{clear:both}#bootstrap-theme .panel-heading{padding:15px 20px;border-bottom:1px solid transparent;border-top-left-radius:2px;border-top-right-radius:2px}#bootstrap-theme .panel-heading>.dropdown .dropdown-toggle{color:inherit}#bootstrap-theme .panel-title{margin-top:0;margin-bottom:0;font-size:15px;color:inherit}#bootstrap-theme .panel-title>a, #bootstrap-theme .panel-title>small, #bootstrap-theme .panel-title>.small, #bootstrap-theme .panel-title>small>a, #bootstrap-theme .panel-title>.small>a{color:inherit}#bootstrap-theme .panel-footer{padding:20px;background-color:#fff;border-top:1px solid #e8eef0;border-bottom-right-radius:2px;border-bottom-left-radius:2px}#bootstrap-theme .panel>.list-group, #bootstrap-theme .panel>.panel-collapse>.list-group{margin-bottom:0}#bootstrap-theme .panel>.list-group .list-group-item, #bootstrap-theme .panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}#bootstrap-theme .panel>.list-group:first-child .list-group-item:first-child, #bootstrap-theme .panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:2px;border-top-right-radius:2px}#bootstrap-theme .panel>.list-group:last-child .list-group-item:last-child, #bootstrap-theme .panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:2px;border-bottom-left-radius:2px}#bootstrap-theme .panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}#bootstrap-theme .panel-heading+.list-group .list-group-item:first-child{border-top-width:0}#bootstrap-theme .list-group+.panel-footer{border-top-width:0}#bootstrap-theme .panel>.table, #bootstrap-theme .panel>.table-responsive>.table, #bootstrap-theme .panel>.panel-collapse>.table{margin-bottom:0}#bootstrap-theme .panel>.table caption, #bootstrap-theme .panel>.table-responsive>.table caption, #bootstrap-theme .panel>.panel-collapse>.table caption{padding-right:15px 20px;padding-left:15px 20px}#bootstrap-theme .panel>.table:first-child, #bootstrap-theme .panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:2px;border-top-right-radius:2px}#bootstrap-theme .panel>.table:first-child>thead:first-child>tr:first-child, #bootstrap-theme .panel>.table:first-child>tbody:first-child>tr:first-child, #bootstrap-theme .panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child, #bootstrap-theme .panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:2px;border-top-right-radius:2px}#bootstrap-theme .panel>.table:first-child>thead:first-child>tr:first-child td:first-child, #bootstrap-theme .panel>.table:first-child>thead:first-child>tr:first-child th:first-child, #bootstrap-theme .panel>.table:first-child>tbody:first-child>tr:first-child td:first-child, #bootstrap-theme .panel>.table:first-child>tbody:first-child>tr:first-child th:first-child, #bootstrap-theme .panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child, #bootstrap-theme .panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child, #bootstrap-theme .panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child, #bootstrap-theme .panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:2px}#bootstrap-theme .panel>.table:first-child>thead:first-child>tr:first-child td:last-child, #bootstrap-theme .panel>.table:first-child>thead:first-child>tr:first-child th:last-child, #bootstrap-theme .panel>.table:first-child>tbody:first-child>tr:first-child td:last-child, #bootstrap-theme .panel>.table:first-child>tbody:first-child>tr:first-child th:last-child, #bootstrap-theme .panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child, #bootstrap-theme .panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child, #bootstrap-theme .panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child, #bootstrap-theme .panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:2px}#bootstrap-theme .panel>.table:last-child, #bootstrap-theme .panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:2px;border-bottom-left-radius:2px}#bootstrap-theme .panel>.table:last-child>tbody:last-child>tr:last-child, #bootstrap-theme .panel>.table:last-child>tfoot:last-child>tr:last-child, #bootstrap-theme .panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child, #bootstrap-theme .panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:2px;border-bottom-left-radius:2px}#bootstrap-theme .panel>.table:last-child>tbody:last-child>tr:last-child td:first-child, #bootstrap-theme .panel>.table:last-child>tbody:last-child>tr:last-child th:first-child, #bootstrap-theme .panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child, #bootstrap-theme .panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child, #bootstrap-theme .panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child, #bootstrap-theme .panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child, #bootstrap-theme .panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child, #bootstrap-theme .panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:2px}#bootstrap-theme .panel>.table:last-child>tbody:last-child>tr:last-child td:last-child, #bootstrap-theme .panel>.table:last-child>tbody:last-child>tr:last-child th:last-child, #bootstrap-theme .panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child, #bootstrap-theme .panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child, #bootstrap-theme .panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child, #bootstrap-theme .panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child, #bootstrap-theme .panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child, #bootstrap-theme .panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:2px}#bootstrap-theme .panel>.panel-body+.table, #bootstrap-theme .panel>.panel-body+.table-responsive, #bootstrap-theme .panel>.table+.panel-body, #bootstrap-theme .panel>.table-responsive+.panel-body{border-top:1px solid #e8eef0}#bootstrap-theme .panel>.table>tbody:first-child>tr:first-child th, #bootstrap-theme .panel>.table>tbody:first-child>tr:first-child td{border-top:0}#bootstrap-theme .panel>.table-bordered, #bootstrap-theme .panel>.table-responsive>.table-bordered{border:0}#bootstrap-theme .panel>.table-bordered>thead>tr>th:first-child, #bootstrap-theme .panel>.table-bordered>thead>tr>td:first-child, #bootstrap-theme .panel>.table-bordered>tbody>tr>th:first-child, #bootstrap-theme .panel>.table-bordered>tbody>tr>td:first-child, #bootstrap-theme .panel>.table-bordered>tfoot>tr>th:first-child, #bootstrap-theme .panel>.table-bordered>tfoot>tr>td:first-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>thead>tr>th:first-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>thead>tr>td:first-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>tbody>tr>th:first-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>tbody>tr>td:first-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}#bootstrap-theme .panel>.table-bordered>thead>tr>th:last-child, #bootstrap-theme .panel>.table-bordered>thead>tr>td:last-child, #bootstrap-theme .panel>.table-bordered>tbody>tr>th:last-child, #bootstrap-theme .panel>.table-bordered>tbody>tr>td:last-child, #bootstrap-theme .panel>.table-bordered>tfoot>tr>th:last-child, #bootstrap-theme .panel>.table-bordered>tfoot>tr>td:last-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>thead>tr>th:last-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>thead>tr>td:last-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>tbody>tr>th:last-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>tbody>tr>td:last-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child, #bootstrap-theme .panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}#bootstrap-theme .panel>.table-bordered>thead>tr:first-child>td, #bootstrap-theme .panel>.table-bordered>thead>tr:first-child>th, #bootstrap-theme .panel>.table-bordered>tbody>tr:first-child>td, #bootstrap-theme .panel>.table-bordered>tbody>tr:first-child>th, #bootstrap-theme .panel>.table-responsive>.table-bordered>thead>tr:first-child>td, #bootstrap-theme .panel>.table-responsive>.table-bordered>thead>tr:first-child>th, #bootstrap-theme .panel>.table-responsive>.table-bordered>tbody>tr:first-child>td, #bootstrap-theme .panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}#bootstrap-theme .panel>.table-bordered>tbody>tr:last-child>td, #bootstrap-theme .panel>.table-bordered>tbody>tr:last-child>th, #bootstrap-theme .panel>.table-bordered>tfoot>tr:last-child>td, #bootstrap-theme .panel>.table-bordered>tfoot>tr:last-child>th, #bootstrap-theme .panel>.table-responsive>.table-bordered>tbody>tr:last-child>td, #bootstrap-theme .panel>.table-responsive>.table-bordered>tbody>tr:last-child>th, #bootstrap-theme .panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td, #bootstrap-theme .panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}#bootstrap-theme .panel>.table-responsive{margin-bottom:0;border:0}#bootstrap-theme .panel-group{margin-bottom:20px}#bootstrap-theme .panel-group .panel{margin-bottom:0;border-radius:3px}#bootstrap-theme .panel-group .panel+.panel{margin-top:5px}#bootstrap-theme .panel-group .panel-heading{border-bottom:0}#bootstrap-theme .panel-group .panel-heading+.panel-collapse>.panel-body, #bootstrap-theme .panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #e8eef0}#bootstrap-theme .panel-group .panel-footer{border-top:0}#bootstrap-theme .panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #e8eef0}#bootstrap-theme .panel-default{border-color:#ddd}#bootstrap-theme .panel-default>.panel-heading{color:#464354;background-color:#f3f6f7;border-color:#ddd}#bootstrap-theme .panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}#bootstrap-theme .panel-default>.panel-heading .badge{color:#f3f6f7;background-color:#464354}#bootstrap-theme .panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}#bootstrap-theme .panel-primary{border-color:#0071bd}#bootstrap-theme .panel-primary>.panel-heading{color:#fff;background-color:#0071bd;border-color:#0071bd}#bootstrap-theme .panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#0071bd}#bootstrap-theme .panel-primary>.panel-heading .badge{color:#0071bd;background-color:#fff}#bootstrap-theme .panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#0071bd}#bootstrap-theme .panel-success{border-color:#44cb7e}#bootstrap-theme .panel-success>.panel-heading{color:#464354;background-color:#44cb7e;border-color:#44cb7e}#bootstrap-theme .panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#44cb7e}#bootstrap-theme .panel-success>.panel-heading .badge{color:#44cb7e;background-color:#464354}#bootstrap-theme .panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#44cb7e}#bootstrap-theme .panel-info{border-color:#0071bd}#bootstrap-theme .panel-info>.panel-heading{color:#fff;background-color:#0071bd;border-color:#0071bd}#bootstrap-theme .panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#0071bd}#bootstrap-theme .panel-info>.panel-heading .badge{color:#0071bd;background-color:#fff}#bootstrap-theme .panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#0071bd}#bootstrap-theme .panel-warning{border-color:#e6ab5e}#bootstrap-theme .panel-warning>.panel-heading{color:#464354;background-color:#e6ab5e;border-color:#e6ab5e}#bootstrap-theme .panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#e6ab5e}#bootstrap-theme .panel-warning>.panel-heading .badge{color:#e6ab5e;background-color:#464354}#bootstrap-theme .panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#e6ab5e}#bootstrap-theme .panel-danger{border-color:#cf3458}#bootstrap-theme .panel-danger>.panel-heading{color:#fff;background-color:#cf3458;border-color:#cf3458}#bootstrap-theme .panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#cf3458}#bootstrap-theme .panel-danger>.panel-heading .badge{color:#cf3458;background-color:#fff}#bootstrap-theme .panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#cf3458}#bootstrap-theme .embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}#bootstrap-theme .embed-responsive .embed-responsive-item, #bootstrap-theme .embed-responsive iframe, #bootstrap-theme .embed-responsive embed, #bootstrap-theme .embed-responsive object, #bootstrap-theme .embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}#bootstrap-theme .embed-responsive-16by9{padding-bottom:56.25%}#bootstrap-theme .embed-responsive-4by3{padding-bottom:75%}#bootstrap-theme .well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:2px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}#bootstrap-theme .well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}#bootstrap-theme .well-lg{padding:24px;border-radius:6px}#bootstrap-theme .well-sm{padding:9px;border-radius:2px}#bootstrap-theme .close{float:right;font-size:19.5px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}#bootstrap-theme .close:hover, #bootstrap-theme .close:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}#bootstrap-theme button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;appearance:none}#bootstrap-theme .modal-open{overflow:hidden}#bootstrap-theme .modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}#bootstrap-theme .modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform 0.3s ease-out;-moz-transition:-moz-transform 0.3s ease-out;-o-transition:-o-transform 0.3s ease-out;transition:transform 0.3s ease-out}#bootstrap-theme .modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}#bootstrap-theme .modal-open .modal{overflow-x:hidden;overflow-y:auto}#bootstrap-theme .modal-dialog{position:relative;width:auto;margin:10px}#bootstrap-theme .modal-content{position:relative;background-color:#fff;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);outline:0}#bootstrap-theme .modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}#bootstrap-theme .modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}#bootstrap-theme .modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}#bootstrap-theme .modal-header{padding:10px 29px;border-bottom:1px solid #e5e5e5}#bootstrap-theme .modal-header:before, #bootstrap-theme .modal-header:after{display:table;content:" "}#bootstrap-theme .modal-header:after{clear:both}#bootstrap-theme .modal-header .close{margin-top:-2px}#bootstrap-theme .modal-title{margin:0;line-height:1.5384615385}#bootstrap-theme .modal-body{position:relative;padding:20px 30px}#bootstrap-theme .modal-footer{padding:20px 30px;text-align:right;border-top:1px solid #e8eef0}#bootstrap-theme .modal-footer:before, #bootstrap-theme .modal-footer:after{display:table;content:" "}#bootstrap-theme .modal-footer:after{clear:both}#bootstrap-theme .modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}#bootstrap-theme .modal-footer .btn-group .btn+.btn{margin-left:-1px}#bootstrap-theme .modal-footer .btn-block+.btn-block{margin-left:0}#bootstrap-theme .modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 768px){#bootstrap-theme .modal-dialog{width:555px;margin:30px auto}#bootstrap-theme .modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}#bootstrap-theme .modal-sm{width:360px}}@media (min-width: 992px){#bootstrap-theme .modal-lg{width:945px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.5384615385;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:2px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.5384615385;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:13px;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow, .popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:13px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}#bootstrap-theme .carousel{position:relative}#bootstrap-theme .carousel-inner{position:relative;width:100%;overflow:hidden}#bootstrap-theme .carousel-inner>.item{position:relative;display:none;-webkit-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left}#bootstrap-theme .carousel-inner>.item>img, #bootstrap-theme .carousel-inner>.item>a>img{display:block;max-width:100%;height:auto;line-height:1}@media all and (transform-3d), (-webkit-transform-3d){#bootstrap-theme .carousel-inner>.item{-webkit-transition:-webkit-transform 0.6s ease-in-out;-moz-transition:-moz-transform 0.6s ease-in-out;-o-transition:-o-transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;-moz-perspective:1000px;perspective:1000px}#bootstrap-theme .carousel-inner>.item.next, #bootstrap-theme .carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}#bootstrap-theme .carousel-inner>.item.prev, #bootstrap-theme .carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}#bootstrap-theme .carousel-inner>.item.next.left, #bootstrap-theme .carousel-inner>.item.prev.right, #bootstrap-theme .carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}#bootstrap-theme .carousel-inner>.active, #bootstrap-theme .carousel-inner>.next, #bootstrap-theme .carousel-inner>.prev{display:block}#bootstrap-theme .carousel-inner>.active{left:0}#bootstrap-theme .carousel-inner>.next, #bootstrap-theme .carousel-inner>.prev{position:absolute;top:0;width:100%}#bootstrap-theme .carousel-inner>.next{left:100%}#bootstrap-theme .carousel-inner>.prev{left:-100%}#bootstrap-theme .carousel-inner>.next.left, #bootstrap-theme .carousel-inner>.prev.right{left:0}#bootstrap-theme .carousel-inner>.active.left{left:-100%}#bootstrap-theme .carousel-inner>.active.right{left:100%}#bootstrap-theme .carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}#bootstrap-theme .carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}#bootstrap-theme .carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}#bootstrap-theme .carousel-control:hover, #bootstrap-theme .carousel-control:focus{color:#fff;text-decoration:none;outline:0;filter:alpha(opacity=90);opacity:.9}#bootstrap-theme .carousel-control .icon-prev, #bootstrap-theme .carousel-control .icon-next, #bootstrap-theme .carousel-control .glyphicon-chevron-left, #bootstrap-theme .carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}#bootstrap-theme .carousel-control .icon-prev, #bootstrap-theme .carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}#bootstrap-theme .carousel-control .icon-next, #bootstrap-theme .carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}#bootstrap-theme .carousel-control .icon-prev, #bootstrap-theme .carousel-control .icon-next{width:20px;height:20px;font-family:serif;line-height:1}#bootstrap-theme .carousel-control .icon-prev:before{content:"\2039"}#bootstrap-theme .carousel-control .icon-next:before{content:"\203a"}#bootstrap-theme .carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}#bootstrap-theme .carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}#bootstrap-theme .carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}#bootstrap-theme .carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}#bootstrap-theme .carousel-caption .btn{text-shadow:none}@media screen and (min-width: 768px){#bootstrap-theme .carousel-control .glyphicon-chevron-left, #bootstrap-theme .carousel-control .glyphicon-chevron-right, #bootstrap-theme .carousel-control .icon-prev, #bootstrap-theme .carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}#bootstrap-theme .carousel-control .glyphicon-chevron-left, #bootstrap-theme .carousel-control .icon-prev{margin-left:-10px}#bootstrap-theme .carousel-control .glyphicon-chevron-right, #bootstrap-theme .carousel-control .icon-next{margin-right:-10px}#bootstrap-theme .carousel-caption{right:20%;left:20%;padding-bottom:30px}#bootstrap-theme .carousel-indicators{bottom:20px}}#bootstrap-theme .clearfix:before, #bootstrap-theme .clearfix:after{display:table;content:" "}#bootstrap-theme .clearfix:after{clear:both}#bootstrap-theme .center-block{display:block;margin-right:auto;margin-left:auto}#bootstrap-theme .pull-right{float:right !important}#bootstrap-theme .pull-left{float:left !important}#bootstrap-theme .hide{display:none !important}#bootstrap-theme .show{display:block !important}#bootstrap-theme .invisible{visibility:hidden}#bootstrap-theme .text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}#bootstrap-theme .hidden{display:none !important}#bootstrap-theme .affix{position:fixed}@-ms-viewport{width:device-width}#bootstrap-theme .visible-xs{display:none !important}#bootstrap-theme .visible-sm{display:none !important}#bootstrap-theme .visible-md{display:none !important}#bootstrap-theme .visible-lg{display:none !important}#bootstrap-theme .visible-xs-block, #bootstrap-theme .visible-xs-inline, #bootstrap-theme .visible-xs-inline-block, #bootstrap-theme .visible-sm-block, #bootstrap-theme .visible-sm-inline, #bootstrap-theme .visible-sm-inline-block, #bootstrap-theme .visible-md-block, #bootstrap-theme .visible-md-inline, #bootstrap-theme .visible-md-inline-block, #bootstrap-theme .visible-lg-block, #bootstrap-theme .visible-lg-inline, #bootstrap-theme .visible-lg-inline-block{display:none !important}@media (max-width: 767px){#bootstrap-theme .visible-xs{display:block !important}#bootstrap-theme table.visible-xs{display:table !important}#bootstrap-theme tr.visible-xs{display:table-row !important}#bootstrap-theme th.visible-xs, #bootstrap-theme td.visible-xs{display:table-cell !important}}@media (max-width: 767px){#bootstrap-theme .visible-xs-block{display:block !important}}@media (max-width: 767px){#bootstrap-theme .visible-xs-inline{display:inline !important}}@media (max-width: 767px){#bootstrap-theme .visible-xs-inline-block{display:inline-block !important}}@media (min-width: 768px) and (max-width: 991px){#bootstrap-theme .visible-sm{display:block !important}#bootstrap-theme table.visible-sm{display:table !important}#bootstrap-theme tr.visible-sm{display:table-row !important}#bootstrap-theme th.visible-sm, #bootstrap-theme td.visible-sm{display:table-cell !important}}@media (min-width: 768px) and (max-width: 991px){#bootstrap-theme .visible-sm-block{display:block !important}}@media (min-width: 768px) and (max-width: 991px){#bootstrap-theme .visible-sm-inline{display:inline !important}}@media (min-width: 768px) and (max-width: 991px){#bootstrap-theme .visible-sm-inline-block{display:inline-block !important}}@media (min-width: 992px) and (max-width: 1199px){#bootstrap-theme .visible-md{display:block !important}#bootstrap-theme table.visible-md{display:table !important}#bootstrap-theme tr.visible-md{display:table-row !important}#bootstrap-theme th.visible-md, #bootstrap-theme td.visible-md{display:table-cell !important}}@media (min-width: 992px) and (max-width: 1199px){#bootstrap-theme .visible-md-block{display:block !important}}@media (min-width: 992px) and (max-width: 1199px){#bootstrap-theme .visible-md-inline{display:inline !important}}@media (min-width: 992px) and (max-width: 1199px){#bootstrap-theme .visible-md-inline-block{display:inline-block !important}}@media (min-width: 1200px){#bootstrap-theme .visible-lg{display:block !important}#bootstrap-theme table.visible-lg{display:table !important}#bootstrap-theme tr.visible-lg{display:table-row !important}#bootstrap-theme th.visible-lg, #bootstrap-theme td.visible-lg{display:table-cell !important}}@media (min-width: 1200px){#bootstrap-theme .visible-lg-block{display:block !important}}@media (min-width: 1200px){#bootstrap-theme .visible-lg-inline{display:inline !important}}@media (min-width: 1200px){#bootstrap-theme .visible-lg-inline-block{display:inline-block !important}}@media (max-width: 767px){#bootstrap-theme .hidden-xs{display:none !important}}@media (min-width: 768px) and (max-width: 991px){#bootstrap-theme .hidden-sm{display:none !important}}@media (min-width: 992px) and (max-width: 1199px){#bootstrap-theme .hidden-md{display:none !important}}@media (min-width: 1200px){#bootstrap-theme .hidden-lg{display:none !important}}#bootstrap-theme .visible-print{display:none !important}@media print{#bootstrap-theme .visible-print{display:block !important}#bootstrap-theme table.visible-print{display:table !important}#bootstrap-theme tr.visible-print{display:table-row !important}#bootstrap-theme th.visible-print, #bootstrap-theme td.visible-print{display:table-cell !important}}#bootstrap-theme .visible-print-block{display:none !important}@media print{#bootstrap-theme .visible-print-block{display:block !important}}#bootstrap-theme .visible-print-inline{display:none !important}@media print{#bootstrap-theme .visible-print-inline{display:inline !important}}#bootstrap-theme .visible-print-inline-block{display:none !important}@media print{#bootstrap-theme .visible-print-inline-block{display:inline-block !important}}@media print{#bootstrap-theme .hidden-print{display:none !important}}#bootstrap-theme .form-horizontal .form-group .editable-controls.form-group{margin-left:0;margin-right:0}#bootstrap-theme [text-angular]{border:1px solid #e8eef0;padding:0}#bootstrap-theme [text-angular] .ta-editor{border:0 !important}#bootstrap-theme [text-angular] .form-control{border:0;box-shadow:none !important;min-height:120px}#bootstrap-theme [text-angular] .form-control.ta-scroll-window{height:auto}#bootstrap-theme [text-angular] .form-control.ta-scroll-window>.ta-bind{min-height:120px;outline:0}#bootstrap-theme [text-angular-toolbar]{background-color:#f4f7f8;border:0;margin-left:0;padding:10px}#bootstrap-theme [text-angular-toolbar] .btn{background:transparent !important;border:0;box-shadow:none;color:#4d4d69;padding-left:10px;padding-right:10px}#bootstrap-theme [text-angular-toolbar] .btn.active{color:#464354 !important}#bootstrap-theme [text-angular-toolbar] .btn:hover:not(:disabled){color:#4d4d69}#bootstrap-theme [text-angular-toolbar] .btn .fa{font-size:12px;font-weight:700}#bootstrap-theme .civihr-ui-select+.input-group-btn .btn{border-top-left-radius:0;border-bottom-left-radius:0;margin-left:-1px}#bootstrap-theme .civihr-ui-select.ui-select-multiple{min-height:30px}#bootstrap-theme .civihr-ui-select.ui-select-multiple .ui-select-search{height:1.9em;text-indent:10px}#bootstrap-theme .civihr-ui-select.ui-select-multiple .ui-select-match .close{line-height:0.8}#bootstrap-theme .civihr-ui-select.ui-select-multiple .ui-select-match-item{border-color:#0071bd;margin:0 2px 2px 1px;padding:2px 8px;text-transform:none}#bootstrap-theme .civihr-ui-select .ui-select-match-text, #bootstrap-theme .civihr-ui-select .ui-select-placeholder{text-transform:none}#bootstrap-theme .civihr-ui-select .ui-select-match-close{color:#0071bd;cursor:pointer}#bootstrap-theme .civihr-ui-select .ui-select-match-close:hover{color:#4d4d69}#bootstrap-theme .civihr-ui-select .ui-select-toggle{border-color:#c2cfd8;padding:4px 30px 4px 12px}#bootstrap-theme .civihr-ui-select .ui-select-toggle:hover{border-color:#c2cfd8}#bootstrap-theme .civihr-ui-select .ui-select-toggle[disabled]{background-color:#f3f6f7;border-color:#c2cfd8;opacity:1}#bootstrap-theme .civihr-ui-select .ui-select-toggle[disabled]:hover, #bootstrap-theme .civihr-ui-select .ui-select-toggle[disabled]:focus, #bootstrap-theme .civihr-ui-select .ui-select-toggle[disabled]:active{background-color:#f3f6f7;border-color:#c2cfd8}#bootstrap-theme .ui-select-container:not(.civihr-ui-select):not(.ui-select-multiple) .btn.ui-select-toggle{border-color:#c2cfd8;height:30px;padding:4px 10px;text-transform:none}#bootstrap-theme .ui-select-container:not(.civihr-ui-select):not(.ui-select-multiple) .btn:hover{background:#fff}#bootstrap-theme .ui-select-container:not(.civihr-ui-select):not(.ui-select-multiple) .dropdown-menu{border:1px solid #c2cfd8;border-top:1px solid transparent}#bootstrap-theme .ui-select-container:not(.civihr-ui-select):not(.ui-select-multiple) .ui-select-match-text{color:#555;padding-right:3em}#bootstrap-theme .ui-select-container:not(.civihr-ui-select):not(.ui-select-multiple) .ui-select-match-text span{text-overflow:ellipsis}#bootstrap-theme .ui-select-container:not(.civihr-ui-select):not(.ui-select-multiple) .ui-select-match a{margin-right:14px !important}#bootstrap-theme .ui-select-container:not(.civihr-ui-select):not(.ui-select-multiple) .glyphicon-remove{font-size:10px}#bootstrap-theme .ui-select-container:not(.civihr-ui-select):not(.ui-select-multiple) .caret{display:none}#bootstrap-theme .ui-select-container:not(.civihr-ui-select):not(.ui-select-multiple) input{border:1px solid #c2cfd8 !important;box-shadow:none !important;height:30px}#bootstrap-theme .ui-select-container:not(.civihr-ui-select):not(.ui-select-multiple)::after{content:'\f002';bottom:0;font-family:"FontAwesome";display:inline-block;height:100%;line-height:30px;pointer-events:none;position:absolute;right:0;text-align:center;width:32px;z-index:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#bootstrap-theme .ui-select-container:not(.civihr-ui-select) .text-muted{color:#4d4d69}#bootstrap-theme .ui-select-multiple:not(.civihr-ui-select){border:1px solid #c2cfd8;min-height:30px;padding:3px !important}#bootstrap-theme .ui-select-multiple:not(.civihr-ui-select) .ui-select-match-item{border:1px solid #ccc;padding:1px 5px;text-transform:none}#bootstrap-theme .ui-select-multiple:not(.civihr-ui-select) .ui-select-search{padding-left:6px !important}#bootstrap-theme .ui-select-multiple:not(.civihr-ui-select)::after{content:'\f002';bottom:0;font-family:"FontAwesome";display:inline-block;height:100%;line-height:30px;pointer-events:none;position:absolute;right:0;text-align:center;width:32px;z-index:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#bootstrap-theme .ui-select-bootstrap .ui-select-choices-row:not(.civihr-ui-select)>span{color:#555;font-size:12px;padding:3px 10px}#bootstrap-theme .ui-select-bootstrap .ui-select-choices-row:not(.civihr-ui-select).active>span{background:#eeeded;color:#555}#bootstrap-theme [uib-datepicker-popup]{border-color:#c2cfd8}#bootstrap-theme [uib-datepicker-popup] .form-control{border-color:#c2cfd8}#bootstrap-theme [uib-datepicker-popup-wrap]+.input-group-btn>button{border-color:#c2cfd8;color:#c2cfd8}#bootstrap-theme .uib-left, #bootstrap-theme .uib-right, #bootstrap-theme .uib-title{border:0;color:#464354}#bootstrap-theme .uib-left:hover, #bootstrap-theme .uib-right:hover, #bootstrap-theme .uib-title:hover{background:transparent}#bootstrap-theme .uib-left i, #bootstrap-theme .uib-right i{font-weight:300}#bootstrap-theme .uib-title strong{font-size:12px;font-weight:700}#bootstrap-theme .uib-daypicker{outline:0}#bootstrap-theme .uib-daypicker th{background:#fff;color:#555;padding:7px;vertical-align:middle}#bootstrap-theme .uib-daypicker tbody{background:rgba(0,0,0,0);border-top:0;box-shadow:0}#bootstrap-theme .uib-day, #bootstrap-theme .uib-month, #bootstrap-theme .uib-year{padding:7px}#bootstrap-theme .uib-day .text-muted, #bootstrap-theme .uib-month .text-muted, #bootstrap-theme .uib-year .text-muted{color:#dcdddd}#bootstrap-theme .uib-day .text-info, #bootstrap-theme .uib-month .text-info, #bootstrap-theme .uib-year .text-info{background-color:rgba(0,0,0,0);border-radius:50%;color:#0071bd}#bootstrap-theme .uib-day .btn-default, #bootstrap-theme .uib-month .btn-default, #bootstrap-theme .uib-year .btn-default{background:transparent;border:0;color:#464354;line-height:1;min-width:0;padding:7px}#bootstrap-theme .uib-day .btn-default span, #bootstrap-theme .uib-month .btn-default span, #bootstrap-theme .uib-year .btn-default span{padding:0 !important}#bootstrap-theme .uib-day .btn-default.active, #bootstrap-theme .uib-month .btn-default.active, #bootstrap-theme .uib-year .btn-default.active{background-color:#0071bd;border-radius:50%;box-shadow:none;color:#fff}#bootstrap-theme .uib-day .btn-default.active .text-info, #bootstrap-theme .uib-month .btn-default.active .text-info, #bootstrap-theme .uib-year .btn-default.active .text-info{color:inherit}#bootstrap-theme .uib-datepicker-popup{border:0;padding:10px !important}#bootstrap-theme .mobile [type='date'][uib-datepicker-popup]{line-height:normal}#bootstrap-theme .mobile [type='date'][uib-datepicker-popup]::-webkit-inner-spin-button, #bootstrap-theme .mobile [type='date'][uib-datepicker-popup]::-webkit-clear-button{-webkit-appearance:none;appearance:none;display:none}#bootstrap-theme .mobile [type='date'][uib-datepicker-popup]::-webkit-calendar-picker-indicator{background:transparent;bottom:0;color:transparent;height:auto;left:0;position:absolute;right:-50px;top:0;width:auto}#bootstrap-theme .mobile [type='date'][uib-datepicker-popup]+.input-group-addon{border-left:1px solid #c2cfd8 !important;height:30px !important;line-height:30px !important;padding:0 !important;pointer-events:none;position:absolute !important;right:0 !important;width:38px !important;z-index:3}#bootstrap-theme .badge{min-width:25px}#bootstrap-theme .badge-danger{background:#cf3458}#bootstrap-theme .badge-secondary{background:#464354}#bootstrap-theme .badge-success{background:#44cb7e;color:#464354}#bootstrap-theme .badge-warning{background:#e6ab5e;color:#464354}#bootstrap-theme .btn-group .btn.active{background-color:#555}#bootstrap-theme .btn{border-color:transparent;padding:7px 19px;text-transform:uppercase}#bootstrap-theme .btn:focus, #bootstrap-theme .btn:hover{border-color:transparent}#bootstrap-theme .btn:active, #bootstrap-theme .btn.active, #bootstrap-theme .open>.btn.dropdown-toggle{border-color:transparent}#bootstrap-theme .btn:active:hover, #bootstrap-theme .btn:active:focus, #bootstrap-theme .btn:active.focus, #bootstrap-theme .btn.active:hover, #bootstrap-theme .btn.active:focus, #bootstrap-theme .btn.active.focus, #bootstrap-theme .open>.btn.dropdown-toggle:hover, #bootstrap-theme .open>.btn.dropdown-toggle:focus, #bootstrap-theme .open>.btn.dropdown-toggle.focus{border-color:transparent}#bootstrap-theme .btn:focus, #bootstrap-theme .btn:active, #bootstrap-theme .btn.active{-webkit-box-shadow:none;box-shadow:none;outline:none !important}#bootstrap-theme .btn-default:hover{background-color:#fff;color:#4d4d69}#bootstrap-theme .btn-lg, #bootstrap-theme .btn-group-lg>.btn{padding:10px 16px;font-size:13px;line-height:1.5384615385;border-radius:2px}#bootstrap-theme .btn-sm, #bootstrap-theme .btn-group-sm>.btn{padding:5px 10px}#bootstrap-theme .btn-xs, #bootstrap-theme .btn-group-xs>.btn{padding:0 5px}#bootstrap-theme .btn-block+.btn-block{margin-top:10px}#bootstrap-theme .btn-link{text-transform:none}#bootstrap-theme .btn-shadow{-webkit-box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);box-shadow:0 3px 18px 0 rgba(48,40,40,0.25)}#bootstrap-theme .btn-icon{margin-right:5px}#bootstrap-theme .btn-primary:hover{background-color:#005c99}#bootstrap-theme .btn-success{color:#464354}#bootstrap-theme .btn-success:hover{background-color:#33b86c;color:#000}#bootstrap-theme .btn-info:hover{background-color:#005c99}#bootstrap-theme .btn-warning{color:#464354}#bootstrap-theme .btn-warning:hover{background-color:#e19b3f;color:#000}#bootstrap-theme .btn-danger:hover{background-color:#b52b4b}#bootstrap-theme .btn .caret{margin:0 5px}#bootstrap-theme .btn-secondary{color:#fff;background-color:#4d4d69;border-color:#4d4d69;border-color:transparent}#bootstrap-theme .btn-secondary:focus, #bootstrap-theme .btn-secondary.focus{color:#fff;background-color:#37374c;border-color:#17171f}#bootstrap-theme .btn-secondary:hover{color:#fff;background-color:#37374c;border-color:#333346}#bootstrap-theme .btn-secondary:active, #bootstrap-theme .btn-secondary.active, #bootstrap-theme .open>.btn-secondary.dropdown-toggle{color:#fff;background-color:#37374c;background-image:none;border-color:#333346}#bootstrap-theme .btn-secondary:active:hover, #bootstrap-theme .btn-secondary:active:focus, #bootstrap-theme .btn-secondary:active.focus, #bootstrap-theme .btn-secondary.active:hover, #bootstrap-theme .btn-secondary.active:focus, #bootstrap-theme .btn-secondary.active.focus, #bootstrap-theme .open>.btn-secondary.dropdown-toggle:hover, #bootstrap-theme .open>.btn-secondary.dropdown-toggle:focus, #bootstrap-theme .open>.btn-secondary.dropdown-toggle.focus{color:#fff;background-color:#282837;border-color:#17171f}#bootstrap-theme .btn-secondary.disabled:hover, #bootstrap-theme .btn-secondary.disabled:focus, #bootstrap-theme .btn-secondary.disabled.focus, #bootstrap-theme .btn-secondary[disabled]:hover, #bootstrap-theme .btn-secondary[disabled]:focus, #bootstrap-theme .btn-secondary[disabled].focus, #bootstrap-theme fieldset[disabled] .btn-secondary:hover, #bootstrap-theme fieldset[disabled] .btn-secondary:focus, #bootstrap-theme fieldset[disabled] .btn-secondary.focus{background-color:#4d4d69;border-color:#4d4d69}#bootstrap-theme .btn-secondary .badge{color:#4d4d69;background-color:#fff}#bootstrap-theme .btn-secondary:focus, #bootstrap-theme .btn-secondary:hover{border-color:transparent}#bootstrap-theme .btn-secondary:active, #bootstrap-theme .btn-secondary.active, #bootstrap-theme .open>.btn-secondary.dropdown-toggle{border-color:transparent}#bootstrap-theme .btn-secondary:active:hover, #bootstrap-theme .btn-secondary:active:focus, #bootstrap-theme .btn-secondary:active.focus, #bootstrap-theme .btn-secondary.active:hover, #bootstrap-theme .btn-secondary.active:focus, #bootstrap-theme .btn-secondary.active.focus, #bootstrap-theme .open>.btn-secondary.dropdown-toggle:hover, #bootstrap-theme .open>.btn-secondary.dropdown-toggle:focus, #bootstrap-theme .open>.btn-secondary.dropdown-toggle.focus{border-color:transparent}#bootstrap-theme .btn-secondary:hover{background-color:#3e3e54}#bootstrap-theme .chart svg:not(:root){overflow:visible}#bootstrap-theme .chart-axis path, #bootstrap-theme .chart-axis line{fill:none;stroke:#f3f6f7}#bootstrap-theme .chart-axis-x>.tick, #bootstrap-theme .chart-axis-y>.tick{fill:#4d4d69}#bootstrap-theme .dropdown-header{padding:7px 19px 7px 24px}#bootstrap-theme .dropdown-menu{-webkit-box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);border:0;margin:0;padding:4px 0 9px 0}#bootstrap-theme .dropdown-menu>li{border:0;margin:0;padding:0}#bootstrap-theme .dropdown-menu>li>a{padding:7px 19px 7px 24px}#bootstrap-theme .dropdown-menu .divider{margin:7px 0}#bootstrap-theme label{color:#464354;font-weight:600}#bootstrap-theme .checkbox>label, #bootstrap-theme .radio>label{color:#4d4d69}#bootstrap-theme .form-control{-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2)}#bootstrap-theme .form-control[disabled], #bootstrap-theme [disabled] .form-control{color:#c2cfd8}#bootstrap-theme .form-control:focus{-webkit-box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2);box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2);border-color:#0071bd}#bootstrap-theme .form-control[readonly]{background-color:#fff;color:#9494a5}#bootstrap-theme .form-error-message{margin-top:5px}#bootstrap-theme .form-horizontal .has-feedback [uib-datepicker-popup]+.form-control-feedback{right:41px}@media (min-width: 768px){#bootstrap-theme .form-horizontal .control-label{text-align:left}#bootstrap-theme .form-horizontal .form-group{overflow:initial}#bootstrap-theme .form-horizontal .form-group [type='checkbox']{margin-top:8px}#bootstrap-theme .form-horizontal .form-group-lg .control-label, #bootstrap-theme .form-horizontal .form-group-sm .control-label{font-size:13px}}#bootstrap-theme .form-horizontal-inline .form-group{margin-bottom:0}@media (min-width: 768px){#bootstrap-theme .form-inline .checkbox [type='checkbox'], #bootstrap-theme .form-inline .radio [type='radio']{margin-right:10px}}#bootstrap-theme .form-inline-tabs .form-group{border:1px solid #c2cfd8;box-shadow:inset 0 0 3px 0 rgba(0,0,0,0.2);line-height:1em;min-width:180px;padding:8px 15px}#bootstrap-theme .form-inline-tabs .form-group.active{background-color:#f3f6f7}#bootstrap-theme .form-inline-tabs .form-group:first-child{border-radius:2px 0 0 2px}#bootstrap-theme .form-inline-tabs .form-group:last-child{border-radius:0 2px 2px 0}#bootstrap-theme .has-success .control-label, #bootstrap-theme .has-warning .control-label, #bootstrap-theme .has-error .control-label{color:#464354}#bootstrap-theme .has-success .help-block, #bootstrap-theme .has-success .control-label, #bootstrap-theme .has-success .radio, #bootstrap-theme .has-success .checkbox, #bootstrap-theme .has-success .radio-inline, #bootstrap-theme .has-success .checkbox-inline, #bootstrap-theme .has-success.radio label, #bootstrap-theme .has-success.checkbox label, #bootstrap-theme .has-success.radio-inline label, #bootstrap-theme .has-success.checkbox-inline label{color:#4d994d}#bootstrap-theme .has-success .form-control{border-color:#44cb7e;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}#bootstrap-theme .has-success .form-control:focus{border-color:#30ac65;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #94e1b5;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #94e1b5}#bootstrap-theme .has-success .input-group-addon{color:#4d994d;background-color:#dff0d8;border-color:#44cb7e}#bootstrap-theme .has-success .form-control-feedback{color:#4d994d}#bootstrap-theme .has-success .radio label, #bootstrap-theme .has-success .checkbox label, #bootstrap-theme .has-success .radio-inline label, #bootstrap-theme .has-success .checkbox-inline label{color:#4d994d}#bootstrap-theme .has-warning .help-block, #bootstrap-theme .has-warning .control-label, #bootstrap-theme .has-warning .radio, #bootstrap-theme .has-warning .checkbox, #bootstrap-theme .has-warning .radio-inline, #bootstrap-theme .has-warning .checkbox-inline, #bootstrap-theme .has-warning.radio label, #bootstrap-theme .has-warning.checkbox label, #bootstrap-theme .has-warning.radio-inline label, #bootstrap-theme .has-warning.checkbox-inline label{color:#bf5900}#bootstrap-theme .has-warning .form-control{border-color:#e6ab5e;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}#bootstrap-theme .has-warning .form-control:focus{border-color:#df9432;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #f4d9b6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #f4d9b6}#bootstrap-theme .has-warning .input-group-addon{color:#bf5900;background-color:#fcf8e3;border-color:#e6ab5e}#bootstrap-theme .has-warning .form-control-feedback{color:#bf5900}#bootstrap-theme .has-warning .radio label, #bootstrap-theme .has-warning .checkbox label, #bootstrap-theme .has-warning .radio-inline label, #bootstrap-theme .has-warning .checkbox-inline label{color:#bf5900}#bootstrap-theme .has-error .radio label, #bootstrap-theme .has-error .checkbox label, #bootstrap-theme .has-error .radio-inline label, #bootstrap-theme .has-error .checkbox-inline label{color:#cf3458}#bootstrap-theme .input-group-btn .btn-default{border-color:#c2cfd8;text-transform:none}#bootstrap-theme .form-submitted .ng-invalid, #bootstrap-theme .ng-submitted .ng-invalid{border:1px solid #cf3458}#bootstrap-theme .required-mark::after{color:#cf3458;content:'*';margin-left:5px}#bootstrap-theme .required-mark-input{position:relative}#bootstrap-theme .required-mark-input::after{bottom:0;position:absolute;right:-10px;top:0}#bootstrap-theme a.fa:hover{text-decoration:none}#bootstrap-theme .input-group-addon{color:#464354}#bootstrap-theme .label{font-size:92.308%;font-weight:400}#bootstrap-theme .label-success, #bootstrap-theme .label-warning{color:#464354}#bootstrap-theme .modal-content{-webkit-box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);border:0;border-radius:2px}#bootstrap-theme .modal-footer{padding:20px}#bootstrap-theme .modal-sm .modal-footer{padding:10px}#bootstrap-theme .modal-header{background:#f3f6f7;border-bottom:0;border-radius:2px}#bootstrap-theme .modal-sm .modal-header{padding:10px 20px}@media (min-width: 768px){#bootstrap-theme .modal-dialog{margin-top:50px}}#bootstrap-theme .modal-civihr-bootstrap{left:initial !important;position:initial !important;top:initial !important}#bootstrap-theme .modal-civihr-bootstrap .loading-spinner{left:initial !important;margin:0 auto -20px !important;position:initial !important;top:initial !important}#bootstrap-theme .nav>li>a{color:#464354;font-weight:600}#bootstrap-theme .nav>.disabled>a:hover{border-left-color:transparent;border-right-color:transparent;border-top-color:transparent}#bootstrap-theme .nav-stacked{background:#fff}#bootstrap-theme .nav-stacked>li>a{padding:15px 20px}#bootstrap-theme .nav-stacked>li>a:active{background:#e8eef0}#bootstrap-theme .nav-stacked>li+li{margin-top:0}#bootstrap-theme .tab-content{background:#fff}#bootstrap-theme .tab-pane{padding:15px 20px}#bootstrap-theme .nav-pills>li>a:focus{background-color:transparent}#bootstrap-theme .nav-pills-horizontal{-webkit-box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);padding-left:20px}#bootstrap-theme .nav-pills-horizontal>li{opacity:0.75}#bootstrap-theme .nav-pills-horizontal>li>a{padding:15px 20px}#bootstrap-theme .nav-pills-horizontal>li+li{margin-left:0}#bootstrap-theme .nav-pills-horizontal>.disabled{opacity:0.4}#bootstrap-theme .nav-pills-horizontal>.active{opacity:1}#bootstrap-theme .nav-pills-horizontal>.active>a{background:transparent !important;position:relative}#bootstrap-theme .nav-pills-horizontal>.active>a::after{border-bottom:3px solid transparent;bottom:0;content:'';left:0;position:absolute;width:100%}#bootstrap-theme .nav-pills-horizontal>.active>a:hover{background:transparent}#bootstrap-theme .nav-pills-horizontal-default{background:#fff}#bootstrap-theme .nav-pills-horizontal-default>li>a{color:#464354 !important}#bootstrap-theme .nav-pills-horizontal-default>li.active>a:after{border-color:#464354}#bootstrap-theme .nav-pills-horizontal-primary{background:#0071bd}#bootstrap-theme .nav-pills-horizontal-primary>li>a{color:#fff !important}#bootstrap-theme .nav-pills-horizontal-primary>li>a:hover{background:#0080d5}#bootstrap-theme .nav-pills-horizontal-primary>li.active>a:after{border-color:#fff}#bootstrap-theme .nav-pills-horizontal-primary>li.active>a:hover{background:#0071bd}#bootstrap-theme .nav-pills-horizontal-success{background:#44cb7e}#bootstrap-theme .nav-pills-horizontal-success>li>a{color:#464354 !important}#bootstrap-theme .nav-pills-horizontal-success>li>a:hover{background:#57d08b}#bootstrap-theme .nav-pills-horizontal-success>li.active>a:after{border-color:#464354}#bootstrap-theme .nav-pills-horizontal-success>li.active>a:hover{background:#44cb7e}#bootstrap-theme .nav-pills-horizontal-info{background:#0071bd}#bootstrap-theme .nav-pills-horizontal-info>li>a{color:#fff !important}#bootstrap-theme .nav-pills-horizontal-info>li>a:hover{background:#0080d5}#bootstrap-theme .nav-pills-horizontal-info>li.active>a:after{border-color:#fff}#bootstrap-theme .nav-pills-horizontal-info>li.active>a:hover{background:#0071bd}#bootstrap-theme .nav-pills-horizontal-warning{background:#e6ab5e}#bootstrap-theme .nav-pills-horizontal-warning>li>a{color:#464354 !important}#bootstrap-theme .nav-pills-horizontal-warning>li>a:hover{background:#e9b673}#bootstrap-theme .nav-pills-horizontal-warning>li.active>a:after{border-color:#464354}#bootstrap-theme .nav-pills-horizontal-warning>li.active>a:hover{background:#e6ab5e}#bootstrap-theme .nav-pills-horizontal-danger{background:#cf3458}#bootstrap-theme .nav-pills-horizontal-danger>li>a{color:#fff !important}#bootstrap-theme .nav-pills-horizontal-danger>li>a:hover{background:#d34868}#bootstrap-theme .nav-pills-horizontal-danger>li.active>a:after{border-color:#fff}#bootstrap-theme .nav-pills-horizontal-danger>li.active>a:hover{background:#cf3458}#bootstrap-theme .nav-pills-stacked-sidebar{background:transparent}#bootstrap-theme .nav-pills-stacked-sidebar>li{position:relative;z-index:2}#bootstrap-theme .nav-pills-stacked-sidebar>li>a{background:#fff}#bootstrap-theme .nav-pills-stacked-sidebar>.active{z-index:0}#bootstrap-theme .nav-pills-stacked-sidebar>.active::after, #bootstrap-theme .nav-pills-stacked-sidebar>.active::before{-webkit-box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);content:'';display:block;height:999999px;left:0;position:absolute;width:100%;z-index:1}#bootstrap-theme .nav-pills-stacked-sidebar>.active::after{top:100%}#bootstrap-theme .nav-pills-stacked-sidebar>.active::before{bottom:100%}#bootstrap-theme .nav-pills-stacked-sidebar>.active>a{background:transparent !important;color:#464354 !important}#bootstrap-theme .panel-primary>.panel-heading>.nav-tabs{border:0;margin:-10px 0}#bootstrap-theme .panel-primary li>a{background:rgba(0,0,0,0) !important;border-color:#fff !important;border-width:0;color:#fff !important;font-weight:600;padding:15px 15px 12px}#bootstrap-theme .panel-primary .active>a, #bootstrap-theme .panel-primary li:hover>a{border-width:0 0 3px 0}#bootstrap-theme .panel-default .nav-tabs:not(.nav-tabs-stacked){background:#f3f6f7;padding:5px 0 0 20px}#bootstrap-theme .panel-default .nav-tabs-stacked{border-bottom:0}#bootstrap-theme .panel-default .nav-tabs-stacked>li{float:none}#bootstrap-theme .panel-default .nav-tabs-stacked a{margin-right:0;padding:15px 20px}#bootstrap-theme .panel-default .nav-tabs-stacked a:hover{border-color:transparent #d3dee2 transparent transparent}#bootstrap-theme .panel-default .nav-tabs-stacked>.active>a, #bootstrap-theme .panel-default .nav-tabs-stacked>.active>a:hover, #bootstrap-theme .panel-default .nav-tabs-stacked>.active>a:focus{border-bottom-color:#d3dee2;border-color:#d3dee2 transparent #d3dee2 transparent}#bootstrap-theme .panel-default .nav-tabs-stacked-wrapper{border-top:1px solid #d3dee2;display:flex}#bootstrap-theme .panel-default .nav-tabs-stacked-wrapper .active:first-child>a{border-top-color:transparent !important}#bootstrap-theme .panel-default .nav-tabs-stacked-wrapper .nav-tabs-stacked{margin-right:-16px}#bootstrap-theme .panel-default .nav-tabs-stacked-wrapper .nav-tabs-stacked-content{display:flex}#bootstrap-theme .panel-default .nav-tabs-stacked-wrapper .tab-content{flex:1}#bootstrap-theme .panel-default .panel-heading+.nav-tabs{border-top:1px solid #e8eef0}#bootstrap-theme strong{font-weight:600}#bootstrap-theme .pagination{margin:0;vertical-align:top}#bootstrap-theme .pagination>li{text-transform:capitalize}#bootstrap-theme .pagination>li.active>a, #bootstrap-theme .pagination>li.first:not(.disabled)>a, #bootstrap-theme .pagination>li.last:not(.disabled)>a, #bootstrap-theme .pagination>li.next:not(.disabled)>a, #bootstrap-theme .pagination>li.prev:not(.disabled)>a{color:#464354;font-weight:bold}#bootstrap-theme .pagination>li>a, #bootstrap-theme .pagination>li>span{border:none;padding:0 5px}#bootstrap-theme .pagination>li>a:hover, #bootstrap-theme .pagination>li>span:hover{text-decoration:underline}#bootstrap-theme .pagination>li.first a:before{content:"«";font-weight:400;margin-right:3px}#bootstrap-theme .pagination>li.last a:after{content:"»";font-weight:400;margin-left:3px}#bootstrap-theme .pagination>li.next a:after{content:"›";font-weight:400;margin-left:3px}#bootstrap-theme .pagination>li.prev a:before{content:"‹";font-weight:400;margin-right:3px}#bootstrap-theme .panel{-webkit-box-shadow:none;box-shadow:none;border:none;margin-bottom:16px}#bootstrap-theme .panel-body{background:#fff}#bootstrap-theme .panel-body>*:last-child{margin-bottom:0}#bootstrap-theme .panel-body>*:last-child.form-horizontal>.row:last-child .form-group{margin-bottom:0}#bootstrap-theme .panel-body-small{padding:15px 20px}#bootstrap-theme .panel-heading{border:none}#bootstrap-theme .panel-title{font-size:18px;font-weight:600;line-height:1.1}#bootstrap-theme .panel-title-sm{font-size:16px;font-weight:600}#bootstrap-theme .panel-body-abstract{padding:8px 20px}#bootstrap-theme .panel-body-extra{padding:5px 20px 10px 35px}#bootstrap-theme .panel-subheading{padding:15px 20px}#bootstrap-theme .panel-heading+.panel-subheading{border-top:1px solid transparent}#bootstrap-theme .panel-subtitle{color:inherit;font-size:13px;font-weight:700;line-height:1.1;margin:0}#bootstrap-theme .panel-nest-group{margin-bottom:20px}#bootstrap-theme .panel-nest-group>.panel{margin-bottom:10px}#bootstrap-theme .panel-nest-group .panel-nested{margin:0 20px}#bootstrap-theme .panel-default{-webkit-box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);box-shadow:0 3px 18px 0 rgba(48,40,40,0.25)}#bootstrap-theme .panel-default .panel-body:not(.panel-body-extra){border-top:1px solid #d3dee2}#bootstrap-theme .panel-default>.panel-heading .btn-group-sm{margin:-13px 0 -10px 0}#bootstrap-theme .panel-default>.panel-heading .btn-group-sm>.btn{padding-left:20px;padding-right:20px}#bootstrap-theme .panel-default>.panel-subheading{background:#f3f6f7;border-top-color:#e8eef0;color:#464354}#bootstrap-theme .panel-default[class*="panel-strip-"]{border-left:6px solid transparent}#bootstrap-theme .panel-default.panel-strip-primary{border-left-color:#0071bd}#bootstrap-theme .panel-default.panel-strip-success{border-left-color:#44cb7e}#bootstrap-theme .panel-default.panel-strip-info{border-left-color:#0071bd}#bootstrap-theme .panel-default.panel-strip-warning{border-left-color:#e6ab5e}#bootstrap-theme .panel-default.panel-strip-danger{border-left-color:#cf3458}#bootstrap-theme .panel-primary>.panel-subheading{background:#0071bd;color:#fff;border-top-color:#0071bd;position:relative}#bootstrap-theme .panel-primary>.panel-subheading:before{content:"";background:#0071bd;top:-11px;height:10px;left:0;position:absolute;width:100%;z-index:2}#bootstrap-theme .panel-success>.panel-heading{color:#464354}#bootstrap-theme .panel-success>.panel-subheading{background:#44cb7e;color:#464354;border-top-color:#44cb7e;position:relative}#bootstrap-theme .panel-success>.panel-subheading:before{content:"";background:#44cb7e;top:-11px;height:10px;left:0;position:absolute;width:100%;z-index:2}#bootstrap-theme .panel-info>.panel-subheading{background:#0071bd;color:#fff;border-top-color:#0071bd;position:relative}#bootstrap-theme .panel-info>.panel-subheading:before{content:"";background:#0071bd;top:-11px;height:10px;left:0;position:absolute;width:100%;z-index:2}#bootstrap-theme .panel-warning>.panel-subheading{background:#e6ab5e;color:#464354;border-top-color:#e6ab5e;position:relative}#bootstrap-theme .panel-warning>.panel-subheading:before{content:"";background:#e6ab5e;top:-11px;height:10px;left:0;position:absolute;width:100%;z-index:2}#bootstrap-theme .panel-danger>.panel-subheading{background:#cf3458;color:#fff;border-top-color:#cf3458;position:relative}#bootstrap-theme .panel-danger>.panel-subheading:before{content:"";background:#cf3458;top:-11px;height:10px;left:0;position:absolute;width:100%;z-index:2}#bootstrap-theme .panel-primary>.panel-body, #bootstrap-theme .panel-primary>.panel-footer, #bootstrap-theme .panel-primary>.panel-heading, #bootstrap-theme .panel-primary>.panel-subheading, #bootstrap-theme .panel-success>.panel-body, #bootstrap-theme .panel-success>.panel-footer, #bootstrap-theme .panel-success>.panel-heading, #bootstrap-theme .panel-success>.panel-subheading, #bootstrap-theme .panel-info>.panel-body, #bootstrap-theme .panel-info>.panel-footer, #bootstrap-theme .panel-info>.panel-heading, #bootstrap-theme .panel-info>.panel-subheading, #bootstrap-theme .panel-warning>.panel-body, #bootstrap-theme .panel-warning>.panel-footer, #bootstrap-theme .panel-warning>.panel-heading, #bootstrap-theme .panel-warning>.panel-subheading, #bootstrap-theme .panel-danger>.panel-body, #bootstrap-theme .panel-danger>.panel-footer, #bootstrap-theme .panel-danger>.panel-heading, #bootstrap-theme .panel-danger>.panel-subheading{-webkit-box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);box-shadow:0 3px 18px 0 rgba(48,40,40,0.25)}#bootstrap-theme .panel-primary>.panel-body, #bootstrap-theme .panel-success>.panel-body, #bootstrap-theme .panel-info>.panel-body, #bootstrap-theme .panel-warning>.panel-body, #bootstrap-theme .panel-danger>.panel-body{position:relative}#bootstrap-theme .panel-primary>.panel-body:before, #bootstrap-theme .panel-success>.panel-body:before, #bootstrap-theme .panel-info>.panel-body:before, #bootstrap-theme .panel-warning>.panel-body:before, #bootstrap-theme .panel-danger>.panel-body:before{content:"";background:#fff;bottom:0;height:10px;left:0;position:absolute;width:100%;z-index:2}#bootstrap-theme .panel-primary>.panel-footer, #bootstrap-theme .panel-success>.panel-footer, #bootstrap-theme .panel-info>.panel-footer, #bootstrap-theme .panel-warning>.panel-footer, #bootstrap-theme .panel-danger>.panel-footer{position:relative}#bootstrap-theme .panel-primary:not(.panel-w-subheading)>.panel-heading, #bootstrap-theme .panel-primary.panel-w-subheading>.panel-subheading, #bootstrap-theme .panel-success:not(.panel-w-subheading)>.panel-heading, #bootstrap-theme .panel-success.panel-w-subheading>.panel-subheading, #bootstrap-theme .panel-info:not(.panel-w-subheading)>.panel-heading, #bootstrap-theme .panel-info.panel-w-subheading>.panel-subheading, #bootstrap-theme .panel-warning:not(.panel-w-subheading)>.panel-heading, #bootstrap-theme .panel-warning.panel-w-subheading>.panel-subheading, #bootstrap-theme .panel-danger:not(.panel-w-subheading)>.panel-heading, #bootstrap-theme .panel-danger.panel-w-subheading>.panel-subheading{border-bottom-right-radius:2px;border-bottom-left-radius:2px;margin-bottom:10px}#bootstrap-theme .panel-heading-w-buttons .btn{margin:-8px 0 -8px 10px}#bootstrap-theme [data-toggle="collapse"]:before{font-family:'FontAwesome';font-style:normal;text-rendering:auto;font-size:.8em;color:#4d4d69;color:inherit;content:"";font-size:inherit;margin-right:10px}#bootstrap-theme [data-toggle="collapse"].collapsed:before{content:""}#bootstrap-theme .progress-bar-success, #bootstrap-theme .progress-bar-warning{color:#464354}#bootstrap-theme table, #bootstrap-theme .table{margin-bottom:0}#bootstrap-theme table th, #bootstrap-theme .table th{background:#f3f6f7}#bootstrap-theme table>thead>tr>th, #bootstrap-theme .table>thead>tr>th{border-bottom:0;color:#464354;text-transform:capitalize}#bootstrap-theme table:not(.table-bordered)>tbody>tr:first-child>td, #bootstrap-theme .table:not(.table-bordered)>tbody>tr:first-child>td{border-color:#d3dee2}#bootstrap-theme .table-tab{margin:-15px -20px}#bootstrap-theme .table-tab>table>thead>tr>th{background:#fff}#bootstrap-theme .panel-default>.table, #bootstrap-theme .panel-default>.table-responsive>.table, #bootstrap-theme .panel-default>.panel-collapse>.table{border-top:1px solid #d3dee2}#bootstrap-theme .panel .table, #bootstrap-theme .panel .table-responsive{margin-bottom:0}#bootstrap-theme h1, #bootstrap-theme .h1{font-weight:600}#bootstrap-theme h2, #bootstrap-theme .h2{font-weight:600}#bootstrap-theme h3, #bootstrap-theme .h3{font-weight:600}#bootstrap-theme h4, #bootstrap-theme .h4{font-weight:500}#bootstrap-theme h5, #bootstrap-theme .h5{font-weight:700}#bootstrap-theme h6, #bootstrap-theme .h6{font-weight:500}#bootstrap-theme .text-default{color:#4d4d69}#bootstrap-theme a.text-default:hover, #bootstrap-theme a.text-default:focus{color:#37374c}#bootstrap-theme .text-default-darker{color:#464354}#bootstrap-theme a.text-default-darker:hover, #bootstrap-theme a.text-default-darker:focus{color:#2e2c38}#bootstrap-theme .page-header{-webkit-box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);background:#fff;border:0;padding:13px 20px;margin:0 0 15px 0}#bootstrap-theme .page-header>h1, #bootstrap-theme .page-header>h2, #bootstrap-theme .page-header>h3, #bootstrap-theme .page-header>h4, #bootstrap-theme .page-header>h5, #bootstrap-theme .page-header>h6{font-size:1.3846153846em;font-weight:600;line-height:1.34;margin:0}#bootstrap-theme .page-header-primary{background:#0071bd}#bootstrap-theme .page-header-primary>h1, #bootstrap-theme .page-header-primary>h2, #bootstrap-theme .page-header-primary>h3, #bootstrap-theme .page-header-primary>h4, #bootstrap-theme .page-header-primary>h5, #bootstrap-theme .page-header-primary>h6{color:#fff}#bootstrap-theme .page-header-primary>h1 small, #bootstrap-theme .page-header-primary>h2 small, #bootstrap-theme .page-header-primary>h3 small, #bootstrap-theme .page-header-primary>h4 small, #bootstrap-theme .page-header-primary>h5 small, #bootstrap-theme .page-header-primary>h6 small{color:#e8eef0}#bootstrap-theme .page-header-success{background:#44cb7e}#bootstrap-theme .page-header-success>h1, #bootstrap-theme .page-header-success>h2, #bootstrap-theme .page-header-success>h3, #bootstrap-theme .page-header-success>h4, #bootstrap-theme .page-header-success>h5, #bootstrap-theme .page-header-success>h6{color:#464354}#bootstrap-theme .page-header-success>h1 small, #bootstrap-theme .page-header-success>h2 small, #bootstrap-theme .page-header-success>h3 small, #bootstrap-theme .page-header-success>h4 small, #bootstrap-theme .page-header-success>h5 small, #bootstrap-theme .page-header-success>h6 small{color:#464354}#bootstrap-theme .page-header-info{background:#0071bd}#bootstrap-theme .page-header-info>h1, #bootstrap-theme .page-header-info>h2, #bootstrap-theme .page-header-info>h3, #bootstrap-theme .page-header-info>h4, #bootstrap-theme .page-header-info>h5, #bootstrap-theme .page-header-info>h6{color:#fff}#bootstrap-theme .page-header-info>h1 small, #bootstrap-theme .page-header-info>h2 small, #bootstrap-theme .page-header-info>h3 small, #bootstrap-theme .page-header-info>h4 small, #bootstrap-theme .page-header-info>h5 small, #bootstrap-theme .page-header-info>h6 small{color:#e8eef0}#bootstrap-theme .page-header-warning{background:#e6ab5e}#bootstrap-theme .page-header-warning>h1, #bootstrap-theme .page-header-warning>h2, #bootstrap-theme .page-header-warning>h3, #bootstrap-theme .page-header-warning>h4, #bootstrap-theme .page-header-warning>h5, #bootstrap-theme .page-header-warning>h6{color:#464354}#bootstrap-theme .page-header-warning>h1 small, #bootstrap-theme .page-header-warning>h2 small, #bootstrap-theme .page-header-warning>h3 small, #bootstrap-theme .page-header-warning>h4 small, #bootstrap-theme .page-header-warning>h5 small, #bootstrap-theme .page-header-warning>h6 small{color:#464354}#bootstrap-theme .page-header-danger{background:#cf3458}#bootstrap-theme .page-header-danger>h1, #bootstrap-theme .page-header-danger>h2, #bootstrap-theme .page-header-danger>h3, #bootstrap-theme .page-header-danger>h4, #bootstrap-theme .page-header-danger>h5, #bootstrap-theme .page-header-danger>h6{color:#fff}#bootstrap-theme .page-header-danger>h1 small, #bootstrap-theme .page-header-danger>h2 small, #bootstrap-theme .page-header-danger>h3 small, #bootstrap-theme .page-header-danger>h4 small, #bootstrap-theme .page-header-danger>h5 small, #bootstrap-theme .page-header-danger>h6 small{color:#e8eef0}#bootstrap-theme dl:last-child{margin-bottom:0}#bootstrap-theme dt{color:#464354;font-weight:600}#bootstrap-theme dt:not(:first-child){margin-top:1.7692307692em}@media (min-width: 768px){#bootstrap-theme .dl-horizontal dt{text-align:left}#bootstrap-theme .dl-horizontal dt:not(:first-child)+dd{margin-top:1.7692307692em}}@media (min-width: 768px){#bootstrap-theme .dl-horizontal-half dd{margin-left:125px}#bootstrap-theme .dl-horizontal-half dt{width:125px}}@media (min-width: 768px){#bootstrap-theme .dl-horizontal-full:before, #bootstrap-theme .dl-horizontal-full:after{display:table;content:" "}#bootstrap-theme .dl-horizontal-full:after{clear:both}#bootstrap-theme .dl-horizontal-full dt{width:auto}#bootstrap-theme .dl-horizontal-full dd{float:right;margin-left:0}}@media (min-width: 768px){#bootstrap-theme .dl-horizontal-inline{display:inline-block;vertical-align:top}#bootstrap-theme .dl-horizontal-inline:before, #bootstrap-theme .dl-horizontal-inline:after{display:table;content:" "}#bootstrap-theme .dl-horizontal-inline:after{clear:both}#bootstrap-theme .dl-horizontal-inline dt{width:auto}#bootstrap-theme .dl-horizontal-inline dd{float:left;margin-left:10px}}#bootstrap-theme .dl-inline>dd, #bootstrap-theme .dl-inline>dt{display:inline-block}#bootstrap-theme .dl-inline>dt{margin:0 5px 0 0}#bootstrap-theme .dl-inline>dt:not(:first-child){margin-left:15px}#bootstrap-theme .select2-container .select2-choices{margin-bottom:0;padding-left:0}#bootstrap-theme .select2-container .select2-choice>.select2-chosen{font-size:13px}#bootstrap-theme .select2-container.form-control.select2-dropdown-open{border-color:#0071bd}#bootstrap-theme .ui-dialog-content#bootstrap-theme{background:#fff;padding:10px 20px}#bootstrap-theme .btn-slide>ul.panel{display:none !important}#bootstrap-theme .crm-button-type-next, #bootstrap-theme .crm-button-type-upload{color:#fff;background-image:none;background-color:transparent;border-color:#0071bd}#bootstrap-theme .crm-button-type-next:focus, #bootstrap-theme .crm-button-type-next.focus, #bootstrap-theme .crm-button-type-next:active, #bootstrap-theme .crm-button-type-next.active, #bootstrap-theme .open>.crm-button-type-next.dropdown-toggle, #bootstrap-theme .crm-button-type-upload:focus, #bootstrap-theme .crm-button-type-upload.focus, #bootstrap-theme .crm-button-type-upload:active, #bootstrap-theme .crm-button-type-upload.active, #bootstrap-theme .open>.crm-button-type-upload.dropdown-toggle{color:#fff;background-color:#0071bd;border-color:#0071bd}#bootstrap-theme .crm-button-type-next:hover, #bootstrap-theme .crm-button-type-upload:hover{color:#fff;background-color:#005c99;border-color:#005c99}#bootstrap-theme .crm-button-type-next.disabled:focus, #bootstrap-theme .crm-button-type-next.disabled.focus, #bootstrap-theme .crm-button-type-next:disabled:focus, #bootstrap-theme .crm-button-type-next:disabled.focus, #bootstrap-theme fieldset[disabled] .crm-button-type-next:focus, #bootstrap-theme fieldset[disabled] .crm-button-type-next.focus, #bootstrap-theme .crm-button-type-upload.disabled:focus, #bootstrap-theme .crm-button-type-upload.disabled.focus, #bootstrap-theme .crm-button-type-upload:disabled:focus, #bootstrap-theme .crm-button-type-upload:disabled.focus, #bootstrap-theme fieldset[disabled] .crm-button-type-upload:focus, #bootstrap-theme fieldset[disabled] .crm-button-type-upload.focus{border-color:#fff}#bootstrap-theme .crm-button-type-next.disabled:hover, #bootstrap-theme .crm-button-type-next:disabled:hover, #bootstrap-theme fieldset[disabled] .crm-button-type-next:hover, #bootstrap-theme .crm-button-type-upload.disabled:hover, #bootstrap-theme .crm-button-type-upload:disabled:hover, #bootstrap-theme fieldset[disabled] .crm-button-type-upload:hover{border-color:#fff}#bootstrap-theme .crm-button-type-next .crm-i, #bootstrap-theme .crm-button-type-upload .crm-i{display:none}#bootstrap-theme .crm-button-type-back, #bootstrap-theme .crm-button-type-cancel, #bootstrap-theme .crm-button-type-delete{border-color:#e8eef0}#bootstrap-theme .crm-button-type-back input, #bootstrap-theme .crm-button-type-cancel input, #bootstrap-theme .crm-button-type-delete input{color:#464354 !important}#bootstrap-theme .crm-button-type-back input:hover, #bootstrap-theme .crm-button-type-cancel input:hover, #bootstrap-theme .crm-button-type-delete input:hover{color:#4d4d69 !important}#bootstrap-theme .crm-error{border-radius:2px;display:inline-block;font-size:12px;margin-top:5px}#bootstrap-theme input[type=file].error{padding-left:5px}#bootstrap-theme .crm-form-radio+label::before{min-width:16px;width:16px;height:16px}#bootstrap-theme .crm-form-radio+label::after{top:8px}#bootstrap-theme .crm-form-text{height:30px}#bootstrap-theme a.crm-i:hover, #bootstrap-theme a.helpicon:hover{text-decoration:none}#bootstrap-theme label .crm-marker-description{font-weight:normal}#bootstrap-theme .panel .panel-footer .button:last-child, #bootstrap-theme .panel .panel-footer .crm-button:last-child{margin-right:0}#bootstrap-theme table thead.sticky th>.sticky-header{background:#f3f6f7;border-bottom:1px solid #d3dee2;height:auto;margin-top:8px}#bootstrap-theme .crm-weight-arrow img.order-icon{height:18px;width:13px}#bootstrap-theme .crm-container.ui-dialog{z-index:2001}#bootstrap-theme .ui-widget-overlay.ui-front{z-index:2000}@media (max-width: 480px){#bootstrap-theme .modal-dialog:not(.modal-sm){border:none;border-radius:0;box-shadow:none;height:100%;margin:0;width:100%}#bootstrap-theme .modal-dialog:not(.modal-sm) .close{font-size:30px;pointer-events:auto}#bootstrap-theme .modal-dialog:not(.modal-sm) .modal-content{border:none;border-radius:0;height:100%;overflow-x:hidden;overflow-y:auto}#bootstrap-theme .modal-dialog:not(.modal-sm) .modal-header{pointer-events:none}#bootstrap-theme .modal-dialog:not(.modal-sm) .modal-body, #bootstrap-theme .modal-dialog:not(.modal-sm) .modal-header, #bootstrap-theme .modal-dialog:not(.modal-sm) .modal-footer{border-radius:0;padding:10px}}#bootstrap-theme .btn-default-outline{color:#fff;background-image:none;background-color:transparent;border-color:#fff}#bootstrap-theme .btn-default-outline:focus, #bootstrap-theme .btn-default-outline.focus, #bootstrap-theme .btn-default-outline:active, #bootstrap-theme .btn-default-outline.active, #bootstrap-theme .open>.btn-default-outline.dropdown-toggle{color:#fff;background-color:#fff;border-color:#fff}#bootstrap-theme .btn-default-outline:hover{color:#fff;background-color:#ededed;border-color:#ededed}#bootstrap-theme .btn-default-outline.disabled:focus, #bootstrap-theme .btn-default-outline.disabled.focus, #bootstrap-theme .btn-default-outline:disabled:focus, #bootstrap-theme .btn-default-outline:disabled.focus, #bootstrap-theme fieldset[disabled] .btn-default-outline:focus, #bootstrap-theme fieldset[disabled] .btn-default-outline.focus{border-color:#fff}#bootstrap-theme .btn-default-outline.disabled:hover, #bootstrap-theme .btn-default-outline:disabled:hover, #bootstrap-theme fieldset[disabled] .btn-default-outline:hover{border-color:#fff}#bootstrap-theme .open .btn-default-outline, #bootstrap-theme .btn-default-outline:hover, #bootstrap-theme .btn-default-outline:active{background-color:#fff !important;color:#4d4d69 !important}#bootstrap-theme .btn-primary-outline{color:#464354;background-image:none;background-color:transparent;border-color:#0071bd}#bootstrap-theme .btn-primary-outline:focus, #bootstrap-theme .btn-primary-outline.focus, #bootstrap-theme .btn-primary-outline:active, #bootstrap-theme .btn-primary-outline.active, #bootstrap-theme .open>.btn-primary-outline.dropdown-toggle{color:#fff;background-color:#0071bd;border-color:#0071bd}#bootstrap-theme .btn-primary-outline:hover{color:#fff;background-color:#005c99;border-color:#005c99}#bootstrap-theme .btn-primary-outline.disabled:focus, #bootstrap-theme .btn-primary-outline.disabled.focus, #bootstrap-theme .btn-primary-outline:disabled:focus, #bootstrap-theme .btn-primary-outline:disabled.focus, #bootstrap-theme fieldset[disabled] .btn-primary-outline:focus, #bootstrap-theme fieldset[disabled] .btn-primary-outline.focus{border-color:#75708d}#bootstrap-theme .btn-primary-outline.disabled:hover, #bootstrap-theme .btn-primary-outline:disabled:hover, #bootstrap-theme fieldset[disabled] .btn-primary-outline:hover{border-color:#75708d}#bootstrap-theme .btn-secondary-outline{color:#464354;background-image:none;background-color:transparent;border-color:#4d4d69;border-color:#464354 !important}#bootstrap-theme .btn-secondary-outline:focus, #bootstrap-theme .btn-secondary-outline.focus, #bootstrap-theme .btn-secondary-outline:active, #bootstrap-theme .btn-secondary-outline.active, #bootstrap-theme .open>.btn-secondary-outline.dropdown-toggle{color:#fff;background-color:#4d4d69;border-color:#4d4d69}#bootstrap-theme .btn-secondary-outline:hover{color:#fff;background-color:#3e3e54;border-color:#3e3e54}#bootstrap-theme .btn-secondary-outline.disabled:focus, #bootstrap-theme .btn-secondary-outline.disabled.focus, #bootstrap-theme .btn-secondary-outline:disabled:focus, #bootstrap-theme .btn-secondary-outline:disabled.focus, #bootstrap-theme fieldset[disabled] .btn-secondary-outline:focus, #bootstrap-theme fieldset[disabled] .btn-secondary-outline.focus{border-color:#75708d}#bootstrap-theme .btn-secondary-outline.disabled:hover, #bootstrap-theme .btn-secondary-outline:disabled:hover, #bootstrap-theme fieldset[disabled] .btn-secondary-outline:hover{border-color:#75708d}#bootstrap-theme .btn-secondary-outline:hover{color:#fff !important}#bootstrap-theme .btn-info-outline{color:#464354;background-image:none;background-color:transparent;border-color:#0071bd}#bootstrap-theme .btn-info-outline:focus, #bootstrap-theme .btn-info-outline.focus, #bootstrap-theme .btn-info-outline:active, #bootstrap-theme .btn-info-outline.active, #bootstrap-theme .open>.btn-info-outline.dropdown-toggle{color:#fff;background-color:#0071bd;border-color:#0071bd}#bootstrap-theme .btn-info-outline:hover{color:#fff;background-color:#005c99;border-color:#005c99}#bootstrap-theme .btn-info-outline.disabled:focus, #bootstrap-theme .btn-info-outline.disabled.focus, #bootstrap-theme .btn-info-outline:disabled:focus, #bootstrap-theme .btn-info-outline:disabled.focus, #bootstrap-theme fieldset[disabled] .btn-info-outline:focus, #bootstrap-theme fieldset[disabled] .btn-info-outline.focus{border-color:#75708d}#bootstrap-theme .btn-info-outline.disabled:hover, #bootstrap-theme .btn-info-outline:disabled:hover, #bootstrap-theme fieldset[disabled] .btn-info-outline:hover{border-color:#75708d}#bootstrap-theme .btn-success-outline{color:#464354;background-image:none;background-color:transparent;border-color:#44cb7e}#bootstrap-theme .btn-success-outline:focus, #bootstrap-theme .btn-success-outline.focus, #bootstrap-theme .btn-success-outline:active, #bootstrap-theme .btn-success-outline.active, #bootstrap-theme .open>.btn-success-outline.dropdown-toggle{color:#fff;background-color:#44cb7e;border-color:#44cb7e}#bootstrap-theme .btn-success-outline:hover{color:#000;background-color:#33b86c;border-color:#33b86c}#bootstrap-theme .btn-success-outline.disabled:focus, #bootstrap-theme .btn-success-outline.disabled.focus, #bootstrap-theme .btn-success-outline:disabled:focus, #bootstrap-theme .btn-success-outline:disabled.focus, #bootstrap-theme fieldset[disabled] .btn-success-outline:focus, #bootstrap-theme fieldset[disabled] .btn-success-outline.focus{border-color:#75708d}#bootstrap-theme .btn-success-outline.disabled:hover, #bootstrap-theme .btn-success-outline:disabled:hover, #bootstrap-theme fieldset[disabled] .btn-success-outline:hover{border-color:#75708d}#bootstrap-theme .btn-warning-outline{color:#464354;background-image:none;background-color:transparent;border-color:#e6ab5e}#bootstrap-theme .btn-warning-outline:focus, #bootstrap-theme .btn-warning-outline.focus, #bootstrap-theme .btn-warning-outline:active, #bootstrap-theme .btn-warning-outline.active, #bootstrap-theme .open>.btn-warning-outline.dropdown-toggle{color:#fff;background-color:#e6ab5e;border-color:#e6ab5e}#bootstrap-theme .btn-warning-outline:hover{color:#000;background-color:#e19b3f;border-color:#e19b3f}#bootstrap-theme .btn-warning-outline.disabled:focus, #bootstrap-theme .btn-warning-outline.disabled.focus, #bootstrap-theme .btn-warning-outline:disabled:focus, #bootstrap-theme .btn-warning-outline:disabled.focus, #bootstrap-theme fieldset[disabled] .btn-warning-outline:focus, #bootstrap-theme fieldset[disabled] .btn-warning-outline.focus{border-color:#75708d}#bootstrap-theme .btn-warning-outline.disabled:hover, #bootstrap-theme .btn-warning-outline:disabled:hover, #bootstrap-theme fieldset[disabled] .btn-warning-outline:hover{border-color:#75708d}#bootstrap-theme .btn-danger-outline{color:#464354;background-image:none;background-color:transparent;border-color:#cf3458}#bootstrap-theme .btn-danger-outline:focus, #bootstrap-theme .btn-danger-outline.focus, #bootstrap-theme .btn-danger-outline:active, #bootstrap-theme .btn-danger-outline.active, #bootstrap-theme .open>.btn-danger-outline.dropdown-toggle{color:#fff;background-color:#cf3458;border-color:#cf3458}#bootstrap-theme .btn-danger-outline:hover{color:#fff;background-color:#b52b4b;border-color:#b52b4b}#bootstrap-theme .btn-danger-outline.disabled:focus, #bootstrap-theme .btn-danger-outline.disabled.focus, #bootstrap-theme .btn-danger-outline:disabled:focus, #bootstrap-theme .btn-danger-outline:disabled.focus, #bootstrap-theme fieldset[disabled] .btn-danger-outline:focus, #bootstrap-theme fieldset[disabled] .btn-danger-outline.focus{border-color:#75708d}#bootstrap-theme .btn-danger-outline.disabled:hover, #bootstrap-theme .btn-danger-outline:disabled:hover, #bootstrap-theme fieldset[disabled] .btn-danger-outline:hover{border-color:#75708d}#bootstrap-theme .chart-color-0{background:#5A6779 !important;border-color:#5A6779 !important;fill:#5A6779 !important}#bootstrap-theme .chart-color-bg-0{background:#5A6779 !important;fill:#5A6779 !important}#bootstrap-theme .chart-color-border-0{border-color:#5A6779 !important}#bootstrap-theme .chart-color-stroke-0{stroke:#5A6779 !important}#bootstrap-theme .chart-color-28{background:#5A6779 !important;border-color:#5A6779 !important;fill:#5A6779 !important}#bootstrap-theme .chart-color-bg-28{background:#5A6779 !important;fill:#5A6779 !important}#bootstrap-theme .chart-color-border-28{border-color:#5A6779 !important}#bootstrap-theme .chart-color-stroke-28{stroke:#5A6779 !important}#bootstrap-theme .chart-color-56{background:#5A6779 !important;border-color:#5A6779 !important;fill:#5A6779 !important}#bootstrap-theme .chart-color-bg-56{background:#5A6779 !important;fill:#5A6779 !important}#bootstrap-theme .chart-color-border-56{border-color:#5A6779 !important}#bootstrap-theme .chart-color-stroke-56{stroke:#5A6779 !important}#bootstrap-theme .chart-color-84{background:#5A6779 !important;border-color:#5A6779 !important;fill:#5A6779 !important}#bootstrap-theme .chart-color-bg-84{background:#5A6779 !important;fill:#5A6779 !important}#bootstrap-theme .chart-color-border-84{border-color:#5A6779 !important}#bootstrap-theme .chart-color-stroke-84{stroke:#5A6779 !important}#bootstrap-theme .chart-color-112{background:#5A6779 !important;border-color:#5A6779 !important;fill:#5A6779 !important}#bootstrap-theme .chart-color-bg-112{background:#5A6779 !important;fill:#5A6779 !important}#bootstrap-theme .chart-color-border-112{border-color:#5A6779 !important}#bootstrap-theme .chart-color-stroke-112{stroke:#5A6779 !important}#bootstrap-theme .chart-color-1{background:#E5807F !important;border-color:#E5807F !important;fill:#E5807F !important}#bootstrap-theme .chart-color-bg-1{background:#E5807F !important;fill:#E5807F !important}#bootstrap-theme .chart-color-border-1{border-color:#E5807F !important}#bootstrap-theme .chart-color-stroke-1{stroke:#E5807F !important}#bootstrap-theme .chart-color-29{background:#E5807F !important;border-color:#E5807F !important;fill:#E5807F !important}#bootstrap-theme .chart-color-bg-29{background:#E5807F !important;fill:#E5807F !important}#bootstrap-theme .chart-color-border-29{border-color:#E5807F !important}#bootstrap-theme .chart-color-stroke-29{stroke:#E5807F !important}#bootstrap-theme .chart-color-57{background:#E5807F !important;border-color:#E5807F !important;fill:#E5807F !important}#bootstrap-theme .chart-color-bg-57{background:#E5807F !important;fill:#E5807F !important}#bootstrap-theme .chart-color-border-57{border-color:#E5807F !important}#bootstrap-theme .chart-color-stroke-57{stroke:#E5807F !important}#bootstrap-theme .chart-color-85{background:#E5807F !important;border-color:#E5807F !important;fill:#E5807F !important}#bootstrap-theme .chart-color-bg-85{background:#E5807F !important;fill:#E5807F !important}#bootstrap-theme .chart-color-border-85{border-color:#E5807F !important}#bootstrap-theme .chart-color-stroke-85{stroke:#E5807F !important}#bootstrap-theme .chart-color-113{background:#E5807F !important;border-color:#E5807F !important;fill:#E5807F !important}#bootstrap-theme .chart-color-bg-113{background:#E5807F !important;fill:#E5807F !important}#bootstrap-theme .chart-color-border-113{border-color:#E5807F !important}#bootstrap-theme .chart-color-stroke-113{stroke:#E5807F !important}#bootstrap-theme .chart-color-2{background:#ECA67F !important;border-color:#ECA67F !important;fill:#ECA67F !important}#bootstrap-theme .chart-color-bg-2{background:#ECA67F !important;fill:#ECA67F !important}#bootstrap-theme .chart-color-border-2{border-color:#ECA67F !important}#bootstrap-theme .chart-color-stroke-2{stroke:#ECA67F !important}#bootstrap-theme .chart-color-30{background:#ECA67F !important;border-color:#ECA67F !important;fill:#ECA67F !important}#bootstrap-theme .chart-color-bg-30{background:#ECA67F !important;fill:#ECA67F !important}#bootstrap-theme .chart-color-border-30{border-color:#ECA67F !important}#bootstrap-theme .chart-color-stroke-30{stroke:#ECA67F !important}#bootstrap-theme .chart-color-58{background:#ECA67F !important;border-color:#ECA67F !important;fill:#ECA67F !important}#bootstrap-theme .chart-color-bg-58{background:#ECA67F !important;fill:#ECA67F !important}#bootstrap-theme .chart-color-border-58{border-color:#ECA67F !important}#bootstrap-theme .chart-color-stroke-58{stroke:#ECA67F !important}#bootstrap-theme .chart-color-86{background:#ECA67F !important;border-color:#ECA67F !important;fill:#ECA67F !important}#bootstrap-theme .chart-color-bg-86{background:#ECA67F !important;fill:#ECA67F !important}#bootstrap-theme .chart-color-border-86{border-color:#ECA67F !important}#bootstrap-theme .chart-color-stroke-86{stroke:#ECA67F !important}#bootstrap-theme .chart-color-114{background:#ECA67F !important;border-color:#ECA67F !important;fill:#ECA67F !important}#bootstrap-theme .chart-color-bg-114{background:#ECA67F !important;fill:#ECA67F !important}#bootstrap-theme .chart-color-border-114{border-color:#ECA67F !important}#bootstrap-theme .chart-color-stroke-114{stroke:#ECA67F !important}#bootstrap-theme .chart-color-3{background:#8EC68A !important;border-color:#8EC68A !important;fill:#8EC68A !important}#bootstrap-theme .chart-color-bg-3{background:#8EC68A !important;fill:#8EC68A !important}#bootstrap-theme .chart-color-border-3{border-color:#8EC68A !important}#bootstrap-theme .chart-color-stroke-3{stroke:#8EC68A !important}#bootstrap-theme .chart-color-31{background:#8EC68A !important;border-color:#8EC68A !important;fill:#8EC68A !important}#bootstrap-theme .chart-color-bg-31{background:#8EC68A !important;fill:#8EC68A !important}#bootstrap-theme .chart-color-border-31{border-color:#8EC68A !important}#bootstrap-theme .chart-color-stroke-31{stroke:#8EC68A !important}#bootstrap-theme .chart-color-59{background:#8EC68A !important;border-color:#8EC68A !important;fill:#8EC68A !important}#bootstrap-theme .chart-color-bg-59{background:#8EC68A !important;fill:#8EC68A !important}#bootstrap-theme .chart-color-border-59{border-color:#8EC68A !important}#bootstrap-theme .chart-color-stroke-59{stroke:#8EC68A !important}#bootstrap-theme .chart-color-87{background:#8EC68A !important;border-color:#8EC68A !important;fill:#8EC68A !important}#bootstrap-theme .chart-color-bg-87{background:#8EC68A !important;fill:#8EC68A !important}#bootstrap-theme .chart-color-border-87{border-color:#8EC68A !important}#bootstrap-theme .chart-color-stroke-87{stroke:#8EC68A !important}#bootstrap-theme .chart-color-115{background:#8EC68A !important;border-color:#8EC68A !important;fill:#8EC68A !important}#bootstrap-theme .chart-color-bg-115{background:#8EC68A !important;fill:#8EC68A !important}#bootstrap-theme .chart-color-border-115{border-color:#8EC68A !important}#bootstrap-theme .chart-color-stroke-115{stroke:#8EC68A !important}#bootstrap-theme .chart-color-4{background:#C096AA !important;border-color:#C096AA !important;fill:#C096AA !important}#bootstrap-theme .chart-color-bg-4{background:#C096AA !important;fill:#C096AA !important}#bootstrap-theme .chart-color-border-4{border-color:#C096AA !important}#bootstrap-theme .chart-color-stroke-4{stroke:#C096AA !important}#bootstrap-theme .chart-color-32{background:#C096AA !important;border-color:#C096AA !important;fill:#C096AA !important}#bootstrap-theme .chart-color-bg-32{background:#C096AA !important;fill:#C096AA !important}#bootstrap-theme .chart-color-border-32{border-color:#C096AA !important}#bootstrap-theme .chart-color-stroke-32{stroke:#C096AA !important}#bootstrap-theme .chart-color-60{background:#C096AA !important;border-color:#C096AA !important;fill:#C096AA !important}#bootstrap-theme .chart-color-bg-60{background:#C096AA !important;fill:#C096AA !important}#bootstrap-theme .chart-color-border-60{border-color:#C096AA !important}#bootstrap-theme .chart-color-stroke-60{stroke:#C096AA !important}#bootstrap-theme .chart-color-88{background:#C096AA !important;border-color:#C096AA !important;fill:#C096AA !important}#bootstrap-theme .chart-color-bg-88{background:#C096AA !important;fill:#C096AA !important}#bootstrap-theme .chart-color-border-88{border-color:#C096AA !important}#bootstrap-theme .chart-color-stroke-88{stroke:#C096AA !important}#bootstrap-theme .chart-color-116{background:#C096AA !important;border-color:#C096AA !important;fill:#C096AA !important}#bootstrap-theme .chart-color-bg-116{background:#C096AA !important;fill:#C096AA !important}#bootstrap-theme .chart-color-border-116{border-color:#C096AA !important}#bootstrap-theme .chart-color-stroke-116{stroke:#C096AA !important}#bootstrap-theme .chart-color-5{background:#9579A8 !important;border-color:#9579A8 !important;fill:#9579A8 !important}#bootstrap-theme .chart-color-bg-5{background:#9579A8 !important;fill:#9579A8 !important}#bootstrap-theme .chart-color-border-5{border-color:#9579A8 !important}#bootstrap-theme .chart-color-stroke-5{stroke:#9579A8 !important}#bootstrap-theme .chart-color-33{background:#9579A8 !important;border-color:#9579A8 !important;fill:#9579A8 !important}#bootstrap-theme .chart-color-bg-33{background:#9579A8 !important;fill:#9579A8 !important}#bootstrap-theme .chart-color-border-33{border-color:#9579A8 !important}#bootstrap-theme .chart-color-stroke-33{stroke:#9579A8 !important}#bootstrap-theme .chart-color-61{background:#9579A8 !important;border-color:#9579A8 !important;fill:#9579A8 !important}#bootstrap-theme .chart-color-bg-61{background:#9579A8 !important;fill:#9579A8 !important}#bootstrap-theme .chart-color-border-61{border-color:#9579A8 !important}#bootstrap-theme .chart-color-stroke-61{stroke:#9579A8 !important}#bootstrap-theme .chart-color-89{background:#9579A8 !important;border-color:#9579A8 !important;fill:#9579A8 !important}#bootstrap-theme .chart-color-bg-89{background:#9579A8 !important;fill:#9579A8 !important}#bootstrap-theme .chart-color-border-89{border-color:#9579A8 !important}#bootstrap-theme .chart-color-stroke-89{stroke:#9579A8 !important}#bootstrap-theme .chart-color-117{background:#9579A8 !important;border-color:#9579A8 !important;fill:#9579A8 !important}#bootstrap-theme .chart-color-bg-117{background:#9579A8 !important;fill:#9579A8 !important}#bootstrap-theme .chart-color-border-117{border-color:#9579A8 !important}#bootstrap-theme .chart-color-stroke-117{stroke:#9579A8 !important}#bootstrap-theme .chart-color-6{background:#42B0CB !important;border-color:#42B0CB !important;fill:#42B0CB !important}#bootstrap-theme .chart-color-bg-6{background:#42B0CB !important;fill:#42B0CB !important}#bootstrap-theme .chart-color-border-6{border-color:#42B0CB !important}#bootstrap-theme .chart-color-stroke-6{stroke:#42B0CB !important}#bootstrap-theme .chart-color-34{background:#42B0CB !important;border-color:#42B0CB !important;fill:#42B0CB !important}#bootstrap-theme .chart-color-bg-34{background:#42B0CB !important;fill:#42B0CB !important}#bootstrap-theme .chart-color-border-34{border-color:#42B0CB !important}#bootstrap-theme .chart-color-stroke-34{stroke:#42B0CB !important}#bootstrap-theme .chart-color-62{background:#42B0CB !important;border-color:#42B0CB !important;fill:#42B0CB !important}#bootstrap-theme .chart-color-bg-62{background:#42B0CB !important;fill:#42B0CB !important}#bootstrap-theme .chart-color-border-62{border-color:#42B0CB !important}#bootstrap-theme .chart-color-stroke-62{stroke:#42B0CB !important}#bootstrap-theme .chart-color-90{background:#42B0CB !important;border-color:#42B0CB !important;fill:#42B0CB !important}#bootstrap-theme .chart-color-bg-90{background:#42B0CB !important;fill:#42B0CB !important}#bootstrap-theme .chart-color-border-90{border-color:#42B0CB !important}#bootstrap-theme .chart-color-stroke-90{stroke:#42B0CB !important}#bootstrap-theme .chart-color-118{background:#42B0CB !important;border-color:#42B0CB !important;fill:#42B0CB !important}#bootstrap-theme .chart-color-bg-118{background:#42B0CB !important;fill:#42B0CB !important}#bootstrap-theme .chart-color-border-118{border-color:#42B0CB !important}#bootstrap-theme .chart-color-stroke-118{stroke:#42B0CB !important}#bootstrap-theme .chart-color-7{background:#3D4A5E !important;border-color:#3D4A5E !important;fill:#3D4A5E !important}#bootstrap-theme .chart-color-bg-7{background:#3D4A5E !important;fill:#3D4A5E !important}#bootstrap-theme .chart-color-border-7{border-color:#3D4A5E !important}#bootstrap-theme .chart-color-stroke-7{stroke:#3D4A5E !important}#bootstrap-theme .chart-color-35{background:#3D4A5E !important;border-color:#3D4A5E !important;fill:#3D4A5E !important}#bootstrap-theme .chart-color-bg-35{background:#3D4A5E !important;fill:#3D4A5E !important}#bootstrap-theme .chart-color-border-35{border-color:#3D4A5E !important}#bootstrap-theme .chart-color-stroke-35{stroke:#3D4A5E !important}#bootstrap-theme .chart-color-63{background:#3D4A5E !important;border-color:#3D4A5E !important;fill:#3D4A5E !important}#bootstrap-theme .chart-color-bg-63{background:#3D4A5E !important;fill:#3D4A5E !important}#bootstrap-theme .chart-color-border-63{border-color:#3D4A5E !important}#bootstrap-theme .chart-color-stroke-63{stroke:#3D4A5E !important}#bootstrap-theme .chart-color-91{background:#3D4A5E !important;border-color:#3D4A5E !important;fill:#3D4A5E !important}#bootstrap-theme .chart-color-bg-91{background:#3D4A5E !important;fill:#3D4A5E !important}#bootstrap-theme .chart-color-border-91{border-color:#3D4A5E !important}#bootstrap-theme .chart-color-stroke-91{stroke:#3D4A5E !important}#bootstrap-theme .chart-color-119{background:#3D4A5E !important;border-color:#3D4A5E !important;fill:#3D4A5E !important}#bootstrap-theme .chart-color-bg-119{background:#3D4A5E !important;fill:#3D4A5E !important}#bootstrap-theme .chart-color-border-119{border-color:#3D4A5E !important}#bootstrap-theme .chart-color-stroke-119{stroke:#3D4A5E !important}#bootstrap-theme .chart-color-8{background:#E56A6A !important;border-color:#E56A6A !important;fill:#E56A6A !important}#bootstrap-theme .chart-color-bg-8{background:#E56A6A !important;fill:#E56A6A !important}#bootstrap-theme .chart-color-border-8{border-color:#E56A6A !important}#bootstrap-theme .chart-color-stroke-8{stroke:#E56A6A !important}#bootstrap-theme .chart-color-36{background:#E56A6A !important;border-color:#E56A6A !important;fill:#E56A6A !important}#bootstrap-theme .chart-color-bg-36{background:#E56A6A !important;fill:#E56A6A !important}#bootstrap-theme .chart-color-border-36{border-color:#E56A6A !important}#bootstrap-theme .chart-color-stroke-36{stroke:#E56A6A !important}#bootstrap-theme .chart-color-64{background:#E56A6A !important;border-color:#E56A6A !important;fill:#E56A6A !important}#bootstrap-theme .chart-color-bg-64{background:#E56A6A !important;fill:#E56A6A !important}#bootstrap-theme .chart-color-border-64{border-color:#E56A6A !important}#bootstrap-theme .chart-color-stroke-64{stroke:#E56A6A !important}#bootstrap-theme .chart-color-92{background:#E56A6A !important;border-color:#E56A6A !important;fill:#E56A6A !important}#bootstrap-theme .chart-color-bg-92{background:#E56A6A !important;fill:#E56A6A !important}#bootstrap-theme .chart-color-border-92{border-color:#E56A6A !important}#bootstrap-theme .chart-color-stroke-92{stroke:#E56A6A !important}#bootstrap-theme .chart-color-120{background:#E56A6A !important;border-color:#E56A6A !important;fill:#E56A6A !important}#bootstrap-theme .chart-color-bg-120{background:#E56A6A !important;fill:#E56A6A !important}#bootstrap-theme .chart-color-border-120{border-color:#E56A6A !important}#bootstrap-theme .chart-color-stroke-120{stroke:#E56A6A !important}#bootstrap-theme .chart-color-9{background:#FA8F55 !important;border-color:#FA8F55 !important;fill:#FA8F55 !important}#bootstrap-theme .chart-color-bg-9{background:#FA8F55 !important;fill:#FA8F55 !important}#bootstrap-theme .chart-color-border-9{border-color:#FA8F55 !important}#bootstrap-theme .chart-color-stroke-9{stroke:#FA8F55 !important}#bootstrap-theme .chart-color-37{background:#FA8F55 !important;border-color:#FA8F55 !important;fill:#FA8F55 !important}#bootstrap-theme .chart-color-bg-37{background:#FA8F55 !important;fill:#FA8F55 !important}#bootstrap-theme .chart-color-border-37{border-color:#FA8F55 !important}#bootstrap-theme .chart-color-stroke-37{stroke:#FA8F55 !important}#bootstrap-theme .chart-color-65{background:#FA8F55 !important;border-color:#FA8F55 !important;fill:#FA8F55 !important}#bootstrap-theme .chart-color-bg-65{background:#FA8F55 !important;fill:#FA8F55 !important}#bootstrap-theme .chart-color-border-65{border-color:#FA8F55 !important}#bootstrap-theme .chart-color-stroke-65{stroke:#FA8F55 !important}#bootstrap-theme .chart-color-93{background:#FA8F55 !important;border-color:#FA8F55 !important;fill:#FA8F55 !important}#bootstrap-theme .chart-color-bg-93{background:#FA8F55 !important;fill:#FA8F55 !important}#bootstrap-theme .chart-color-border-93{border-color:#FA8F55 !important}#bootstrap-theme .chart-color-stroke-93{stroke:#FA8F55 !important}#bootstrap-theme .chart-color-121{background:#FA8F55 !important;border-color:#FA8F55 !important;fill:#FA8F55 !important}#bootstrap-theme .chart-color-bg-121{background:#FA8F55 !important;fill:#FA8F55 !important}#bootstrap-theme .chart-color-border-121{border-color:#FA8F55 !important}#bootstrap-theme .chart-color-stroke-121{stroke:#FA8F55 !important}#bootstrap-theme .chart-color-10{background:#6DAD68 !important;border-color:#6DAD68 !important;fill:#6DAD68 !important}#bootstrap-theme .chart-color-bg-10{background:#6DAD68 !important;fill:#6DAD68 !important}#bootstrap-theme .chart-color-border-10{border-color:#6DAD68 !important}#bootstrap-theme .chart-color-stroke-10{stroke:#6DAD68 !important}#bootstrap-theme .chart-color-38{background:#6DAD68 !important;border-color:#6DAD68 !important;fill:#6DAD68 !important}#bootstrap-theme .chart-color-bg-38{background:#6DAD68 !important;fill:#6DAD68 !important}#bootstrap-theme .chart-color-border-38{border-color:#6DAD68 !important}#bootstrap-theme .chart-color-stroke-38{stroke:#6DAD68 !important}#bootstrap-theme .chart-color-66{background:#6DAD68 !important;border-color:#6DAD68 !important;fill:#6DAD68 !important}#bootstrap-theme .chart-color-bg-66{background:#6DAD68 !important;fill:#6DAD68 !important}#bootstrap-theme .chart-color-border-66{border-color:#6DAD68 !important}#bootstrap-theme .chart-color-stroke-66{stroke:#6DAD68 !important}#bootstrap-theme .chart-color-94{background:#6DAD68 !important;border-color:#6DAD68 !important;fill:#6DAD68 !important}#bootstrap-theme .chart-color-bg-94{background:#6DAD68 !important;fill:#6DAD68 !important}#bootstrap-theme .chart-color-border-94{border-color:#6DAD68 !important}#bootstrap-theme .chart-color-stroke-94{stroke:#6DAD68 !important}#bootstrap-theme .chart-color-122{background:#6DAD68 !important;border-color:#6DAD68 !important;fill:#6DAD68 !important}#bootstrap-theme .chart-color-bg-122{background:#6DAD68 !important;fill:#6DAD68 !important}#bootstrap-theme .chart-color-border-122{border-color:#6DAD68 !important}#bootstrap-theme .chart-color-stroke-122{stroke:#6DAD68 !important}#bootstrap-theme .chart-color-11{background:#B37995 !important;border-color:#B37995 !important;fill:#B37995 !important}#bootstrap-theme .chart-color-bg-11{background:#B37995 !important;fill:#B37995 !important}#bootstrap-theme .chart-color-border-11{border-color:#B37995 !important}#bootstrap-theme .chart-color-stroke-11{stroke:#B37995 !important}#bootstrap-theme .chart-color-39{background:#B37995 !important;border-color:#B37995 !important;fill:#B37995 !important}#bootstrap-theme .chart-color-bg-39{background:#B37995 !important;fill:#B37995 !important}#bootstrap-theme .chart-color-border-39{border-color:#B37995 !important}#bootstrap-theme .chart-color-stroke-39{stroke:#B37995 !important}#bootstrap-theme .chart-color-67{background:#B37995 !important;border-color:#B37995 !important;fill:#B37995 !important}#bootstrap-theme .chart-color-bg-67{background:#B37995 !important;fill:#B37995 !important}#bootstrap-theme .chart-color-border-67{border-color:#B37995 !important}#bootstrap-theme .chart-color-stroke-67{stroke:#B37995 !important}#bootstrap-theme .chart-color-95{background:#B37995 !important;border-color:#B37995 !important;fill:#B37995 !important}#bootstrap-theme .chart-color-bg-95{background:#B37995 !important;fill:#B37995 !important}#bootstrap-theme .chart-color-border-95{border-color:#B37995 !important}#bootstrap-theme .chart-color-stroke-95{stroke:#B37995 !important}#bootstrap-theme .chart-color-123{background:#B37995 !important;border-color:#B37995 !important;fill:#B37995 !important}#bootstrap-theme .chart-color-bg-123{background:#B37995 !important;fill:#B37995 !important}#bootstrap-theme .chart-color-border-123{border-color:#B37995 !important}#bootstrap-theme .chart-color-stroke-123{stroke:#B37995 !important}#bootstrap-theme .chart-color-12{background:#84619C !important;border-color:#84619C !important;fill:#84619C !important}#bootstrap-theme .chart-color-bg-12{background:#84619C !important;fill:#84619C !important}#bootstrap-theme .chart-color-border-12{border-color:#84619C !important}#bootstrap-theme .chart-color-stroke-12{stroke:#84619C !important}#bootstrap-theme .chart-color-40{background:#84619C !important;border-color:#84619C !important;fill:#84619C !important}#bootstrap-theme .chart-color-bg-40{background:#84619C !important;fill:#84619C !important}#bootstrap-theme .chart-color-border-40{border-color:#84619C !important}#bootstrap-theme .chart-color-stroke-40{stroke:#84619C !important}#bootstrap-theme .chart-color-68{background:#84619C !important;border-color:#84619C !important;fill:#84619C !important}#bootstrap-theme .chart-color-bg-68{background:#84619C !important;fill:#84619C !important}#bootstrap-theme .chart-color-border-68{border-color:#84619C !important}#bootstrap-theme .chart-color-stroke-68{stroke:#84619C !important}#bootstrap-theme .chart-color-96{background:#84619C !important;border-color:#84619C !important;fill:#84619C !important}#bootstrap-theme .chart-color-bg-96{background:#84619C !important;fill:#84619C !important}#bootstrap-theme .chart-color-border-96{border-color:#84619C !important}#bootstrap-theme .chart-color-stroke-96{stroke:#84619C !important}#bootstrap-theme .chart-color-124{background:#84619C !important;border-color:#84619C !important;fill:#84619C !important}#bootstrap-theme .chart-color-bg-124{background:#84619C !important;fill:#84619C !important}#bootstrap-theme .chart-color-border-124{border-color:#84619C !important}#bootstrap-theme .chart-color-stroke-124{stroke:#84619C !important}#bootstrap-theme .chart-color-13{background:#2997B3 !important;border-color:#2997B3 !important;fill:#2997B3 !important}#bootstrap-theme .chart-color-bg-13{background:#2997B3 !important;fill:#2997B3 !important}#bootstrap-theme .chart-color-border-13{border-color:#2997B3 !important}#bootstrap-theme .chart-color-stroke-13{stroke:#2997B3 !important}#bootstrap-theme .chart-color-41{background:#2997B3 !important;border-color:#2997B3 !important;fill:#2997B3 !important}#bootstrap-theme .chart-color-bg-41{background:#2997B3 !important;fill:#2997B3 !important}#bootstrap-theme .chart-color-border-41{border-color:#2997B3 !important}#bootstrap-theme .chart-color-stroke-41{stroke:#2997B3 !important}#bootstrap-theme .chart-color-69{background:#2997B3 !important;border-color:#2997B3 !important;fill:#2997B3 !important}#bootstrap-theme .chart-color-bg-69{background:#2997B3 !important;fill:#2997B3 !important}#bootstrap-theme .chart-color-border-69{border-color:#2997B3 !important}#bootstrap-theme .chart-color-stroke-69{stroke:#2997B3 !important}#bootstrap-theme .chart-color-97{background:#2997B3 !important;border-color:#2997B3 !important;fill:#2997B3 !important}#bootstrap-theme .chart-color-bg-97{background:#2997B3 !important;fill:#2997B3 !important}#bootstrap-theme .chart-color-border-97{border-color:#2997B3 !important}#bootstrap-theme .chart-color-stroke-97{stroke:#2997B3 !important}#bootstrap-theme .chart-color-125{background:#2997B3 !important;border-color:#2997B3 !important;fill:#2997B3 !important}#bootstrap-theme .chart-color-bg-125{background:#2997B3 !important;fill:#2997B3 !important}#bootstrap-theme .chart-color-border-125{border-color:#2997B3 !important}#bootstrap-theme .chart-color-stroke-125{stroke:#2997B3 !important}#bootstrap-theme .chart-color-14{background:#263345 !important;border-color:#263345 !important;fill:#263345 !important}#bootstrap-theme .chart-color-bg-14{background:#263345 !important;fill:#263345 !important}#bootstrap-theme .chart-color-border-14{border-color:#263345 !important}#bootstrap-theme .chart-color-stroke-14{stroke:#263345 !important}#bootstrap-theme .chart-color-42{background:#263345 !important;border-color:#263345 !important;fill:#263345 !important}#bootstrap-theme .chart-color-bg-42{background:#263345 !important;fill:#263345 !important}#bootstrap-theme .chart-color-border-42{border-color:#263345 !important}#bootstrap-theme .chart-color-stroke-42{stroke:#263345 !important}#bootstrap-theme .chart-color-70{background:#263345 !important;border-color:#263345 !important;fill:#263345 !important}#bootstrap-theme .chart-color-bg-70{background:#263345 !important;fill:#263345 !important}#bootstrap-theme .chart-color-border-70{border-color:#263345 !important}#bootstrap-theme .chart-color-stroke-70{stroke:#263345 !important}#bootstrap-theme .chart-color-98{background:#263345 !important;border-color:#263345 !important;fill:#263345 !important}#bootstrap-theme .chart-color-bg-98{background:#263345 !important;fill:#263345 !important}#bootstrap-theme .chart-color-border-98{border-color:#263345 !important}#bootstrap-theme .chart-color-stroke-98{stroke:#263345 !important}#bootstrap-theme .chart-color-126{background:#263345 !important;border-color:#263345 !important;fill:#263345 !important}#bootstrap-theme .chart-color-bg-126{background:#263345 !important;fill:#263345 !important}#bootstrap-theme .chart-color-border-126{border-color:#263345 !important}#bootstrap-theme .chart-color-stroke-126{stroke:#263345 !important}#bootstrap-theme .chart-color-15{background:#CC4A49 !important;border-color:#CC4A49 !important;fill:#CC4A49 !important}#bootstrap-theme .chart-color-bg-15{background:#CC4A49 !important;fill:#CC4A49 !important}#bootstrap-theme .chart-color-border-15{border-color:#CC4A49 !important}#bootstrap-theme .chart-color-stroke-15{stroke:#CC4A49 !important}#bootstrap-theme .chart-color-43{background:#CC4A49 !important;border-color:#CC4A49 !important;fill:#CC4A49 !important}#bootstrap-theme .chart-color-bg-43{background:#CC4A49 !important;fill:#CC4A49 !important}#bootstrap-theme .chart-color-border-43{border-color:#CC4A49 !important}#bootstrap-theme .chart-color-stroke-43{stroke:#CC4A49 !important}#bootstrap-theme .chart-color-71{background:#CC4A49 !important;border-color:#CC4A49 !important;fill:#CC4A49 !important}#bootstrap-theme .chart-color-bg-71{background:#CC4A49 !important;fill:#CC4A49 !important}#bootstrap-theme .chart-color-border-71{border-color:#CC4A49 !important}#bootstrap-theme .chart-color-stroke-71{stroke:#CC4A49 !important}#bootstrap-theme .chart-color-99{background:#CC4A49 !important;border-color:#CC4A49 !important;fill:#CC4A49 !important}#bootstrap-theme .chart-color-bg-99{background:#CC4A49 !important;fill:#CC4A49 !important}#bootstrap-theme .chart-color-border-99{border-color:#CC4A49 !important}#bootstrap-theme .chart-color-stroke-99{stroke:#CC4A49 !important}#bootstrap-theme .chart-color-127{background:#CC4A49 !important;border-color:#CC4A49 !important;fill:#CC4A49 !important}#bootstrap-theme .chart-color-bg-127{background:#CC4A49 !important;fill:#CC4A49 !important}#bootstrap-theme .chart-color-border-127{border-color:#CC4A49 !important}#bootstrap-theme .chart-color-stroke-127{stroke:#CC4A49 !important}#bootstrap-theme .chart-color-16{background:#D97038 !important;border-color:#D97038 !important;fill:#D97038 !important}#bootstrap-theme .chart-color-bg-16{background:#D97038 !important;fill:#D97038 !important}#bootstrap-theme .chart-color-border-16{border-color:#D97038 !important}#bootstrap-theme .chart-color-stroke-16{stroke:#D97038 !important}#bootstrap-theme .chart-color-44{background:#D97038 !important;border-color:#D97038 !important;fill:#D97038 !important}#bootstrap-theme .chart-color-bg-44{background:#D97038 !important;fill:#D97038 !important}#bootstrap-theme .chart-color-border-44{border-color:#D97038 !important}#bootstrap-theme .chart-color-stroke-44{stroke:#D97038 !important}#bootstrap-theme .chart-color-72{background:#D97038 !important;border-color:#D97038 !important;fill:#D97038 !important}#bootstrap-theme .chart-color-bg-72{background:#D97038 !important;fill:#D97038 !important}#bootstrap-theme .chart-color-border-72{border-color:#D97038 !important}#bootstrap-theme .chart-color-stroke-72{stroke:#D97038 !important}#bootstrap-theme .chart-color-100{background:#D97038 !important;border-color:#D97038 !important;fill:#D97038 !important}#bootstrap-theme .chart-color-bg-100{background:#D97038 !important;fill:#D97038 !important}#bootstrap-theme .chart-color-border-100{border-color:#D97038 !important}#bootstrap-theme .chart-color-stroke-100{stroke:#D97038 !important}#bootstrap-theme .chart-color-128{background:#D97038 !important;border-color:#D97038 !important;fill:#D97038 !important}#bootstrap-theme .chart-color-bg-128{background:#D97038 !important;fill:#D97038 !important}#bootstrap-theme .chart-color-border-128{border-color:#D97038 !important}#bootstrap-theme .chart-color-stroke-128{stroke:#D97038 !important}#bootstrap-theme .chart-color-17{background:#4F944A !important;border-color:#4F944A !important;fill:#4F944A !important}#bootstrap-theme .chart-color-bg-17{background:#4F944A !important;fill:#4F944A !important}#bootstrap-theme .chart-color-border-17{border-color:#4F944A !important}#bootstrap-theme .chart-color-stroke-17{stroke:#4F944A !important}#bootstrap-theme .chart-color-45{background:#4F944A !important;border-color:#4F944A !important;fill:#4F944A !important}#bootstrap-theme .chart-color-bg-45{background:#4F944A !important;fill:#4F944A !important}#bootstrap-theme .chart-color-border-45{border-color:#4F944A !important}#bootstrap-theme .chart-color-stroke-45{stroke:#4F944A !important}#bootstrap-theme .chart-color-73{background:#4F944A !important;border-color:#4F944A !important;fill:#4F944A !important}#bootstrap-theme .chart-color-bg-73{background:#4F944A !important;fill:#4F944A !important}#bootstrap-theme .chart-color-border-73{border-color:#4F944A !important}#bootstrap-theme .chart-color-stroke-73{stroke:#4F944A !important}#bootstrap-theme .chart-color-101{background:#4F944A !important;border-color:#4F944A !important;fill:#4F944A !important}#bootstrap-theme .chart-color-bg-101{background:#4F944A !important;fill:#4F944A !important}#bootstrap-theme .chart-color-border-101{border-color:#4F944A !important}#bootstrap-theme .chart-color-stroke-101{stroke:#4F944A !important}#bootstrap-theme .chart-color-129{background:#4F944A !important;border-color:#4F944A !important;fill:#4F944A !important}#bootstrap-theme .chart-color-bg-129{background:#4F944A !important;fill:#4F944A !important}#bootstrap-theme .chart-color-border-129{border-color:#4F944A !important}#bootstrap-theme .chart-color-stroke-129{stroke:#4F944A !important}#bootstrap-theme .chart-color-18{background:#995978 !important;border-color:#995978 !important;fill:#995978 !important}#bootstrap-theme .chart-color-bg-18{background:#995978 !important;fill:#995978 !important}#bootstrap-theme .chart-color-border-18{border-color:#995978 !important}#bootstrap-theme .chart-color-stroke-18{stroke:#995978 !important}#bootstrap-theme .chart-color-46{background:#995978 !important;border-color:#995978 !important;fill:#995978 !important}#bootstrap-theme .chart-color-bg-46{background:#995978 !important;fill:#995978 !important}#bootstrap-theme .chart-color-border-46{border-color:#995978 !important}#bootstrap-theme .chart-color-stroke-46{stroke:#995978 !important}#bootstrap-theme .chart-color-74{background:#995978 !important;border-color:#995978 !important;fill:#995978 !important}#bootstrap-theme .chart-color-bg-74{background:#995978 !important;fill:#995978 !important}#bootstrap-theme .chart-color-border-74{border-color:#995978 !important}#bootstrap-theme .chart-color-stroke-74{stroke:#995978 !important}#bootstrap-theme .chart-color-102{background:#995978 !important;border-color:#995978 !important;fill:#995978 !important}#bootstrap-theme .chart-color-bg-102{background:#995978 !important;fill:#995978 !important}#bootstrap-theme .chart-color-border-102{border-color:#995978 !important}#bootstrap-theme .chart-color-stroke-102{stroke:#995978 !important}#bootstrap-theme .chart-color-130{background:#995978 !important;border-color:#995978 !important;fill:#995978 !important}#bootstrap-theme .chart-color-bg-130{background:#995978 !important;fill:#995978 !important}#bootstrap-theme .chart-color-border-130{border-color:#995978 !important}#bootstrap-theme .chart-color-stroke-130{stroke:#995978 !important}#bootstrap-theme .chart-color-19{background:#5F3D76 !important;border-color:#5F3D76 !important;fill:#5F3D76 !important}#bootstrap-theme .chart-color-bg-19{background:#5F3D76 !important;fill:#5F3D76 !important}#bootstrap-theme .chart-color-border-19{border-color:#5F3D76 !important}#bootstrap-theme .chart-color-stroke-19{stroke:#5F3D76 !important}#bootstrap-theme .chart-color-47{background:#5F3D76 !important;border-color:#5F3D76 !important;fill:#5F3D76 !important}#bootstrap-theme .chart-color-bg-47{background:#5F3D76 !important;fill:#5F3D76 !important}#bootstrap-theme .chart-color-border-47{border-color:#5F3D76 !important}#bootstrap-theme .chart-color-stroke-47{stroke:#5F3D76 !important}#bootstrap-theme .chart-color-75{background:#5F3D76 !important;border-color:#5F3D76 !important;fill:#5F3D76 !important}#bootstrap-theme .chart-color-bg-75{background:#5F3D76 !important;fill:#5F3D76 !important}#bootstrap-theme .chart-color-border-75{border-color:#5F3D76 !important}#bootstrap-theme .chart-color-stroke-75{stroke:#5F3D76 !important}#bootstrap-theme .chart-color-103{background:#5F3D76 !important;border-color:#5F3D76 !important;fill:#5F3D76 !important}#bootstrap-theme .chart-color-bg-103{background:#5F3D76 !important;fill:#5F3D76 !important}#bootstrap-theme .chart-color-border-103{border-color:#5F3D76 !important}#bootstrap-theme .chart-color-stroke-103{stroke:#5F3D76 !important}#bootstrap-theme .chart-color-131{background:#5F3D76 !important;border-color:#5F3D76 !important;fill:#5F3D76 !important}#bootstrap-theme .chart-color-bg-131{background:#5F3D76 !important;fill:#5F3D76 !important}#bootstrap-theme .chart-color-border-131{border-color:#5F3D76 !important}#bootstrap-theme .chart-color-stroke-131{stroke:#5F3D76 !important}#bootstrap-theme .chart-color-20{background:#147E99 !important;border-color:#147E99 !important;fill:#147E99 !important}#bootstrap-theme .chart-color-bg-20{background:#147E99 !important;fill:#147E99 !important}#bootstrap-theme .chart-color-border-20{border-color:#147E99 !important}#bootstrap-theme .chart-color-stroke-20{stroke:#147E99 !important}#bootstrap-theme .chart-color-48{background:#147E99 !important;border-color:#147E99 !important;fill:#147E99 !important}#bootstrap-theme .chart-color-bg-48{background:#147E99 !important;fill:#147E99 !important}#bootstrap-theme .chart-color-border-48{border-color:#147E99 !important}#bootstrap-theme .chart-color-stroke-48{stroke:#147E99 !important}#bootstrap-theme .chart-color-76{background:#147E99 !important;border-color:#147E99 !important;fill:#147E99 !important}#bootstrap-theme .chart-color-bg-76{background:#147E99 !important;fill:#147E99 !important}#bootstrap-theme .chart-color-border-76{border-color:#147E99 !important}#bootstrap-theme .chart-color-stroke-76{stroke:#147E99 !important}#bootstrap-theme .chart-color-104{background:#147E99 !important;border-color:#147E99 !important;fill:#147E99 !important}#bootstrap-theme .chart-color-bg-104{background:#147E99 !important;fill:#147E99 !important}#bootstrap-theme .chart-color-border-104{border-color:#147E99 !important}#bootstrap-theme .chart-color-stroke-104{stroke:#147E99 !important}#bootstrap-theme .chart-color-132{background:#147E99 !important;border-color:#147E99 !important;fill:#147E99 !important}#bootstrap-theme .chart-color-bg-132{background:#147E99 !important;fill:#147E99 !important}#bootstrap-theme .chart-color-border-132{border-color:#147E99 !important}#bootstrap-theme .chart-color-stroke-132{stroke:#147E99 !important}#bootstrap-theme .chart-color-21{background:#151D2C !important;border-color:#151D2C !important;fill:#151D2C !important}#bootstrap-theme .chart-color-bg-21{background:#151D2C !important;fill:#151D2C !important}#bootstrap-theme .chart-color-border-21{border-color:#151D2C !important}#bootstrap-theme .chart-color-stroke-21{stroke:#151D2C !important}#bootstrap-theme .chart-color-49{background:#151D2C !important;border-color:#151D2C !important;fill:#151D2C !important}#bootstrap-theme .chart-color-bg-49{background:#151D2C !important;fill:#151D2C !important}#bootstrap-theme .chart-color-border-49{border-color:#151D2C !important}#bootstrap-theme .chart-color-stroke-49{stroke:#151D2C !important}#bootstrap-theme .chart-color-77{background:#151D2C !important;border-color:#151D2C !important;fill:#151D2C !important}#bootstrap-theme .chart-color-bg-77{background:#151D2C !important;fill:#151D2C !important}#bootstrap-theme .chart-color-border-77{border-color:#151D2C !important}#bootstrap-theme .chart-color-stroke-77{stroke:#151D2C !important}#bootstrap-theme .chart-color-105{background:#151D2C !important;border-color:#151D2C !important;fill:#151D2C !important}#bootstrap-theme .chart-color-bg-105{background:#151D2C !important;fill:#151D2C !important}#bootstrap-theme .chart-color-border-105{border-color:#151D2C !important}#bootstrap-theme .chart-color-stroke-105{stroke:#151D2C !important}#bootstrap-theme .chart-color-133{background:#151D2C !important;border-color:#151D2C !important;fill:#151D2C !important}#bootstrap-theme .chart-color-bg-133{background:#151D2C !important;fill:#151D2C !important}#bootstrap-theme .chart-color-border-133{border-color:#151D2C !important}#bootstrap-theme .chart-color-stroke-133{stroke:#151D2C !important}#bootstrap-theme .chart-color-22{background:#B32E2E !important;border-color:#B32E2E !important;fill:#B32E2E !important}#bootstrap-theme .chart-color-bg-22{background:#B32E2E !important;fill:#B32E2E !important}#bootstrap-theme .chart-color-border-22{border-color:#B32E2E !important}#bootstrap-theme .chart-color-stroke-22{stroke:#B32E2E !important}#bootstrap-theme .chart-color-50{background:#B32E2E !important;border-color:#B32E2E !important;fill:#B32E2E !important}#bootstrap-theme .chart-color-bg-50{background:#B32E2E !important;fill:#B32E2E !important}#bootstrap-theme .chart-color-border-50{border-color:#B32E2E !important}#bootstrap-theme .chart-color-stroke-50{stroke:#B32E2E !important}#bootstrap-theme .chart-color-78{background:#B32E2E !important;border-color:#B32E2E !important;fill:#B32E2E !important}#bootstrap-theme .chart-color-bg-78{background:#B32E2E !important;fill:#B32E2E !important}#bootstrap-theme .chart-color-border-78{border-color:#B32E2E !important}#bootstrap-theme .chart-color-stroke-78{stroke:#B32E2E !important}#bootstrap-theme .chart-color-106{background:#B32E2E !important;border-color:#B32E2E !important;fill:#B32E2E !important}#bootstrap-theme .chart-color-bg-106{background:#B32E2E !important;fill:#B32E2E !important}#bootstrap-theme .chart-color-border-106{border-color:#B32E2E !important}#bootstrap-theme .chart-color-stroke-106{stroke:#B32E2E !important}#bootstrap-theme .chart-color-134{background:#B32E2E !important;border-color:#B32E2E !important;fill:#B32E2E !important}#bootstrap-theme .chart-color-bg-134{background:#B32E2E !important;fill:#B32E2E !important}#bootstrap-theme .chart-color-border-134{border-color:#B32E2E !important}#bootstrap-theme .chart-color-stroke-134{stroke:#B32E2E !important}#bootstrap-theme .chart-color-23{background:#BF561D !important;border-color:#BF561D !important;fill:#BF561D !important}#bootstrap-theme .chart-color-bg-23{background:#BF561D !important;fill:#BF561D !important}#bootstrap-theme .chart-color-border-23{border-color:#BF561D !important}#bootstrap-theme .chart-color-stroke-23{stroke:#BF561D !important}#bootstrap-theme .chart-color-51{background:#BF561D !important;border-color:#BF561D !important;fill:#BF561D !important}#bootstrap-theme .chart-color-bg-51{background:#BF561D !important;fill:#BF561D !important}#bootstrap-theme .chart-color-border-51{border-color:#BF561D !important}#bootstrap-theme .chart-color-stroke-51{stroke:#BF561D !important}#bootstrap-theme .chart-color-79{background:#BF561D !important;border-color:#BF561D !important;fill:#BF561D !important}#bootstrap-theme .chart-color-bg-79{background:#BF561D !important;fill:#BF561D !important}#bootstrap-theme .chart-color-border-79{border-color:#BF561D !important}#bootstrap-theme .chart-color-stroke-79{stroke:#BF561D !important}#bootstrap-theme .chart-color-107{background:#BF561D !important;border-color:#BF561D !important;fill:#BF561D !important}#bootstrap-theme .chart-color-bg-107{background:#BF561D !important;fill:#BF561D !important}#bootstrap-theme .chart-color-border-107{border-color:#BF561D !important}#bootstrap-theme .chart-color-stroke-107{stroke:#BF561D !important}#bootstrap-theme .chart-color-135{background:#BF561D !important;border-color:#BF561D !important;fill:#BF561D !important}#bootstrap-theme .chart-color-bg-135{background:#BF561D !important;fill:#BF561D !important}#bootstrap-theme .chart-color-border-135{border-color:#BF561D !important}#bootstrap-theme .chart-color-stroke-135{stroke:#BF561D !important}#bootstrap-theme .chart-color-24{background:#377A31 !important;border-color:#377A31 !important;fill:#377A31 !important}#bootstrap-theme .chart-color-bg-24{background:#377A31 !important;fill:#377A31 !important}#bootstrap-theme .chart-color-border-24{border-color:#377A31 !important}#bootstrap-theme .chart-color-stroke-24{stroke:#377A31 !important}#bootstrap-theme .chart-color-52{background:#377A31 !important;border-color:#377A31 !important;fill:#377A31 !important}#bootstrap-theme .chart-color-bg-52{background:#377A31 !important;fill:#377A31 !important}#bootstrap-theme .chart-color-border-52{border-color:#377A31 !important}#bootstrap-theme .chart-color-stroke-52{stroke:#377A31 !important}#bootstrap-theme .chart-color-80{background:#377A31 !important;border-color:#377A31 !important;fill:#377A31 !important}#bootstrap-theme .chart-color-bg-80{background:#377A31 !important;fill:#377A31 !important}#bootstrap-theme .chart-color-border-80{border-color:#377A31 !important}#bootstrap-theme .chart-color-stroke-80{stroke:#377A31 !important}#bootstrap-theme .chart-color-108{background:#377A31 !important;border-color:#377A31 !important;fill:#377A31 !important}#bootstrap-theme .chart-color-bg-108{background:#377A31 !important;fill:#377A31 !important}#bootstrap-theme .chart-color-border-108{border-color:#377A31 !important}#bootstrap-theme .chart-color-stroke-108{stroke:#377A31 !important}#bootstrap-theme .chart-color-136{background:#377A31 !important;border-color:#377A31 !important;fill:#377A31 !important}#bootstrap-theme .chart-color-bg-136{background:#377A31 !important;fill:#377A31 !important}#bootstrap-theme .chart-color-border-136{border-color:#377A31 !important}#bootstrap-theme .chart-color-stroke-136{stroke:#377A31 !important}#bootstrap-theme .chart-color-25{background:#803D5E !important;border-color:#803D5E !important;fill:#803D5E !important}#bootstrap-theme .chart-color-bg-25{background:#803D5E !important;fill:#803D5E !important}#bootstrap-theme .chart-color-border-25{border-color:#803D5E !important}#bootstrap-theme .chart-color-stroke-25{stroke:#803D5E !important}#bootstrap-theme .chart-color-53{background:#803D5E !important;border-color:#803D5E !important;fill:#803D5E !important}#bootstrap-theme .chart-color-bg-53{background:#803D5E !important;fill:#803D5E !important}#bootstrap-theme .chart-color-border-53{border-color:#803D5E !important}#bootstrap-theme .chart-color-stroke-53{stroke:#803D5E !important}#bootstrap-theme .chart-color-81{background:#803D5E !important;border-color:#803D5E !important;fill:#803D5E !important}#bootstrap-theme .chart-color-bg-81{background:#803D5E !important;fill:#803D5E !important}#bootstrap-theme .chart-color-border-81{border-color:#803D5E !important}#bootstrap-theme .chart-color-stroke-81{stroke:#803D5E !important}#bootstrap-theme .chart-color-109{background:#803D5E !important;border-color:#803D5E !important;fill:#803D5E !important}#bootstrap-theme .chart-color-bg-109{background:#803D5E !important;fill:#803D5E !important}#bootstrap-theme .chart-color-border-109{border-color:#803D5E !important}#bootstrap-theme .chart-color-stroke-109{stroke:#803D5E !important}#bootstrap-theme .chart-color-137{background:#803D5E !important;border-color:#803D5E !important;fill:#803D5E !important}#bootstrap-theme .chart-color-bg-137{background:#803D5E !important;fill:#803D5E !important}#bootstrap-theme .chart-color-border-137{border-color:#803D5E !important}#bootstrap-theme .chart-color-stroke-137{stroke:#803D5E !important}#bootstrap-theme .chart-color-26{background:#47275C !important;border-color:#47275C !important;fill:#47275C !important}#bootstrap-theme .chart-color-bg-26{background:#47275C !important;fill:#47275C !important}#bootstrap-theme .chart-color-border-26{border-color:#47275C !important}#bootstrap-theme .chart-color-stroke-26{stroke:#47275C !important}#bootstrap-theme .chart-color-54{background:#47275C !important;border-color:#47275C !important;fill:#47275C !important}#bootstrap-theme .chart-color-bg-54{background:#47275C !important;fill:#47275C !important}#bootstrap-theme .chart-color-border-54{border-color:#47275C !important}#bootstrap-theme .chart-color-stroke-54{stroke:#47275C !important}#bootstrap-theme .chart-color-82{background:#47275C !important;border-color:#47275C !important;fill:#47275C !important}#bootstrap-theme .chart-color-bg-82{background:#47275C !important;fill:#47275C !important}#bootstrap-theme .chart-color-border-82{border-color:#47275C !important}#bootstrap-theme .chart-color-stroke-82{stroke:#47275C !important}#bootstrap-theme .chart-color-110{background:#47275C !important;border-color:#47275C !important;fill:#47275C !important}#bootstrap-theme .chart-color-bg-110{background:#47275C !important;fill:#47275C !important}#bootstrap-theme .chart-color-border-110{border-color:#47275C !important}#bootstrap-theme .chart-color-stroke-110{stroke:#47275C !important}#bootstrap-theme .chart-color-138{background:#47275C !important;border-color:#47275C !important;fill:#47275C !important}#bootstrap-theme .chart-color-bg-138{background:#47275C !important;fill:#47275C !important}#bootstrap-theme .chart-color-border-138{border-color:#47275C !important}#bootstrap-theme .chart-color-stroke-138{stroke:#47275C !important}#bootstrap-theme .chart-color-27{background:#056780 !important;border-color:#056780 !important;fill:#056780 !important}#bootstrap-theme .chart-color-bg-27{background:#056780 !important;fill:#056780 !important}#bootstrap-theme .chart-color-border-27{border-color:#056780 !important}#bootstrap-theme .chart-color-stroke-27{stroke:#056780 !important}#bootstrap-theme .chart-color-55{background:#056780 !important;border-color:#056780 !important;fill:#056780 !important}#bootstrap-theme .chart-color-bg-55{background:#056780 !important;fill:#056780 !important}#bootstrap-theme .chart-color-border-55{border-color:#056780 !important}#bootstrap-theme .chart-color-stroke-55{stroke:#056780 !important}#bootstrap-theme .chart-color-83{background:#056780 !important;border-color:#056780 !important;fill:#056780 !important}#bootstrap-theme .chart-color-bg-83{background:#056780 !important;fill:#056780 !important}#bootstrap-theme .chart-color-border-83{border-color:#056780 !important}#bootstrap-theme .chart-color-stroke-83{stroke:#056780 !important}#bootstrap-theme .chart-color-111{background:#056780 !important;border-color:#056780 !important;fill:#056780 !important}#bootstrap-theme .chart-color-bg-111{background:#056780 !important;fill:#056780 !important}#bootstrap-theme .chart-color-border-111{border-color:#056780 !important}#bootstrap-theme .chart-color-stroke-111{stroke:#056780 !important}#bootstrap-theme .chart-color-139{background:#056780 !important;border-color:#056780 !important;fill:#056780 !important}#bootstrap-theme .chart-color-bg-139{background:#056780 !important;fill:#056780 !important}#bootstrap-theme .chart-color-border-139{border-color:#056780 !important}#bootstrap-theme .chart-color-stroke-139{stroke:#056780 !important}#bootstrap-theme .crm_custom-select{background:#fff;display:inline-block;position:relative}#bootstrap-theme .crm_custom-select>select{background:transparent;border:1px solid #c2cfd8;height:30px;padding:4px 44px 4px 12px;position:relative;width:100%;z-index:2;-moz-appearance:none;-webkit-appearance:none;-webkit-border-radius:0px}#bootstrap-theme .crm_custom-select>select::-ms-expand{display:none}#bootstrap-theme .crm_custom-select>select:disabled+.crm_custom-select__arrow{z-index:2}#bootstrap-theme .crm_custom-select>select:focus+.crm_custom-select__arrow{border-color:#66afe9}#bootstrap-theme .ie9 .crm_custom-select>select{padding-right:10px}#bootstrap-theme .ie9 .crm_custom-select .crm_custom-select__arrow{display:none}#bootstrap-theme .crm_custom-select--full{display:block;width:auto}#bootstrap-theme .crm_custom-select--transparent{background:transparent}#bootstrap-theme .crm_custom-select--transparent option{background:#fff}#bootstrap-theme .crm_custom-select__arrow{border-left:1px solid #c2cfd8;bottom:0;display:inline-block;line-height:31px;position:absolute;right:0;text-align:center;top:0;width:32px;z-index:1}#bootstrap-theme .crm_custom-select__arrow:before{content:'\f0d7';color:#4d4d69;font-family:"FontAwesome";font-style:normal;text-rendering:auto;-webkit-font-smoothing:antialiased}#bootstrap-theme .has-error .crm_custom-select__arrow{border-color:#cf3458 !important}#bootstrap-theme .has-feedback>.crm_custom-select>select{padding-right:54px}#bootstrap-theme .has-feedback>.crm_custom-select+.form-control-feedback{right:27px !important}#bootstrap-theme .crm-editable-enabled{display:block;position:relative}#bootstrap-theme .crm-editable-enabled .crm-i{opacity:1}#bootstrap-theme .crm-editable-enabled .crm-editable-form button{background-color:#f3f6f7;border:1px solid #c2cfd8;border-radius:0 0 0 2px;bottom:-29px;color:#0071bd;height:30px;left:auto;outline:none;padding:1px 6px;right:32px}#bootstrap-theme .crm-editable-enabled .crm-editable-form button .crm-i{opacity:1}#bootstrap-theme .crm-editable-enabled .crm-editable-form button[type='cancel']{border-left:0;border-left:0;border-radius:0 0 2px;color:#cf3458;right:0}#bootstrap-theme .crm-editable-enabled .crm-editable-form select{background:#fff;border:1px solid #c2cfd8;border-radius:2px 2px 0;outline:none;width:100%}#bootstrap-theme .crm-editable-enabled .crm-editable-form input{border:1px solid #c2cfd8;border-radius:2px 2px 0 2px;display:block;outline:none;width:100% !important}#bootstrap-theme .crm-editable-enabled .crm-editable-form textarea{border:1px solid #c2cfd8;border-radius:2px 2px 0 2px;display:block;outline:none;width:100% !important}#bootstrap-theme .crm-editable-enabled:not(.crm-editable-editing):hover{border:1px solid #c2cfd8;border-radius:2px;padding:1px 30px 1px 3px}#bootstrap-theme .crm-editable-enabled:not(.crm-editable-editing):hover::before{background-color:#e8eef0;border-left:1px solid #c2cfd8;border-radius:0 2px 2px 0;bottom:0;content:'';height:100%;position:absolute;right:0;text-align:center;top:0;vertical-align:middle;width:30px}#bootstrap-theme .crm-editable-enabled:not(.crm-editable-editing):hover::after{font-family:'FontAwesome';font-style:normal;text-rendering:auto;font-size:13px;content:"";color:#4d4d69;position:absolute;right:0;text-align:center;top:calc(50% - 9px);width:30px}#bootstrap-theme .crm-editable-enabled[crm-editable-tab-title].crm-editable-editing{border:1px solid #c2cfd8}#bootstrap-theme .crm-editable-enabled[crm-editable-tab-title]:not(.crm-editable-editing):hover{padding:1px 3px}#bootstrap-theme .crm-editable-enabled[crm-editable-tab-title]:not(.crm-editable-editing):hover::before{display:none}#bootstrap-theme .crm-editable-enabled[crm-editable-tab-title]:not(.crm-editable-editing):hover::after{display:none}#bootstrap-theme .crm_notification{-webkit-box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);box-shadow:0 3px 18px 0 rgba(48,40,40,0.25);background:#fff;border-radius:2px;padding:20px;width:360px}#bootstrap-theme .crm_notification+.crm_notification{margin-top:20px}#bootstrap-theme .crm_notification__close{float:right}#bootstrap-theme .crm_notification__close:before{font-family:'FontAwesome';font-style:normal;text-rendering:auto;font-size:1em;content:"";color:#4d4d69;color:#464354}#bootstrap-theme .crm_notification__header{margin-bottom:10px}#bootstrap-theme .crm_notification__heading>.crm_notification__title{display:inline-block;margin:0 0 0 5px}#bootstrap-theme .crm_notification__icon:before{font-family:'FontAwesome';font-style:normal;text-rendering:auto;font-size:1em;color:#4d4d69}#bootstrap-theme .crm_notification__title{font-size:13px}#bootstrap-theme .crm_notification--danger .crm_notification__icon:before{content:"";color:#cf3458}#bootstrap-theme .crm_notification--info .crm_notification__icon:before{content:"";color:#0071bd}#bootstrap-theme .crm_notification--success .crm_notification__icon:before{content:"";color:#44cb7e}@media (min-width: 992px){#bootstrap-theme .crm_page__content{overflow:hidden}#bootstrap-theme .crm_page__content>.row>[class*="col-"]:first-child{max-width:186px}#bootstrap-theme .crm_page__content .crm_page__main{margin:0 auto;max-width:1004px}#bootstrap-theme .crm_page__content .crm_page__sidebar{margin-bottom:-99999px;padding-bottom:99999px}}@media (min-width: 1240px){#bootstrap-theme .crm_page__content>.row>[class*="col-"]:last-child{float:none;margin-left:170px;overflow:hidden;width:auto}}#bootstrap-theme .crm_page__footer{text-align:center}#bootstrap-theme .crm_page__footer>.crm_page__footer__logo{display:inline-block;margin-bottom:15px}#bootstrap-theme .crm_page__footer__logo{color:#464354;font-size:1.6923076923em}#bootstrap-theme .crm_page__main{padding:16px 16px 50px 16px}#bootstrap-theme .crm_page__main>.crm_page__footer{margin-top:50px}@media (min-width: 992px){#bootstrap-theme .crm_page__main{padding-left:0}}@media (min-width: 1240px){#bootstrap-theme .crm_page__main{padding-right:0}}#bootstrap-theme .crm_page__sidebar{background:#fff}#bootstrap-theme .crm_page__topbar{background:#222831;color:#fff}#bootstrap-theme .crm_page__topbar .breadcrumb{background:transparent;margin-bottom:0;padding:15px}#bootstrap-theme .crm_page__topbar .breadcrumb>li{color:#fff}#bootstrap-theme .crm_page__topbar .breadcrumb>li:before{color:#fff}#bootstrap-theme .crm_page__topbar .breadcrumb>li>a{color:#fff}#bootstrap-theme .crm_page__topbar__link{display:inline-block;padding:15px}#bootstrap-theme .crm_page__topbar__link>a{color:#fff !important}#bootstrap-theme .crm_show-more__button{margin-top:30px}#bootstrap-theme .crm_spinner{background:rgba(255,255,255,0.5);animation:fadeIn 0.3s;min-height:100px;position:relative;text-align:center;width:100%;margin-bottom:15px}#bootstrap-theme .crm_spinner__img{background-image:url("data:image/gif;base64,R0lGODlhIAAgAOfzAAABAAACAAEEAAIFAQQHAgUIBAcJBQgLBwoMCAsNCgwPCw4QDA8RDRASDxETEBIUERMUEhQVExUWFBYYFRcYFhgZFxkbGBocGRscGhwdGx0fHB4fHR8gHiAhHyEjICIkISMkIiQlIyUnJCYoJScoJigpJykrKCosKSstKiwtKy0uLC4vLS8xLjAyLzEzMDIzMTM0MjQ2MzU3NDY4NTc5Njg5Nzk6ODo7OTs9Ojw+Oz0/PD5APT9APkBBP0FCQEFDQUNFQkRGQ0VHREZIRUdJRkhJR0lKSEpLSUpMSkxOS01PTE5QTU9RTlBST1FTUFJUUVNUUlRVU1VWVFZXVVZYVVdZVllbWFpcWVtdWlxeW11fXF5gXV9hXmBiX2FjYGJkYWNlYmRlY2VmZGZnZWdoZmhpZ2hqZ2lraGpsaWttamxua21vbG5wbW9xbnFzcHJ0cXN1cnR2c3V3dHZ4dXd5dnh6d3l7eHp8eXt9enx+e31/fH6AfX+BfoCCf4GDgIKEgYOFgoSGg4WHhIaIhYeJhoiKh4mLiIqMiYuNioyOi42PjI6QjY+RjpCSj5GTkJKUkZOVkpSWk5WXlJaYlZeZlpial5qbmJudmZyem52fnJ6gnZ+hnqCin6GjoKKkoaOloqSmo6WnpKaopaeppqiqp6mrqKqsqautqqyuq62vrK6wrbCyrrGzr7K0sbO1srS2s7W3tLa4tbe5tri6t7m7uLq8ubu9ury+u72/vL7BvcDCvsHDv8LEwcPFwsTGw8XHxMbIxcfJxsjKx8nLyMrMycvOys3Py87QzM/RztDSz9HT0NLU0dPV0tTW09XX1NbY1dja1tnb19rc2dvd2tze293f3N7g3d/h3uDi3+Hk4OPl4eTm4+Xn5Obo5efp5ujq5+nr6Ors6evu6u3v6+7w7e/x7vDy7/Hz8PL08fP18vT38/b49Pf59vj69/n7+Pr8+fv9+vz/+/7//P///////////////////////////////////////////////////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQBBQD/ACwAAAAAIAAgAAAI/gDlCRxIUCA8gcJ+HSzIsCFDbcbAqUIQQJTDiw5z4XrVI0AAB+UwihQoLhevPQFGKAggZqTIaLmOhQgQp1AACL5cXhSmC04AFKSCjQig5J1OhuiAqSJgwNI1Xp4CIBh1tGC3XzcC+DgnDxovLAGAfKs68NofqbQGUiNFIYAmsvLe9QIRwEpBa20CXOhGlt2ZAAbEMTzmIoAbssA6BEDkkBLFXkfdyQlgAqORACv4uix2IYAsjLsYBLjjbeQ7LwGkpBP5JUCHVtpE5pLaamQ5BwGqBMuGUUWAJ4JHptJrqdc1h4sCUAjFzuU7GwGo2MJ1vCA4AgGuZDPq8hjgTrtwsPEmqCUABlvVXaoDE6CJxl5cBdZaEKAQsXCmzWmrFgpCAD+8MNOOQOo40R4xyohEjjXMHBOML8CIEUAD5Qwo0CjYtZHGHI404uGHjRhChxtsqJHGiW4EIscDAbRBkCUexSjjjDTSGAVB4ATixRFHVPHEEUYEeYQSTljhxRdIJqlkGKUxRMwwywQTDDHMRNNNfHDJg04utvRyjDXenONOlgWZ4006FpKp5ppstummTgEBACH5BAEFAP8ALAAAAAAgACAAAAj+AOUJHEiw4DlzAtW5K8iwYcNwvGiBs+WCDkKHGAu6W2Yr2CAHAQwEy0hSoDdczfIgCBngU8mM74T9ItQgQA07Abyoe+nQmi5BIGPsekRABDmeDN3pMrTSyDldn0QEgIa0YLNDBgIcSSfPmiwiAQhVHXgO0Eoo4gSyO1YmwI6xAgUxCPDkG8FviUAurGqp5hB0GnV5CNCqaisJAWw4POcjQBakuiAE0JExEAELPHPVrJEtozEFAUaSTEXBZq+SHwLEIRkKsYxW50qOCRDDrsNQEQLAgEXtJaoAFWhxZRgrN4pcwWKXhBcAAaDOBX1VmMxrlzWkja0IuyhwXY8AExS7dcpVlZFNWs0IuqtDIEAADUYAsYJOkloAD5xygSN4zhegIKUFwMAJRggCTEbwcGCAIr/8UlA4ucRyzCxplOBecxhMoUk04XjTjTYgZjNFAG78gks0BHVzSy4s7tKLLIAMscCFAXzAhSW12IILLoEEkAMruTCj3jXQFGkkNddcA4seO3AAWnM+6NGKKAQ48Eo07cDljSpzFEHChSuAVAtcGilTyh5H5BbALGQ6BI4zsLQyXJt01mnnnW0GBAAh+QQBBQD/ACwAAAAAIAAgAAAI/gDlCRxIsCBBeAYTKlxIjlg1hAsjJgTXy1Ywb6QsgZPI0ZsvXcESHSEQABDHiB594dKCIIDLLicVggO2C84Cl0LqBPgS0+C4XYs+uPQgKFihnT0JjtOUxGWHOuSu+YKDNKk8Y2IcBHiQxpjAbMHSVI2p7hAJl09sESRXTCzPmLhekAShKl1Bd8vcnixHxWUAOe8URlMzViE6Ri4J2KgVURybwgXPsWrhkoUfaBLdtYE8sBYUlyDUwIrWjuMczlcZBNBw55MvZqU5EgpwJSEvlxIEAVsW+CTiJwnfSboZYIa1no0CLFmXkN0rIFoD5Pl2MvkRbwbhHcNlLBQOAwFMrnQaJzG5EWoJq+mqFWyXnhQuj4iKaJ6Z3YLvznlzFgwXKTUKBLDAEsEoZF4xGykEjzvoZGPMKU0F0EAY1xhkXjAVngRPObOY4BICfwRjDToCJcdEMMy4Y1UmQgVwgiWxAGPNHwFUccwv9yUFzhwguBQFJsKItcQxtpBnlUDFEBZABWqQEcARwNRi5JHysNPLES5BEIAW7oQDEZUCtXPKBC6FAeZCbUSB3ZlstklQQAAh+QQBBQD/ACwAAAAAIAAgAAAI/gDlCRxIsKDBgwgTKlzI8CC8b93cNZw4jpmuW9kmLnQXrReuXsGYCQv2TqPBbr527fKFyMcCAgFmmRw4zherSF5MBNgJM4Aqk+yiqYrTo8HOABhUDGGDgMGxhuB8RTqz4ugCHWAW+YIWzFIAId4UMpP05YaGox+6WNKVraS8breuBLjjlqC7WmhSUDBwNIWaX93OGdRm60OAUAXhaeq5M0IRQbCuwUuoTVUAD8EKqpMRgECNOJl8BWvGbqG3OQGGoCv4jhIBCZOC9ToGDmqOAGoOhpMTwMCecXUXajOgoJTBc79qxQmAwJLGWgEuVDNYjlYuYXsCLIA0EU6AGwi7wnn85UZ7p4YxAsBJ2M5Zr19rAig4tbCaUVQLyR37lUY+/YSbGMAANAzB440xZ8jHSUJcBNBDRg25k80b8gkyWUHr2BAAHtSY5E4hzM2RzYUC+ZJBAKhEM5M8jjB3BzDhDOSaCLwQuGKLBvhRizHouCNGAF/sYuOKjRiQ4y668KKhKbkMORM8jRDg2zGLGCDBME2uSFAj8hUSSQA+2JKllgO1iEAHAaBRy5hkCtRib75Qw0w7bRL0iANMaFPnQcvEqFBAACH5BAEFAP8ALAAAAAAgACAAAAj+AOUJHEiwoMGDCBMqXMiwocOHECOaOxcxoTtvzXzp0jWu4sB357Id04VLFy+S4gyiG5arpcuXuGSJkqRI0SJGjCyBetQolq5yBFEFGEq0qNGjRvEQ5DSUDRsvWKJaqXJFDBw7d7JqtUMGi5YHAb4Q9HYjwJJgtWjNojWM2jh069i1m0uX3S9fbggESCnQWzA7BgJMGgYt3DuG7nKNQhFg0UB4xW4BKwsDYrdaYwKEYEdQmy5itBAQiPSQGScHAVgZ9HwsS4AV2hz+IhLACdDVu2JJCMCnYbpGASbUSui5T4AOxxh2sxAgy0JtsmgEUKNuoZwAF6AplzR0mEJvYN+pOKTWJAARdwnDBMCA3uEt5qEQ8poQwFLEN8GrqXRtw1vEcBsEkARFBNEylCftVGRJAAhswpc84wgRwBbBeCRPCwHM8Es4An0SQAWcWGPhMQzyEUw44VAQwBm3EOgRGwG8cIoyYASQAivBtOeRNh0EcEgkBBiACC/NwGOhQJkEcIIHAbQwjC2xHSlPN0AQZQkvu5gjpUCeBHbGOb0wk+CW7CBSBkXo6IhQQAAh+QQBBQD/ACwAAAAAIAAgAAAI/gDlCRxIsKDBgwgTKlzIsKHDhw2t1YLYMBoDAguOWAJHESEtAgEMBBjpIQ6wceneUXyHThwCA3agmKAwMgCCI490NdN2rmc5cuV+lnNHEFwuYDECwDkWa9KaIikQ1NTAI0wgTrl64aJFq1Y1gt1w9SIUQAg4a9m0VRPmSlKaFTURgNiBplOzY7+iEXR3DJevACvCHVTnDVqmKFIDEHCgwYebawW98eq1QcJEhfDkBZNzoqZiIuoIKts1JcAhiMrm+ABJ4RfBc70aBchCtGE6Yb0WSfVSeyA1VgRe6G0IrRYbkGlCF3yXqwYBXA3L5Yojks86hN7KBIDUkJickXqUwR+ENyqAlXMLtc0ZuSbdQmgdIljDvGdkmHYMz2kJUEqhopFfPPSfF96MB4lIZfTG0CwOnPCLSgVJIpUa7j10DQwBzAJNQZuMtMU4HUURACDAoDOQKSNFMRxFiAQwxC/KCJTJSEXscl1HwDzAwC+/eCMJSEfQ0k1HA4UQACu4zCEVFbQcoyBFVwSQByEgqUFLLwUSKRAoAUgg1Ri65HKMlgOJA1IATcySiy7okSlQEhV8kU0vt6zo5jvDEJVNNBAyFBAAIfkEAQUA/wAsAAAAACAAIAAACP4A5QkcSLCgwYMIEypcWBAdqk3gGEocOCxAgBydJi5cVy6XxQAKchDTeDBdr12OApC5YtHAmHMkCZLTpatRgCu8RI0gEEBDqXQxBfbqVSpAF16+fM3xYPHILXQxlfEaFYBOOG3RnLHaUsGil1jl2k3slitSgDvuBLJD940WGIsOrMiilq3cQne4GJ0V23DZEbhoegULxqwbX4PEFu1NqCqDxQ2PfuXKpUsZPIPWFC1O6E5OA4tQXlH+Zc6guUSbFTqL4iCAhTajqh189yj1wlQ+LJawBNPgJ9sKwcHyMyMAgUAHXwFH2G2XsE0ULKY56PHNOobecuG2SCDOO+oBwKlAVehNFZwFARAEyZbQY5VxCqn9WWExR6qF7qkldAXE4gpH4jDkkRXCHKSNF60ZMIg1E3mExSzfEeRHawFIEU1aDQaAhSzhDLQLCBZlMEpQHm1BCzTyMLOFRRF4wUxQ8ni0xi+iHIJeA0J0og2MMQZgBhofWAREIrlYcxmMHj3A0wmawFLkkTDq8lEauexyi5E8ClQOHl94w8tk0UCZ5UDL/ILimAf1FlNAACH5BAEFAP8ALAAAAAAgACAAAAj+AOUJHEhQ3jt17woqXMhwYCMalhpKVPgOnLd0AQIYgARvosdzuXxBCtBAYyKPE8nVCrYkABU7GRWhbFiOlrYQARrtshJz5sJyt1pJIMBsly4mBAJM8lmwHC86BJLI61aMV5GMizoyleeUSABNAttlA/ZEo8yt5k6hCOCNoDpoXjQCcsf0XKIGJ7QSFPcmIxpwPtOtCSCHobu+AcwcM4fy25EAtiSOSTpGV7R1E6GBQKBtYpqMYGgB04aZYbAASch51BOAgBlbtop1S1gQnqYAgehOhCeo9Zxjt3RdU7gOS4BRM9kBylgm2K1oCstRuIDL57kwoLMtXBZARzOU7rrEBevlJKmhhYECaOm2e1yzWqMoGVoQYMEwhTEM/Onc0N0wSWo0gQICGQXABHsEnRPABqQMt9A1jjAxwgQFSvCEJbzwR5ApAdQAjDV6yaMLHSY4YECBJ+SxyzjohEiQcXP0Qo074mgSBYEFMpBEJ+fMBAECmigShgkFGhABCmK0shUxGVFQoAYvPFHHKg5udUxGCLRwBiGYtBJMNIxtJRA8vowSTCm9/JILMeLQJiZB5tSCiy7auPimQO5UA41ud/bp550BAQA7");background-repeat:no-repeat;bottom:0;display:inline-block;height:32px;left:0;margin:auto;position:absolute;right:0;top:0;width:32px}#bootstrap-theme .table.table-transparent{background-color:transparent;border:1px solid #d3dee2}#bootstrap-theme .table.table-transparent th{background-color:transparent}#bootstrap-theme .crm_wizard__body, #bootstrap-theme .crm_wizard__title{margin-bottom:30px}#bootstrap-theme .crm_wizard__title .crm_wizard__title__number{border:2px solid;border-radius:50px;display:inline-block;height:32px;line-height:28px;margin-right:5px;text-align:center;width:32px}#bootstrap-theme .crm_wizard__title .nav-pills{padding:0}#bootstrap-theme .crm_wizard__title .nav-pills a, #bootstrap-theme .crm_wizard__title .nav-pills a:active, #bootstrap-theme .crm_wizard__title .nav-pills a:hover{color:#0071bd;padding:15px 20px}#bootstrap-theme .crm_wizard__title .nav-pills li.active a{background:#f3f6f7;color:#0071bd;position:relative}#bootstrap-theme .crm_wizard__title .nav-pills li.active a:after, #bootstrap-theme .crm_wizard__title .nav-pills li.active a:before{border-color:transparent;border-style:solid;border-width:30px 0 30px 8px;bottom:0;content:'';height:100%;position:absolute;top:0;width:0}#bootstrap-theme .crm_wizard__title .nav-pills li.active a:after{border-left-color:#f3f6f7;right:-7px}#bootstrap-theme .crm_wizard__title .nav-pills li.active a:before{border-left-color:#fff;left:0}#bootstrap-theme .crm_wizard__title .nav-pills li.active:first-child a:before{display:none}#bootstrap-theme .crm_wizard__title .nav-pills li:not(.active) a{color:#464354}#bootstrap-theme .crm_wizard__title .nav-pills li:not(.active) a:after, #bootstrap-theme .crm_wizard__title .nav-pills li:not(.active) a:before{background:#f3f6f7;content:'';height:31px;position:absolute;right:-9px;width:1px}#bootstrap-theme .crm_wizard__title .nav-pills li:not(.active) a:after{-webkit-transform:skewX(-15deg);-ms-transform:skewX(-15deg);-moz-transform:skewX(-15deg);transform:skewX(-15deg);bottom:0}#bootstrap-theme .crm_wizard__title .nav-pills li:not(.active) a:before{-webkit-transform:skewX(15deg);-ms-transform:skewX(15deg);-moz-transform:skewX(15deg);transform:skewX(15deg);top:0}#bootstrap-theme .crm_wizard__title .nav-pills li:not(.active) a:hover{background:none;color:#0071bd}#bootstrap-theme .crm_wizard__title .nav-pills li.completed a{color:#00B0B9}#bootstrap-theme .crm_wizard__title .panel-body{padding:0}#bootstrap-theme .crm_wizard__body .form-group, #bootstrap-theme .crm_wizard__body .form-group-lg{max-width:550px}#bootstrap-theme .crm_wizard__body .form-group-lg .form-control{min-width:100%;margin-bottom:15px}#bootstrap-theme .crm_wizard__body .form-control, #bootstrap-theme .crm_wizard__body .form-group .select2-container{max-width:370px}#bootstrap-theme .crm_wizard__body .select2-container{height:auto}#bootstrap-theme .crm_wizard .panel-body{border-radius:2px}#bootstrap-theme .chr_wysiwyg{border-bottom:none !important}#bootstrap-theme .chr_wysiwyg [text-angular-toolbar] .btn[disabled]{opacity:1}#bootstrap-theme .chr_wysiwyg .placeholder-text{opacity:0.6}#bootstrap-theme .chr_wysiwyg__action{background-color:#fff;border:1px solid #e8eef0;border-top:none;padding:5px 0;text-align:right}#bootstrap-theme .chr_wysiwyg__action hr{margin:0 10px}#bootstrap-theme .chr_wysiwyg__action .btn-link{margin-top:5px}#bootstrap-theme .chr_wysiwyg__action .btn-link[disabled]{opacity:0.6}#bootstrap-theme .chr_wysiwyg__action .fa{margin-right:5px}#bootstrap-theme .crm_collapsible-content{padding:10px}#bootstrap-theme .crm_collapsible-content .crm_collapsible-content__title{margin-bottom:10px;text-transform:uppercase}#bootstrap-theme .crm_collapsible-content .crm_collapsible-content__title .fa{color:#464354;margin-right:10px}#bootstrap-theme .crm_notification-badge{background:#0071bd;border-radius:2px;color:#fff;display:inline-block;font-size:13px;line-height:14px;padding:5px 8px;text-align:center;vertical-align:middle;white-space:nowrap}#bootstrap-theme .crm_notification-badge:hover{color:#fff;opacity:0.8;text-decoration:none}#bootstrap-theme .crm_notification-badge__info{background:#0071bd;color:#fff}#bootstrap-theme .crm_notification-badge__success{background:#44cb7e;color:#464354}#bootstrap-theme .crm_notification-badge__warning{background:#e6ab5e;color:#464354}#bootstrap-theme .crm_notification-badge__danger{background:#cf3458;color:#fff}#bootstrap-theme .pointer{cursor:pointer !important} .crm-container.ui-dialog{z-index:2001} .ui-widget-overlay.ui-front{z-index:2000} .mobile [type='date'][uib-datepicker-popup]{line-height:normal} .mobile [type='date'][uib-datepicker-popup]::-webkit-inner-spin-button, .mobile [type='date'][uib-datepicker-popup]::-webkit-clear-button{-webkit-appearance:none;appearance:none;display:none} .mobile [type='date'][uib-datepicker-popup]::-webkit-calendar-picker-indicator{background:transparent;bottom:0;color:transparent;height:auto;left:0;position:absolute;right:-50px;top:0;width:auto} .mobile [type='date'][uib-datepicker-popup]+.input-group-addon{border-left:1px solid #c2cfd8 !important;height:30px !important;line-height:30px !important;padding:0 !important;pointer-events:none;position:absolute !important;right:0 !important;width:38px !important;z-index:3}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/shoreditch/dropdown.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/shoreditch/dropdown.js
new file mode 100644
index 00000000..4ded8501
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/lib/shoreditch/dropdown.js
@@ -0,0 +1,165 @@
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.4.1
+ * https://getbootstrap.com/docs/3.4/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2011-2019 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // DROPDOWN CLASS DEFINITION
+ // =========================
+
+ var backdrop = '.dropdown-backdrop'
+ var toggle = '[data-toggle="dropdown"]'
+ var Dropdown = function (element) {
+ $(element).on('click.bs.dropdown', this.toggle)
+ }
+
+ Dropdown.VERSION = '3.4.1'
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = selector !== '#' ? $(document).find(selector) : null
+
+ return $parent && $parent.length ? $parent : $this.parent()
+ }
+
+ function clearMenus(e) {
+ if (e && e.which === 3) return
+ $(backdrop).remove()
+ $(toggle).each(function () {
+ var $this = $(this)
+ var $parent = getParent($this)
+ var relatedTarget = { relatedTarget: this }
+
+ if (!$parent.hasClass('open')) return
+
+ if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
+
+ $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this.attr('aria-expanded', 'false')
+ $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
+ })
+ }
+
+ Dropdown.prototype.toggle = function (e) {
+ var $this = $(this)
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+ // if mobile we use a backdrop because click events don't delegate
+ $(document.createElement('div'))
+ .addClass('dropdown-backdrop')
+ .insertAfter($(this))
+ .on('click', clearMenus)
+ }
+
+ var relatedTarget = { relatedTarget: this }
+ $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this
+ .trigger('focus')
+ .attr('aria-expanded', 'true')
+
+ $parent
+ .toggleClass('open')
+ .trigger($.Event('shown.bs.dropdown', relatedTarget))
+ }
+
+ return false
+ }
+
+ Dropdown.prototype.keydown = function (e) {
+ if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
+
+ var $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ if (!isActive && e.which != 27 || isActive && e.which == 27) {
+ if (e.which == 27) $parent.find(toggle).trigger('focus')
+ return $this.trigger('click')
+ }
+
+ var desc = ' li:not(.disabled):visible a'
+ var $items = $parent.find('.dropdown-menu' + desc)
+
+ if (!$items.length) return
+
+ var index = $items.index(e.target)
+
+ if (e.which == 38 && index > 0) index-- // up
+ if (e.which == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items.eq(index).trigger('focus')
+ }
+
+
+ // DROPDOWN PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.dropdown')
+
+ if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ var old = $.fn.dropdown
+
+ $.fn.dropdown = Plugin
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ // DROPDOWN NO CONFLICT
+ // ====================
+
+ $.fn.dropdown.noConflict = function () {
+ $.fn.dropdown = old
+ return this
+ }
+
+
+ // APPLY TO STANDARD DROPDOWN ELEMENTS
+ // ===================================
+
+ $(document)
+ .on('click.bs.dropdown.data-api', clearMenus)
+ .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+ .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
+ .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
+
+}(jQuery);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/phpunit.xml.dist b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/phpunit.xml.dist
new file mode 100644
index 00000000..bf600a87
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/phpunit.xml.dist
@@ -0,0 +1,29 @@
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false"
+ bootstrap="tests/phpunit/bootstrap.php"
+>
+ <testsuites>
+ <testsuite name="My Test Suite">
+ <directory>./tests/phpunit</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory suffix=".php">./</directory>
+ </whitelist>
+ </filter>
+
+ <listeners>
+ <listener class="Civi\Test\CiviTestListener">
+ <arguments></arguments>
+ </listener>
+ </listeners>
+</phpunit>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/readme.md b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/readme.md
new file mode 100644
index 00000000..831b7f73
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/readme.md
@@ -0,0 +1,120 @@
+CiviCRM API Version 4
+=====================
+
+Welcome
+-------
+
+This is the latest version of the API (Application Programming Interface) for CiviCRM. If you are here because you're trying to install an extension that requires this, just install this and you're done!
+
+If you are a developer, read on...
+
+Using Api4
+----------
+
+Once installed you can navigate to **Support -> Developer -> Api4 Explorer** in the menu. This gives a live, interactive code generator in which you can build and test api calls:
+
+![Screenshot](/images/ApiExplorer.png)
+
+Output
+------
+
+The php binding returns an [arrayObject](http://php.net/manual/en/class.arrayobject.php). This gives immediate access to the results, plus allows returning additional metadata properties.
+
+
+```php
+$result = \Civi\Api4\Contact::get()->execute();
+
+// you can loop through the results directly
+foreach ($result as $contact) {}
+
+// you can just grab the first one
+$contact1 = $result->first();
+
+// reindex results on-the-fly (replacement for sequential=1 in v3)
+$result->indexBy('id');
+
+// or fetch some metadata about the call
+$entity = $result->entity; // "Contact"
+```
+
+We can do the something very similar in javascript thanks to js arrays also being objects:
+
+```javascript
+CRM.api4('Contact', 'get', params).then(function(result) {
+ // you can loop through the results
+ result.forEach(function(contact, n) {});
+
+ // you can just grab the first one
+ var contact1 = result[0];
+
+ // or fetch some metadata about the call
+ var entity = result.entity; // "Contact"
+});
+```
+
+Notable changes from Version 3:
+-------------------------------
+
+* **Api wrapper**
+ - In addition to the familiar style of `civicrm_api4('Entity', 'action', $params)` there is now an OO style in php `\Civi\Api4\Entity::action()`.
+ - When chaining api calls together, backreferences to values from the main api call must be explicitly given (discoverable in the api explorer).
+ - `$checkPermissions` always defaults to `TRUE`. In api3 it defaulted to `TRUE` in REST/Javascript but `FALSE` in php.
+ - A 4th param `index` controls how results are returned:
+ Passing a string will index all results by that key e.g. `civicrm_api4('Contact', 'get', $params, 'id')` will index by id.
+ Passing a number will return the result at that index e.g. `civicrm_api4('Contact', 'get', $params, 0)` will return the first result and is the same as `\Civi\Api4\Contact::get()->execute()->first()`. `-1` is the equivalent of `$result->last()`.
+* **Actions**
+ - `Get` no longer sets a default limit of 25 outside the api explorer.
+ - Use the `Update` action to update an entity rather than `Create` with an id.
+ - `Update` and `Delete` can be performed on multiple items at once by specifying a `where` clause, vs a single item by id in api3.
+ - `getsingle` is gone, use `$result->first()` or `index` `0`.
+ - `getoptions` is no longer a standalone action, but part of `getFields`.
+* **Input**
+ - Instead of a single `$params` array containing a mishmash of fields, options and parameters, each api4 parameters is distinct.
+ e.g. for the `Get` action: `select`, `where`, `orderBy` and `limit` are different params.
+ - Custom fields are refered to by name rather than id. E.g. use `constituent_information.Most_Important_Issue` instead of `custom_4`.
+* **Output**
+ - Output is an array with object properties rather than a nested array.
+ - In PHP, you can `foreach` the results arrayObject directly, or you can call methods on it like `$result->first()` or `$result->indexBy('foo')`.
+ - By default, results are indexed sequentially like api3 `sequential => 1`, but the `index` param or `indexBy()` method let you change that.
+ e.g. `civicrm_api4('Contact', 'get', $params, 'id')` or `\Civi\Api4\Contact::get()->execute()->indexBy('id')` will index results by id.
+
+Security
+--------
+
+Each `action` object has a `$checkPermissions` property. This always defaults to `TRUE`, and for calls from REST it cannot be disabled.
+
+Architecture
+------------
+
+* An [**Entity**](Civi/Api4/Generic/AbstractEntity.php) is a class implementing one or more static methods (`get()`, `create()`, `delete()`, etc).
+* Each static method constructs and returns an [**Action object**](Civi/Api4/Generic/AbstractAction.php).
+* All actions extend the [AbstractAction class](Civi/Api4/Generic/AbstractAction.php). A number of other abstract action classes build on this, e.g. [AbstractBatchAction](Civi/Api4/Generic/AbstractBatchAction.php) is the base class for batch-process actions (`delete`, `update`, `replace`).
+* Most entity classes correspond to a `CRM_Core_DAO` subclass. E.g. `Civi\Api4\Contact` corresponds to `CRM_Contact_DAO_Contact`.
+* A set of **`DAO` action classes** (e.g. [DAOGetAction](Civi/Api4/Generic/DAOGetAction.php), [DAODeleteAction](Civi/Api4/Generic/DAODeleteAction.php)) exists to support DAO-based entities. [DAOGetAction](Civi/Api4/Generic/DAOGetAction.php) uses [`Api4SelectQuery`](Civi/API/Api4SelectQuery.php) to query the database.
+* A set of **`Basic` action classes** (e.g. [BasicGetAction](Civi/Api4/Generic/BasicGetAction.php), [BasicBatchAction](Civi/Api4/Generic/BasicBatchAction.php)) exists to support many other use-cases, e.g. file-based entities.
+* The base action `execute()` method calls the core [`civi_api_kernel`](https://github.com/civicrm/civicrm-core/blob/master/Civi/API/Kernel.php)
+service `runRequest()` method which invokes hooks and then calls the `_run` method for that action.
+* Each action object has a `_run()` method that accepts and updates a [`Result`](Civi/Api4/Generic/Result.php) object (which is an extended [ArrayObject](http://php.net/manual/en/class.arrayobject.php)).
+
+Extending Api4
+--------------
+
+#### Modifying an existing entity/action:
+
+To alter the behavior of an existing entiy action, use [hook_civicrm_apiWrappers](https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_apiWrappers).
+
+#### Adding an action to an existing entity:
+
+Create a class which extends a generic action class (see below). Give it the same namespace and file location as other actions for that entity. It will be picked up automatically by api4's getActions file scanner.
+
+#### Adding a new api entity:
+
+If your entity has a database table and DAO, simply add a class to the `Civi/Api4` directory of your extension. Give the file and class the same name as your entity, and extend the [DAOEntity class](Civi/Api4/Generic/DAOEntity.php).
+
+For specialty apis, try the `BasicGet`, `BasicCreate`, `BasicUpdate`, `BasicBatch` and `BasicReplace` actions as in [this example](tests/phpunit/Mock/Api4/MockBasicEntity.php).
+
+Tests
+-----
+
+Tests are located in the `tests` directory (surprise!)
+To run the entire Api4 test suite go to the api4 extension directory and type `phpunit4` from the command line.
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/services.xml b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/services.xml
new file mode 100644
index 00000000..24d0b061
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/services.xml
@@ -0,0 +1,122 @@
+<container xmlns="http://symfony.com/schema/dic/services"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+
+ <services>
+
+ <service id="spec_gatherer" class="Civi\Api4\Service\Spec\SpecGatherer"/>
+
+ <service id="schema_map_builder" class="Civi\Api4\Service\Schema\SchemaMapBuilder" public="false">
+ <argument type="service" id="dispatcher" />
+ </service>
+
+ <service id="schema_map" class="Civi\Api4\Service\Schema\SchemaMap">
+ <factory service="schema_map_builder" method="build"/>
+ </service>
+
+ <service id="joiner" class="Civi\Api4\Service\Schema\Joiner">
+ <argument type="service" id="schema_map"/>
+ </service>
+
+ <service id="action_object_provider" class="Civi\Api4\Provider\ActionObjectProvider">
+ <tag name="event_subscriber"/>
+ </service>
+
+ <service id="contact_creation.spec_provider" class="Civi\Api4\Service\Spec\Provider\ContactCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\ContactTypeCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\AddressCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service id="option_value_creation.spec_provider" class="Civi\Api4\Service\Spec\Provider\OptionValueCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\ActivityCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\ActionScheduleCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\EmailCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\PhoneCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\EventCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\NoteCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\ContributionCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\CustomGroupCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\GroupCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\NavigationCreationSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ <service id="custom_group.pre_creation.subscriber" class="Civi\Api4\Event\Subscriber\CustomGroupPreCreationSubscriber">
+ <tag name="event_subscriber"/>
+ </service>
+
+ <service id="custom_field.pre_creation.subsciber" class="Civi\Api4\Event\Subscriber\CustomFieldPreCreationSubscriber">
+ <tag name="event_subscriber"/>
+ </service>
+
+ <service id="option_value.pre_creation.subscriber" class="Civi\Api4\Event\Subscriber\OptionValuePreCreationSubscriber">
+ <tag name="event_subscriber"/>
+ </service>
+
+ <service id="activity.pre_creation.subscriber" class="Civi\Api4\Event\Subscriber\ActivityPreCreationSubscriber">
+ <tag name="event_subscriber"/>
+ </service>
+
+ <service id="contact.schema_map.subscriber" class="Civi\Api4\Event\Subscriber\ContactSchemaMapSubscriber">
+ <tag name="event_subscriber"/>
+ </service>
+
+ <service id="activity.schema_map.subscriber" class="Civi\Api4\Event\Subscriber\ActivitySchemaMapSubscriber">
+ <tag name="event_subscriber"/>
+ </service>
+
+ <service id="api4.permission_check.subscriber" class="Civi\Api4\Event\Subscriber\PermissionCheckSubscriber">
+ <tag name="event_subscriber"/>
+ </service>
+
+ <service id="api4.required_fields.subscriber" class="Civi\Api4\Event\Subscriber\ValidateFieldsSubscriber">
+ <tag name="event_subscriber"/>
+ </service>
+
+ <service id="api4.post_select_query.subscriber" class="Civi\Api4\Event\Subscriber\PostSelectQuerySubscriber">
+ <tag name="event_subscriber"/>
+ </service>
+
+ <service class="Civi\Api4\Service\Spec\Provider\CustomValueSpecProvider">
+ <tag name="spec_provider"/>
+ </service>
+
+ </services>
+</container>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/templates/CRM/Api4/Page/Api4Explorer.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/templates/CRM/Api4/Page/Api4Explorer.tpl
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/templates/CRM/Api4/Page/Api4Explorer.tpl
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BaseCustomValueTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BaseCustomValueTest.php
new file mode 100644
index 00000000..41fe7281
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BaseCustomValueTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Test\Api4\UnitTestCase;
+use Civi\Test\Api4\Traits\TableDropperTrait;
+
+abstract class BaseCustomValueTest extends UnitTestCase {
+
+ use \Civi\Test\Api4\Traits\OptionCleanupTrait {
+ setUp as setUpOptionCleanup;
+ }
+ use TableDropperTrait;
+
+ /**
+ * Set up baseline for testing
+ */
+ public function setUp() {
+ $this->setUpOptionCleanup();
+ $cleanup_params = [
+ 'tablesToTruncate' => [
+ 'civicrm_custom_group',
+ 'civicrm_custom_field',
+ ],
+ ];
+
+ $this->dropByPrefix('civicrm_value_mycontact');
+ $this->cleanup($cleanup_params);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicActionsTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicActionsTest.php
new file mode 100644
index 00000000..29523389
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicActionsTest.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Test\Api4\UnitTestCase;
+use Civi\Api4\MockBasicEntity;
+
+/**
+ * @group headless
+ */
+class BasicActionsTest extends UnitTestCase {
+
+ public function testCrud() {
+ MockBasicEntity::delete()->addWhere('id', '>', 0)->execute();
+
+ $id1 = MockBasicEntity::create()->addValue('foo', 'one')->execute()->first()['id'];
+
+ $result = MockBasicEntity::get()->execute();
+ $this->assertCount(1, $result);
+
+ $id2 = MockBasicEntity::create()->addValue('foo', 'two')->execute()->first()['id'];
+
+ $result = MockBasicEntity::get()->execute();
+ $this->assertCount(2, $result);
+
+ MockBasicEntity::update()->addWhere('id', '=', $id2)->addValue('foo', 'new')->execute();
+
+ $result = MockBasicEntity::get()->addOrderBy('id', 'DESC')->setLimit(1)->execute();
+ $this->assertCount(1, $result);
+ $this->assertEquals('new', $result->first()['foo']);
+
+ MockBasicEntity::delete()->addWhere('id', '=', $id2);
+ $result = MockBasicEntity::get()->execute();
+ $this->assertEquals('one', $result->first()['foo']);
+ }
+
+ public function testReplace() {
+ MockBasicEntity::delete()->addWhere('id', '>', 0)->execute();
+
+ $objects = [
+ ['group' => 'one', 'color' => 'red'],
+ ['group' => 'one', 'color' => 'blue'],
+ ['group' => 'one', 'color' => 'green'],
+ ['group' => 'two', 'color' => 'orange'],
+ ];
+
+ foreach ($objects as &$object) {
+ $object['id'] = MockBasicEntity::create()->setValues($object)->execute()->first()['id'];
+ }
+
+ // Keep red, change blue, delete green, and add yellow
+ $replacements = [
+ ['color' => 'red', 'id' => $objects[0]['id']],
+ ['color' => 'not blue', 'id' => $objects[1]['id']],
+ ['color' => 'yellow']
+ ];
+
+ MockBasicEntity::replace()->addWhere('group', '=', 'one')->setRecords($replacements)->execute();
+
+ $newObjects = MockBasicEntity::get()->addOrderBy('id', 'DESC')->execute()->indexBy('id');
+
+ $this->assertCount(4, $newObjects);
+
+ $this->assertEquals('yellow', $newObjects->first()['color']);
+
+ $this->assertEquals('not blue', $newObjects[$objects[1]['id']]['color']);
+
+ // Ensure group two hasn't been altered
+ $this->assertEquals('orange', $newObjects[$objects[3]['id']]['color']);
+ $this->assertEquals('two', $newObjects[$objects[3]['id']]['group']);
+ }
+
+ public function testBatchFrobnicate() {
+ MockBasicEntity::delete()->addWhere('id', '>', 0)->execute();
+
+ $objects = [
+ ['group' => 'one', 'color' => 'red', 'number' => 10],
+ ['group' => 'one', 'color' => 'blue', 'number' => 20],
+ ['group' => 'one', 'color' => 'green', 'number' => 30],
+ ['group' => 'two', 'color' => 'blue', 'number' => 40],
+ ];
+ foreach ($objects as &$object) {
+ $object['id'] = MockBasicEntity::create()->setValues($object)->execute()->first()['id'];
+ }
+
+ $result = MockBasicEntity::batchFrobnicate()->addWhere('color', '=', 'blue')->execute();
+ $this->assertEquals(2, count($result));
+ $this->assertEquals([400, 1600], \CRM_Utils_Array::collect('frobnication', (array) $result));
+ }
+
+ public function testGetFields() {
+ $getFields = MockBasicEntity::getFields()->execute()->indexBy('name');
+
+ $this->assertCount(6, $getFields);
+ $this->assertEquals('Id', $getFields['id']['title']);
+ // Ensure default data type is "String" when not specified
+ $this->assertEquals('String', $getFields['color']['data_type']);
+
+ // Getfields should default to loadOptions = false and reduce them to bool
+ $this->assertTrue($getFields['group']['options']);
+ $this->assertFalse($getFields['id']['options']);
+
+ // Now load options
+ $getFields = MockBasicEntity::getFields()
+ ->addWhere('name', '=', 'group')
+ ->setLoadOptions(TRUE)
+ ->execute()->indexBy('name');
+
+ $this->assertCount(1, $getFields);
+ $this->assertArrayHasKey('one', $getFields['group']['options']);
+ }
+
+ public function testItemsToGet() {
+ $get = MockBasicEntity::get()
+ ->addWhere('color', 'NOT IN', ['yellow'])
+ ->addWhere('color', 'IN', ['red', 'blue'])
+ ->addWhere('color', '!=', 'green')
+ ->addWhere('group', '=', 'one');
+
+ $this->assertEquals(['red', 'blue'], $get->_itemsToGet('color'));
+ $this->assertEquals(['one'], $get->_itemsToGet('group'));
+ }
+
+ public function testFieldsToGet() {
+ $get = MockBasicEntity::get()
+ ->addWhere('color', '!=', 'green');
+
+ // If no "select" is set, should always return true
+ $this->assertTrue($get->_isFieldSelected('color'));
+ $this->assertTrue($get->_isFieldSelected('shape'));
+ $this->assertTrue($get->_isFieldSelected('size'));
+
+ // With a non-empty "select" fieldsToSelect() will return fields needed to evaluate each clause.
+ $get->addSelect('id');
+ $this->assertTrue($get->_isFieldSelected('color'));
+ $this->assertTrue($get->_isFieldSelected('id'));
+ $this->assertFalse($get->_isFieldSelected('shape'));
+ $this->assertFalse($get->_isFieldSelected('size'));
+ $this->assertFalse($get->_isFieldSelected('weight'));
+ $this->assertFalse($get->_isFieldSelected('group'));
+
+ $get->addClause('OR', ['shape', '=', 'round'], ['AND', [['size', '=', 'big'], ['weight', '!=', 'small']]]);
+ $this->assertTrue($get->_isFieldSelected('color'));
+ $this->assertTrue($get->_isFieldSelected('id'));
+ $this->assertTrue($get->_isFieldSelected('shape'));
+ $this->assertTrue($get->_isFieldSelected('size'));
+ $this->assertTrue($get->_isFieldSelected('weight'));
+ $this->assertFalse($get->_isFieldSelected('group'));
+
+ $get->addOrderBy('group');
+ $this->assertTrue($get->_isFieldSelected('group'));
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicCustomFieldTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicCustomFieldTest.php
new file mode 100644
index 00000000..85687a08
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/BasicCustomFieldTest.php
@@ -0,0 +1,187 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\Contact;
+use Civi\Api4\CustomField;
+use Civi\Api4\CustomGroup;
+
+/**
+ * @group headless
+ */
+class BasicCustomFieldTest extends BaseCustomValueTest {
+
+ public function testWithSingleField() {
+
+ $customGroup = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', 'MyContactFields')
+ ->addValue('extends', 'Contact')
+ ->execute()
+ ->first();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'FavColor')
+ ->addValue('custom_group_id', $customGroup['id'])
+ ->addValue('html_type', 'Text')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ $contactId = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Johann')
+ ->addValue('last_name', 'Tester')
+ ->addValue('contact_type', 'Individual')
+ ->addValue('MyContactFields.FavColor', 'Red')
+ ->execute()
+ ->first()['id'];
+
+ $contact = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('first_name')
+ ->addSelect('MyContactFields.FavColor')
+ ->addWhere('id', '=', $contactId)
+ ->addWhere('MyContactFields.FavColor', '=', 'Red')
+ ->execute()
+ ->first();
+
+ $this->assertArrayHasKey('MyContactFields', $contact);
+ $contactFields = $contact['MyContactFields'];
+ $this->assertArrayHasKey('FavColor', $contactFields);
+ $this->assertEquals('Red', $contactFields['FavColor']);
+
+ Contact::update()
+ ->addWhere('id', '=', $contactId)
+ ->addValue('MyContactFields.FavColor', 'Blue')
+ ->execute();
+
+ $contact = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('MyContactFields.FavColor')
+ ->addWhere('id', '=', $contactId)
+ ->execute()
+ ->first();
+
+ $contactFields = $contact['MyContactFields'];
+ $this->assertEquals('Blue', $contactFields['FavColor']);
+ }
+
+ public function testWithTwoFields() {
+
+ $customGroup = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', 'MyContactFields')
+ ->addValue('extends', 'Contact')
+ ->execute()
+ ->first();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'FavColor')
+ ->addValue('custom_group_id', $customGroup['id'])
+ ->addValue('html_type', 'Text')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'FavFood')
+ ->addValue('custom_group_id', $customGroup['id'])
+ ->addValue('html_type', 'Text')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ $contactId1 = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Johann')
+ ->addValue('last_name', 'Tester')
+ ->addValue('MyContactFields.FavColor', 'Red')
+ ->addValue('MyContactFields.FavFood', 'Cherry')
+ ->execute()
+ ->first()['id'];
+
+ $contactId2 = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'MaryLou')
+ ->addValue('last_name', 'Tester')
+ ->addValue('MyContactFields.FavColor', 'Purple')
+ ->addValue('MyContactFields.FavFood', 'Grapes')
+ ->execute()
+ ->first()['id'];
+
+ $contact = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('first_name')
+ ->addSelect('MyContactFields.FavColor')
+ ->addSelect('MyContactFields.FavFood')
+ ->addWhere('id', '=', $contactId1)
+ ->addWhere('MyContactFields.FavColor', '=', 'Red')
+ ->addWhere('MyContactFields.FavFood', '=', 'Cherry')
+ ->execute()
+ ->first();
+
+ $this->assertArrayHasKey('MyContactFields', $contact);
+ $contactFields = $contact['MyContactFields'];
+ $this->assertArrayHasKey('FavColor', $contactFields);
+ $this->assertEquals('Red', $contactFields['FavColor']);
+
+ Contact::update()
+ ->addWhere('id', '=', $contactId1)
+ ->addValue('MyContactFields.FavColor', 'Blue')
+ ->execute();
+
+ $contact = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('MyContactFields.FavColor')
+ ->addWhere('id', '=', $contactId1)
+ ->execute()
+ ->first();
+
+ $contactFields = $contact['MyContactFields'];
+ $this->assertEquals('Blue', $contactFields['FavColor']);
+
+ $search = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addClause('OR', ['MyContactFields.FavColor', '=', 'Blue'], ['MyContactFields.FavFood', '=', 'Grapes'])
+ ->addSelect('id')
+ ->addOrderBy('id')
+ ->execute()
+ ->indexBy('id');
+
+ $this->assertEquals([$contactId1, $contactId2], array_keys((array) $search));
+
+ $search = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addClause('NOT', ['MyContactFields.FavColor', '=', 'Purple'], ['MyContactFields.FavFood', '=', 'Grapes'])
+ ->addSelect('id')
+ ->addOrderBy('id')
+ ->execute()
+ ->indexBy('id');
+
+ $this->assertNotContains($contactId2, array_keys((array) $search));
+
+ $search = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addClause('NOT', ['MyContactFields.FavColor', '=', 'Purple'], ['MyContactFields.FavFood', '=', 'Grapes'])
+ ->addSelect('id')
+ ->addOrderBy('id')
+ ->execute()
+ ->indexBy('id');
+
+ $this->assertContains($contactId1, array_keys((array) $search));
+ $this->assertNotContains($contactId2, array_keys((array) $search));
+
+ $search = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->setWhere([['NOT', ['OR', [['MyContactFields.FavColor', '=', 'Blue'], ['MyContactFields.FavFood', '=', 'Grapes']]]]])
+ ->addSelect('id')
+ ->addOrderBy('id')
+ ->execute()
+ ->indexBy('id');
+
+ $this->assertNotContains($contactId1, array_keys((array) $search));
+ $this->assertNotContains($contactId2, array_keys((array) $search));
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ChainTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ChainTest.php
new file mode 100644
index 00000000..bbd6a092
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ChainTest.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class ChainTest extends UnitTestCase {
+
+ public function testGetActionsWithFields() {
+ $actions = \Civi\Api4\Activity::getActions()
+ ->addChain('fields', \Civi\Api4\Activity::getFields()->setAction('$name'), 'name')
+ ->execute()
+ ->indexBy('name');
+
+ $this->assertEquals('Array', $actions['getActions']['fields']['params']['data_type']);
+ }
+
+ public function testGetEntityWithActions() {
+ $entities = \Civi\Api4\Entity::get()
+ ->addSelect('name')
+ ->setChain([
+ 'actions' => ['$name', 'getActions', ['select' => ['name']], 'name']
+ ])
+ ->execute()
+ ->indexBy('name');
+
+ $this->assertArrayHasKey('replace', $entities['Contact']['actions']);
+ $this->assertArrayHasKey('getLinks', $entities['Entity']['actions']);
+ $this->assertArrayNotHasKey('replace', $entities['Entity']['actions']);
+ }
+
+ public function testContactCreateWithGroup() {
+ $firstName = uniqid('cwtf');
+ $lastName = uniqid('cwtl');
+
+ $contact = \Civi\Api4\Contact::create()
+ ->addValue('first_name', $firstName)
+ ->addValue('last_name', $lastName)
+ ->addChain('group', \Civi\Api4\Group::create()->addValue('title', '$display_name'), 0)
+ ->addChain('add_to_group', \Civi\Api4\GroupContact::create()->addValue('contact_id', '$id')->addValue('group_id', '$group.id'), 0)
+ ->addChain('check_group', \Civi\Api4\GroupContact::get()->addWhere('group_id', '=', '$group.id'))
+ ->execute()
+ ->first();
+
+ $this->assertCount(1, $contact['check_group']);
+ $this->assertEquals($contact['id'], $contact['check_group'][0]['contact_id']);
+ $this->assertEquals($contact['group']['id'], $contact['check_group'][0]['group_id']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ComplexQueryTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ComplexQueryTest.php
new file mode 100644
index 00000000..0a320100
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ComplexQueryTest.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Test\Api4\UnitTestCase;
+use Civi\Api4\Activity;
+
+/**
+ * @group headless
+ *
+ * This class tests a series of complex query situations described in the
+ * initial APIv4 specification
+ */
+class ComplexQueryTest extends UnitTestCase {
+
+ public function setUpHeadless() {
+ $relatedTables = [
+ 'civicrm_activity',
+ 'civicrm_activity_contact',
+ ];
+ $this->cleanup(['tablesToTruncate' => $relatedTables]);
+ $this->loadDataSet('DefaultDataSet');
+
+ return parent::setUpHeadless();
+ }
+
+ /**
+ * Fetch all phone call activities
+ * Expects at least one activity loaded from the data set.
+ */
+ public function testGetAllHousingSupportActivities() {
+ $results = Activity::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('activity_type.name', '=', 'Phone Call')
+ ->execute();
+
+ $this->assertGreaterThan(0, count($results));
+ }
+
+ /**
+ * Fetch all activities with a blue tag; and return all tags on the activities
+ */
+ public function testGetAllTagsForBlueTaggedActivities() {
+
+ }
+
+ /**
+ * Fetch contacts named 'Bob' and all of their blue activities
+ */
+ public function testGetAllBlueActivitiesForBobs() {
+
+ }
+
+ /**
+ * Get all contacts in a zipcode and return their Home or Work email addresses
+ */
+ public function testGetHomeOrWorkEmailsForContactsWithZipcode() {
+
+ }
+
+ /**
+ * Fetch all activities where Bob is the assignee or source
+ */
+ public function testGetActivitiesWithBobAsAssigneeOrSource() {
+
+ }
+
+ /**
+ * Get all contacts which
+ * (a) have address in zipcode 94117 or 94118 or in city "San Francisco","LA"
+ * and
+ * (b) are not deceased and
+ * (c) have a custom-field "most_important_issue=Environment".
+ */
+ public function testAWholeLotOfConditions() {
+
+ }
+
+ /**
+ * Get participants who attended CiviCon 2012 but not CiviCon 2013.
+ * Return their name and email.
+ */
+ public function testGettingNameAndEmailOfAttendeesOfCiviCon2012Only() {
+
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ContactApiKeyTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ContactApiKeyTest.php
new file mode 100644
index 00000000..63d85d55
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ContactApiKeyTest.php
@@ -0,0 +1,170 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\Contact;
+
+/**
+ * @group headless
+ */
+class ContactApiKeyTest extends \Civi\Test\Api4\UnitTestCase {
+
+ public function testGetApiKey() {
+ \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM'];
+ $key = uniqid();
+
+ $contact = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Api')
+ ->addValue('last_name', 'Key0')
+ ->addValue('api_key', $key)
+ ->execute()
+ ->first();
+
+ $result = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('id', '=', $contact['id'])
+ ->addSelect('api_key')
+ ->execute()
+ ->first();
+
+ $this->assertEquals($result['api_key'], $key);
+
+ $result = Contact::get()
+ ->addWhere('id', '=', $contact['id'])
+ ->addSelect('api_key')
+ ->execute()
+ ->first();
+
+ $this->assertTrue(empty($result['api_key']));
+ }
+
+ public function testCreateWithApiKey() {
+ \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'add contacts'];
+ $key = uniqid();
+
+ $error = '';
+ try {
+ Contact::create()
+ ->addValue('first_name', 'Api')
+ ->addValue('last_name', 'Key1')
+ ->addValue('api_key', $key)
+ ->execute()
+ ->first();
+ }
+ catch (\Exception $e) {
+ $error = $e->getMessage();
+ }
+ $this->assertContains('key', $error);
+ }
+
+ public function testUpdateApiKey() {
+ \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM'];
+ $key = uniqid();
+
+ $contact = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Api')
+ ->addValue('last_name', 'Key2')
+ ->addValue('api_key', $key)
+ ->execute()
+ ->first();
+
+ $error = '';
+ try {
+ // Try to update the key without permissions; nothing should happen
+ Contact::update()
+ ->addWhere('id', '=', $contact['id'])
+ ->addValue('api_key', "NotAllowed")
+ ->execute();
+ }
+ catch (\Exception $e) {
+ $error = $e->getMessage();
+ }
+
+ $result = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('id', '=', $contact['id'])
+ ->addSelect('api_key')
+ ->execute()
+ ->first();
+
+ $this->assertContains('key', $error);
+
+ // Assert key is still the same
+ $this->assertEquals($result['api_key'], $key);
+
+ // Now we can update the key
+ \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'administer CiviCRM', 'edit all contacts'];
+
+ Contact::update()
+ ->addWhere('id', '=', $contact['id'])
+ ->addValue('api_key', "IGotThePower!")
+ ->execute();
+
+ $result = Contact::get()
+ ->addWhere('id', '=', $contact['id'])
+ ->addSelect('api_key')
+ ->execute()
+ ->first();
+
+ // Assert key was updated
+ $this->assertEquals($result['api_key'], "IGotThePower!");
+ }
+
+ public function testUpdateOwnApiKey() {
+ \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'edit own api keys', 'edit my contact'];
+ $key = uniqid();
+
+ $contact = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Api')
+ ->addValue('last_name', 'Key3')
+ ->addValue('api_key', $key)
+ ->execute()
+ ->first();
+
+ $error = '';
+ try {
+ // Try to update the key without permissions; nothing should happen
+ Contact::update()
+ ->addWhere('id', '=', $contact['id'])
+ ->addValue('api_key', "NotAllowed")
+ ->execute();
+ }
+ catch (\Exception $e) {
+ $error = $e->getMessage();
+ }
+
+ $this->assertContains('key', $error);
+
+ $result = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('id', '=', $contact['id'])
+ ->addSelect('api_key')
+ ->execute()
+ ->first();
+
+ // Assert key is still the same
+ $this->assertEquals($result['api_key'], $key);
+
+ // Now we can update the key
+ \CRM_Core_Session::singleton()->set('userID', $contact['id']);
+
+ Contact::update()
+ ->addWhere('id', '=', $contact['id'])
+ ->addValue('api_key', "MyId!")
+ ->execute();
+
+ $result = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('id', '=', $contact['id'])
+ ->addSelect('api_key')
+ ->execute()
+ ->first();
+
+ // Assert key was updated
+ $this->assertEquals($result['api_key'], "MyId!");
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateCustomValueTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateCustomValueTest.php
new file mode 100644
index 00000000..762b4617
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateCustomValueTest.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\CustomField;
+use Civi\Api4\CustomGroup;
+use Civi\Api4\OptionGroup;
+use Civi\Api4\OptionValue;
+
+/**
+ * @group headless
+ */
+class CreateCustomValueTest extends BaseCustomValueTest {
+
+ public function testGetWithCustomData() {
+ $optionValues = ['r' => 'Red', 'g' => 'Green', 'b' => 'Blue'];
+
+ $customGroup = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', 'MyContactFields')
+ ->addValue('extends', 'Contact')
+ ->execute()
+ ->first();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'Color')
+ ->addValue('options', $optionValues)
+ ->addValue('custom_group_id', $customGroup['id'])
+ ->addValue('html_type', 'Select')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ $customField = CustomField::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('label', '=', 'Color')
+ ->execute()
+ ->first();
+
+ $this->assertNotNull($customField['option_group_id']);
+ $optionGroupId = $customField['option_group_id'];
+
+ $optionGroup = OptionGroup::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('id', '=', $optionGroupId)
+ ->execute()
+ ->first();
+
+ $this->assertEquals('Color', $optionGroup['title']);
+
+ $createdOptionValues = OptionValue::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('option_group_id', '=', $optionGroupId)
+ ->execute()
+ ->getArrayCopy();
+
+ $values = array_column($createdOptionValues, 'value');
+ $labels = array_column($createdOptionValues, 'label');
+ $createdOptionValues = array_combine($values, $labels);
+
+ $this->assertEquals($optionValues, $createdOptionValues);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateWithOptionGroupTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateWithOptionGroupTest.php
new file mode 100644
index 00000000..b4d0af85
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CreateWithOptionGroupTest.php
@@ -0,0 +1,190 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\CustomField;
+use Civi\Api4\CustomGroup;
+use Civi\Api4\Contact;
+
+/**
+ * @group headless
+ */
+class CreateWithOptionGroupTest extends BaseCustomValueTest {
+
+ /**
+ * Remove the custom tables
+ */
+ public function setUp() {
+ $this->dropByPrefix('civicrm_value_financial');
+ $this->dropByPrefix('civicrm_value_favorite');
+ parent::setUp();
+ }
+
+ public function testGetWithCustomData() {
+ $group = uniqid('fava');
+ $colorField = uniqid('colora');
+ $foodField = uniqid('fooda');
+
+ $customGroupId = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', $group)
+ ->addValue('extends', 'Contact')
+ ->execute()
+ ->first()['id'];
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', $colorField)
+ ->addValue('name', $colorField)
+ ->addValue('options', ['r' => 'Red', 'g' => 'Green', 'b' => 'Blue'])
+ ->addValue('custom_group_id', $customGroupId)
+ ->addValue('html_type', 'Select')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', $foodField)
+ ->addValue('name', $foodField)
+ ->addValue('options', ['1' => 'Corn', '2' => 'Potatoes', '3' => 'Cheese'])
+ ->addValue('custom_group_id', $customGroupId)
+ ->addValue('html_type', 'Select')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ $customGroupId = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', 'FinancialStuff')
+ ->addValue('extends', 'Contact')
+ ->execute()
+ ->first()['id'];
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'Salary')
+ ->addValue('custom_group_id', $customGroupId)
+ ->addValue('html_type', 'Number')
+ ->addValue('data_type', 'Money')
+ ->execute();
+
+ Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Jerome')
+ ->addValue('last_name', 'Tester')
+ ->addValue('contact_type', 'Individual')
+ ->addValue("$group.$colorField", 'r')
+ ->addValue("$group.$foodField", '1')
+ ->addValue('FinancialStuff.Salary', 50000)
+ ->execute();
+
+ $result = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('first_name')
+ ->addSelect("$group.$colorField.label")
+ ->addSelect("$group.$foodField.label")
+ ->addSelect('FinancialStuff.Salary')
+ ->addWhere("$group.$foodField.label", 'IN', ['Corn', 'Potatoes'])
+ ->addWhere('FinancialStuff.Salary', '>', '10000')
+ ->execute()
+ ->first();
+
+ $this->assertArrayHasKey($group, $result);
+ $favoriteThings = $result[$group];
+ $favoriteFood = $favoriteThings[$foodField];
+ $favoriteColor = $favoriteThings[$colorField];
+ $financialStuff = $result['FinancialStuff'];
+ $this->assertEquals('Red', $favoriteColor['label']);
+ $this->assertEquals('Corn', $favoriteFood['label']);
+ $this->assertEquals(50000, $financialStuff['Salary']);
+ }
+
+ public function testWithCustomDataForMultipleContacts() {
+ $group = uniqid('favb');
+ $colorField = uniqid('colorb');
+ $foodField = uniqid('foodb');
+
+ $customGroupId = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', $group)
+ ->addValue('extends', 'Contact')
+ ->execute()
+ ->first()['id'];
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', $colorField)
+ ->addValue('name', $colorField)
+ ->addValue('options', ['r' => 'Red', 'g' => 'Green', 'b' => 'Blue'])
+ ->addValue('custom_group_id', $customGroupId)
+ ->addValue('html_type', 'Select')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', $foodField)
+ ->addValue('name', $foodField)
+ ->addValue('options', ['1' => 'Corn', '2' => 'Potatoes', '3' => 'Cheese'])
+ ->addValue('custom_group_id', $customGroupId)
+ ->addValue('html_type', 'Select')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ $customGroupId = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', 'FinancialStuff')
+ ->addValue('extends', 'Contact')
+ ->execute()
+ ->first()['id'];
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'Salary')
+ ->addValue('custom_group_id', $customGroupId)
+ ->addValue('html_type', 'Number')
+ ->addValue('data_type', 'Money')
+ ->execute();
+
+ Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Red')
+ ->addValue('last_name', 'Corn')
+ ->addValue('contact_type', 'Individual')
+ ->addValue("$group.$colorField", 'r')
+ ->addValue("$group.$foodField", '1')
+ ->addValue('FinancialStuff.Salary', 10000)
+ ->execute();
+
+ Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Blue')
+ ->addValue('last_name', 'Cheese')
+ ->addValue('contact_type', 'Individual')
+ ->addValue("$group.$colorField", 'b')
+ ->addValue("$group.$foodField", '3')
+ ->addValue('FinancialStuff.Salary', 500000)
+ ->execute();
+
+ $result = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('first_name')
+ ->addSelect('last_name')
+ ->addSelect("$group.$colorField.label")
+ ->addSelect("$group.$foodField.label")
+ ->addSelect('FinancialStuff.Salary')
+ ->addWhere("$group.$foodField.label", 'IN', ['Corn', 'Cheese'])
+ ->execute();
+
+ $blueCheese = NULL;
+ foreach ($result as $contact) {
+ if ($contact['first_name'] === 'Blue') {
+ $blueCheese = $contact;
+ }
+ }
+
+ $this->assertEquals('Blue', $blueCheese[$group][$colorField]['label']);
+ $this->assertEquals('Cheese', $blueCheese[$group][$foodField]['label']);
+ $this->assertEquals(500000, $blueCheese['FinancialStuff']['Salary']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValuePerformanceTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValuePerformanceTest.php
new file mode 100644
index 00000000..3fa59ef4
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValuePerformanceTest.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\Contact;
+use Civi\Api4\CustomField;
+use Civi\Api4\CustomGroup;
+use Civi\Test\Api4\Traits\QueryCounterTrait;
+
+/**
+ * @group headless
+ */
+class CustomValuePerformanceTest extends BaseCustomValueTest {
+
+ use QueryCounterTrait;
+
+ public function testQueryCount() {
+
+ $this->markTestIncomplete();
+
+ $customGroupId = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', 'MyContactFields')
+ ->addValue('title', 'MyContactFields')
+ ->addValue('extends', 'Contact')
+ ->execute()
+ ->first()['id'];
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'FavColor')
+ ->addValue('custom_group_id', $customGroupId)
+ ->addValue('options', ['r' => 'Red', 'g' => 'Green', 'b' => 'Blue'])
+ ->addValue('html_type', 'Select')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'FavAnimal')
+ ->addValue('custom_group_id', $customGroupId)
+ ->addValue('html_type', 'Text')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'FavLetter')
+ ->addValue('custom_group_id', $customGroupId)
+ ->addValue('html_type', 'Text')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'FavFood')
+ ->addValue('custom_group_id', $customGroupId)
+ ->addValue('html_type', 'Text')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ $this->beginQueryCount();
+
+ Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Red')
+ ->addValue('last_name', 'Tester')
+ ->addValue('contact_type', 'Individual')
+ ->addValue('MyContactFields.FavColor', 'r')
+ ->addValue('MyContactFields.FavAnimal', 'Sheep')
+ ->addValue('MyContactFields.FavLetter', 'z')
+ ->addValue('MyContactFields.FavFood', 'Coconuts')
+ ->execute();
+
+ Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('display_name')
+ ->addSelect('MyContactFields.FavColor.label')
+ ->addSelect('MyContactFields.FavColor.weight')
+ ->addSelect('MyContactFields.FavColor.is_default')
+ ->addSelect('MyContactFields.FavAnimal')
+ ->addSelect('MyContactFields.FavLetter')
+ ->addWhere('MyContactFields.FavColor', '=', 'r')
+ ->addWhere('MyContactFields.FavFood', '=', 'Coconuts')
+ ->addWhere('MyContactFields.FavAnimal', '=', 'Sheep')
+ ->addWhere('MyContactFields.FavLetter', '=', 'z')
+ ->execute()
+ ->first();
+
+ // FIXME: This count is artificially high due to the line
+ // $this->entity = Tables::getBriefName(Tables::getClassForTable($targetTable));
+ // In class Joinable. TODO: Investigate why.
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValueTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValueTest.php
new file mode 100644
index 00000000..da954bc7
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/CustomValueTest.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\CustomField;
+use Civi\Api4\CustomGroup;
+use Civi\Api4\CustomValue;
+use Civi\Api4\Contact;
+
+/**
+ * @group headless
+ */
+class CustomValueTest extends BaseCustomValueTest {
+
+ protected $contactID;
+
+ /**
+ * Test CustomValue::GetFields/Get/Create/Update/Replace/Delete
+ */
+ public function testCRUD() {
+ $optionValues = ['r' => 'Red', 'g' => 'Green', 'b' => 'Blue'];
+
+ $group = uniqid('groupc');
+ $colorField = uniqid('colorc');
+
+ $customGroup = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', $group)
+ ->addValue('extends', 'Contact')
+ ->addValue('is_multiple', TRUE)
+ ->execute()
+ ->first();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', $colorField)
+ ->addValue('options', $optionValues)
+ ->addValue('custom_group_id', $customGroup['id'])
+ ->addValue('html_type', 'Select')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ $this->contactID = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Johann')
+ ->addValue('last_name', 'Tester')
+ ->addValue('contact_type', 'Individual')
+ ->execute()
+ ->first()['id'];
+
+ // Retrieve and check the fields of CustomValue = Custom_$group
+ $fields = CustomValue::getFields($group)->execute();
+ $expectedResult = [
+ [
+ 'custom_field_id' => 1,
+ 'custom_group' => $group,
+ 'name' => $colorField,
+ 'title' => ts($colorField),
+ 'entity' => "Custom_$group",
+ 'data_type' => 'String',
+ 'fk_entity' => NULL,
+ ],
+ [
+ 'name' => 'id',
+ 'title' => ts('Custom Value ID'),
+ 'entity' => "Custom_$group",
+ 'data_type' => 'Integer',
+ 'fk_entity' => NULL,
+ ],
+ [
+ 'name' => 'entity_id',
+ 'title' => ts('Entity ID'),
+ 'entity' => "Custom_$group",
+ 'data_type' => 'Integer',
+ 'fk_entity' => 'Contact',
+ ],
+ ];
+
+ foreach ($expectedResult as $key => $field) {
+ foreach ($field as $attr => $value) {
+ $this->assertEquals($expectedResult[$key][$attr], $fields[$key][$attr]);
+ }
+ }
+
+ // CASE 1: Test CustomValue::create
+ // Create two records for a single contact and using CustomValue::get ensure that two records are created
+ CustomValue::create($group)
+ ->addValue($colorField, 'Green')
+ ->addValue("entity_id", $this->contactID)
+ ->execute();
+ CustomValue::create($group)
+ ->addValue($colorField, 'Red')
+ ->addValue("entity_id", $this->contactID)
+ ->execute();
+ // fetch custom values using API4 CustomValue::get
+ $result = CustomValue::get($group)->execute();
+
+ // check if two custom values are created
+ $this->assertEquals(2, count($result));
+ $expectedResult = [
+ [
+ 'id' => 1,
+ $colorField => 'Green',
+ 'entity_id' => $this->contactID,
+ ],
+ [
+ 'id' => 2,
+ $colorField => 'Red',
+ 'entity_id' => $this->contactID,
+ ],
+ ];
+ // match the data
+ foreach ($expectedResult as $key => $field) {
+ foreach ($field as $attr => $value) {
+ $this->assertEquals($expectedResult[$key][$attr], $result[$key][$attr]);
+ }
+ }
+
+ // CASE 2: Test CustomValue::update
+ // Update a records whose id is 1 and change the custom field (name = Color) value to 'White' from 'Green'
+ CustomValue::update($group)
+ ->addWhere("id", "=", 1)
+ ->addValue($colorField, 'White')
+ ->execute();
+
+ // ensure that the value is changed for id = 1
+ $color = CustomValue::get($group)
+ ->addWhere("id", "=", 1)
+ ->execute()
+ ->first()[$colorField];
+ $this->assertEquals('White', $color);
+
+ // CASE 3: Test CustomValue::replace
+ // create a second contact which will be used to replace the custom values, created earlier
+ $secondContactID = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Adam')
+ ->addValue('last_name', 'Tester')
+ ->addValue('contact_type', 'Individual')
+ ->execute()
+ ->first()['id'];
+ // Replace all the records which was created earlier with entity_id = first contact
+ // with custom record [$colorField => 'Rainbow', 'entity_id' => $secondContactID]
+ CustomValue::replace($group)
+ ->setRecords([[$colorField => 'Rainbow', 'entity_id' => $secondContactID]])
+ ->addWhere('entity_id', '=', $this->contactID)
+ ->execute();
+
+ // Check the two records created earlier is replaced by new contact
+ $result = CustomValue::get($group)->execute();
+ $this->assertEquals(1, count($result));
+
+ $expectedResult = [
+ [
+ 'id' => 3,
+ $colorField => 'Rainbow',
+ 'entity_id' => $secondContactID,
+ ],
+ ];
+ foreach ($expectedResult as $key => $field) {
+ foreach ($field as $attr => $value) {
+ $this->assertEquals($expectedResult[$key][$attr], $result[$key][$attr]);
+ }
+ }
+
+ // CASE 4: Test CustomValue::delete
+ // There is only record left whose id = 3, delete that record on basis of criteria id = 3
+ CustomValue::delete($group)->addWhere("id", "=", 3)->execute();
+ $result = CustomValue::get($group)->execute();
+ // check that there are no custom values present
+ $this->assertEquals(0, count($result));
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/DateTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/DateTest.php
new file mode 100644
index 00000000..8cdbfc7c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/DateTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\Contact;
+use Civi\Api4\Relationship;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class DateTest extends UnitTestCase {
+
+ public function testRelationshipDate() {
+ $c1 = Contact::create()
+ ->addValue('first_name', 'c')
+ ->addValue('last_name', 'one')
+ ->execute()
+ ->first()['id'];
+ $c2 = Contact::create()
+ ->addValue('first_name', 'c')
+ ->addValue('last_name', 'two')
+ ->execute()
+ ->first()['id'];
+ $r = Relationship::create()
+ ->addValue('contact_id_a', $c1)
+ ->addValue('contact_id_b', $c2)
+ ->addValue('relationship_type_id', 1)
+ ->addValue('start_date', 'now')
+ ->addValue('end_date', 'now + 1 week')
+ ->execute()
+ ->first()['id'];
+ $result = Relationship::get()
+ ->addWhere('start_date', '=', 'now')
+ ->addWhere('end_date', '>', 'now + 1 day')
+ ->execute()
+ ->indexBy('id');
+ $this->assertArrayHasKey($r, $result);
+ $result = Relationship::get()
+ ->addWhere('start_date', '<', 'now')
+ ->execute()
+ ->indexBy('id');
+ $this->assertArrayNotHasKey($r, $result);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/EvaluateConditionTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/EvaluateConditionTest.php
new file mode 100644
index 00000000..3726042f
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/EvaluateConditionTest.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\MockBasicEntity;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class EvaluateConditionTest extends UnitTestCase {
+
+ public function testEvaluateCondition() {
+ $action = MockBasicEntity::get();
+ $reflection = new \ReflectionClass($action);
+ $method = $reflection->getMethod('evaluateCondition');
+ $method->setAccessible(TRUE);
+
+ $data = [
+ 'nada' => 0,
+ 'uno' => 1,
+ 'dos' => 2,
+ 'apple' => 'red',
+ 'banana' => 'yellow',
+ 'values' => ['one' => 1, 'two' => 2, 'three' => 3],
+ ];
+
+ $this->assertFalse($method->invoke($action, '$uno > $dos', $data));
+ $this->assertTrue($method->invoke($action, '$uno < $dos', $data));
+ $this->assertTrue($method->invoke($action, '$apple == "red" && $banana != "red"', $data));
+ $this->assertFalse($method->invoke($action, '$apple == "red" && $banana != "yellow"', $data));
+ $this->assertTrue($method->invoke($action, '$values.one == $uno', $data));
+ $this->assertTrue($method->invoke($action, '$values.one + $dos == $values.three', $data));
+ $this->assertTrue($method->invoke($action, 'empty($nada)', $data));
+ $this->assertFalse($method->invoke($action, 'empty($values)', $data));
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ExtendFromIndividualTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ExtendFromIndividualTest.php
new file mode 100644
index 00000000..2d3be50c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ExtendFromIndividualTest.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\Contact;
+use Civi\Api4\CustomField;
+use Civi\Api4\CustomGroup;
+
+/**
+ * @group headless
+ */
+class ExtendFromIndividualTest extends BaseCustomValueTest {
+
+ public function testGetWithNonStandardExtends() {
+
+ $customGroup = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', 'MyContactFields')
+ ->addValue('extends', 'Individual') // not Contact
+ ->execute()
+ ->first();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'FavColor')
+ ->addValue('custom_group_id', $customGroup['id'])
+ ->addValue('html_type', 'Text')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ $contactId = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Johann')
+ ->addValue('last_name', 'Tester')
+ ->addValue('contact_type', 'Individual')
+ ->addValue('MyContactFields.FavColor', 'Red')
+ ->execute()
+ ->first()['id'];
+
+ $contact = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('display_name')
+ ->addSelect('MyContactFields.FavColor')
+ ->addWhere('id', '=', $contactId)
+ ->execute()
+ ->first();
+
+ $this->assertArrayHasKey('MyContactFields', $contact);
+ $contactFields = $contact['MyContactFields'];
+ $favColor = $contactFields['FavColor'];
+ $this->assertEquals('Red', $favColor);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/FkJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/FkJoinTest.php
new file mode 100644
index 00000000..c2b044e5
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/FkJoinTest.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Test\Api4\UnitTestCase;
+use Civi\Api4\Activity;
+use Civi\Api4\Contact;
+
+/**
+ * @group headless
+ */
+class FkJoinTest extends UnitTestCase {
+
+ public function setUpHeadless() {
+ $relatedTables = [
+ 'civicrm_activity',
+ 'civicrm_phone',
+ 'civicrm_activity_contact',
+ ];
+ $this->cleanup(['tablesToTruncate' => $relatedTables]);
+ $this->loadDataSet('DefaultDataSet');
+
+ return parent::setUpHeadless();
+ }
+
+ /**
+ * Fetch all phone call activities. Expects a single activity
+ * loaded from the data set.
+ */
+ public function testThreeLevelJoin() {
+ $results = Activity::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('activity_type.name', '=', 'Phone Call')
+ ->execute();
+
+ $this->assertCount(1, $results);
+ }
+
+ public function testActivityContactJoin() {
+ $results = Activity::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('assignees.id')
+ ->addSelect('assignees.first_name')
+ ->addSelect('assignees.display_name')
+ ->addWhere('assignees.first_name', '=', 'Phoney')
+ ->execute();
+
+ $firstResult = $results->first();
+
+ $this->assertCount(1, $results);
+ $this->assertTrue(is_array($firstResult['assignees']));
+
+ $firstAssignee = array_shift($firstResult['assignees']);
+ $this->assertEquals($firstAssignee['first_name'], 'Phoney');
+ }
+
+ public function testContactPhonesJoin() {
+ $testContact = $this->getReference('test_contact_1');
+ $testPhone = $this->getReference('test_phone_1');
+
+ $results = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('phones.phone')
+ ->addWhere('id', '=', $testContact['id'])
+ ->addWhere('phones.location_type.name', '=', 'Home')
+ ->execute()
+ ->first();
+
+ $this->assertArrayHasKey('phones', $results);
+ $this->assertCount(1, $results['phones']);
+ $firstPhone = array_shift($results['phones']);
+ $this->assertEquals($testPhone['phone'], $firstPhone['phone']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetExtraFieldsTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetExtraFieldsTest.php
new file mode 100644
index 00000000..bc9b10e7
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetExtraFieldsTest.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Test\Api4\UnitTestCase;
+use Civi\Api4\Contact;
+
+/**
+ * @group headless
+ */
+class GetExtraFieldsTest extends UnitTestCase {
+
+ public function testBAOFieldsWillBeReturned() {
+ $returnedFields = Contact::getFields()
+ ->execute()
+ ->getArrayCopy();
+
+ $baseFields = \CRM_Contact_BAO_Contact::fields();
+ $baseFieldNames = array_column($baseFields, 'name');
+ $returnedFieldNames = array_column($returnedFields, 'name');
+ $notReturned = array_diff($baseFieldNames, $returnedFieldNames);
+
+ $this->assertEmpty($notReturned);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetFromArrayTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetFromArrayTest.php
new file mode 100644
index 00000000..bee6fbf3
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/GetFromArrayTest.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Test\Api4\UnitTestCase;
+use Civi\Api4\MockArrayEntity;
+
+/**
+ * @group headless
+ */
+class GetFromArrayTest extends UnitTestCase {
+
+ public function testArrayGetWithLimit() {
+ $result = MockArrayEntity::get()
+ ->setOffset(2)
+ ->setLimit(2)
+ ->execute();
+ $this->assertEquals(3, $result[0]['field1']);
+ $this->assertEquals(4, $result[1]['field1']);
+ $this->assertEquals(2, count($result));
+ }
+
+ public function testArrayGetWithSort() {
+ $result = MockArrayEntity::get()
+ ->addOrderBy('field1', 'DESC')
+ ->execute();
+ $this->assertEquals([5, 4, 3, 2, 1], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addOrderBy('field5', 'DESC')
+ ->addOrderBy('field2', 'ASC')
+ ->execute();
+ $this->assertEquals([3, 2, 5, 4, 1], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addOrderBy('field3', 'ASC')
+ ->addOrderBy('field2', 'ASC')
+ ->execute();
+ $this->assertEquals([3, 1, 2, 5, 4], array_column((array) $result, 'field1'));
+ }
+
+ public function testArrayGetWithSelect() {
+ $result = MockArrayEntity::get()
+ ->addSelect('field1')
+ ->addSelect('field3')
+ ->setLimit(4)
+ ->execute();
+ $this->assertEquals([
+ [
+ 'field1' => 1,
+ 'field3' => NULL,
+ ],
+ [
+ 'field1' => 2,
+ 'field3' => 0,
+ ],
+ [
+ 'field1' => 3,
+ ],
+ [
+ 'field1' => 4,
+ 'field3' => 1,
+ ],
+ ], (array) $result);
+ }
+
+ public function testArrayGetWithWhere() {
+ $result = MockArrayEntity::get()
+ ->addWhere('field2', '=', 'yack')
+ ->execute();
+ $this->assertEquals([2], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field5', '!=', 'banana')
+ ->addWhere('field3', 'IS NOT NULL')
+ ->execute();
+ $this->assertEquals([4, 5], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field1', '>=', '4')
+ ->execute();
+ $this->assertEquals([4, 5], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field1', '<', '2')
+ ->execute();
+ $this->assertEquals([1], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field2', 'LIKE', '%ra%')
+ ->execute();
+ $this->assertEquals([1, 3], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field3', 'IS NULL')
+ ->execute();
+ $this->assertEquals([1, 3], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field3', '=', '0')
+ ->execute();
+ $this->assertEquals([2], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field2', 'LIKE', '%ra')
+ ->execute();
+ $this->assertEquals([1], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field2', 'LIKE', 'ra')
+ ->execute();
+ $this->assertEquals(0, count($result));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field2', 'NOT LIKE', '%ra%')
+ ->execute();
+ $this->assertEquals([2, 4, 5], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field6', '=', '0')
+ ->execute();
+ $this->assertEquals([3, 4, 5], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field6', '=', 0)
+ ->execute();
+ $this->assertEquals([3, 4, 5], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field1', 'BETWEEN', [3, 5])
+ ->execute();
+ $this->assertEquals([3, 4, 5], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addWhere('field1', 'NOT BETWEEN', [3, 4])
+ ->execute();
+ $this->assertEquals([1, 2, 5], array_column((array) $result, 'field1'));
+ }
+
+ public function testArrayGetWithNestedWhereClauses() {
+ $result = MockArrayEntity::get()
+ ->addClause('OR', ['field2', 'LIKE', '%ra'], ['field2', 'LIKE', 'x ray'])
+ ->execute();
+ $this->assertEquals([1, 3], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addClause('OR', ['field2', '=', 'zebra'], ['field2', '=', 'yack'])
+ ->addClause('OR', ['field5', '!=', 'apple'], ['field3', 'IS NULL'])
+ ->execute();
+ $this->assertEquals([1, 2], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addClause('NOT', ['field2', '!=', 'yack'])
+ ->execute();
+ $this->assertEquals([2], array_column((array) $result, 'field1'));
+
+ $result = MockArrayEntity::get()
+ ->addClause('OR', ['field1', '=', 2], ['AND', [['field5', '=', 'apple'], ['field3', '=', 1]]])
+ ->execute();
+ $this->assertEquals([2, 4, 5], array_column((array) $result, 'field1'));
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/IndexTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/IndexTest.php
new file mode 100644
index 00000000..17356c92
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/IndexTest.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class IndexTest extends UnitTestCase {
+
+ public function testIndex() {
+ // Results indexed by name
+ $resultByName = civicrm_api4('Activity', 'getActions', [], 'name');
+ $this->assertInstanceOf('Civi\Api4\Generic\Result', $resultByName);
+ $this->assertEquals('get', $resultByName['get']['name']);
+
+ // Get result at index 0
+ $firstResult = civicrm_api4('Activity', 'getActions', [], 0);
+ $this->assertInstanceOf('Civi\Api4\Generic\Result', $firstResult);
+ $this->assertArrayHasKey('name', $firstResult);
+
+ $this->assertEquals($resultByName->first(), (array) $firstResult);
+ }
+
+ public function testBadIndexInt() {
+ $error = '';
+ try {
+ civicrm_api4('Activity', 'getActions', [], 99);
+ }
+ catch (\API_Exception $e) {
+ $error = $e->getMessage();
+ }
+ $this->assertContains('not found', $error);
+ }
+
+ public function testBadIndexString() {
+ $error = '';
+ try {
+ civicrm_api4('Activity', 'getActions', [], 'xyz');
+ }
+ catch (\API_Exception $e) {
+ $error = $e->getMessage();
+ }
+ $this->assertContains('not found', $error);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/NullValueTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/NullValueTest.php
new file mode 100644
index 00000000..dc4f656a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/NullValueTest.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\Contact;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class NullValueTest extends UnitTestCase {
+
+ public function setUpHeadless() {
+ $format = '{contact.first_name}{ }{contact.last_name}';
+ \Civi::settings()->set('display_name_format', $format);
+ return parent::setUpHeadless();
+ }
+
+ public function testStringNull() {
+ $contact = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Joseph')
+ ->addValue('last_name', 'null')
+ ->addValue('contact_type', 'Individual')
+ ->execute()
+ ->first();
+
+ $this->assertSame('Null', $contact['last_name']);
+ $this->assertSame('Joseph Null', $contact['display_name']);
+ }
+
+ public function testSettingToNull() {
+ $contact = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'ILoveMy')
+ ->addValue('last_name', 'LastName')
+ ->addValue('contact_type', 'Individual')
+ ->execute()
+ ->first();
+
+ $this->assertSame('ILoveMy LastName', $contact['display_name']);
+ $contactId = $contact['id'];
+
+ $contact = Contact::update()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('id', '=', $contactId)
+ ->addValue('last_name', NULL)
+ ->execute()
+ ->first();
+
+ $this->assertSame(NULL, $contact['last_name']);
+ $this->assertSame('ILoveMy', $contact['display_name']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ReplaceTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ReplaceTest.php
new file mode 100644
index 00000000..097e12b0
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/ReplaceTest.php
@@ -0,0 +1,171 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\CustomField;
+use Civi\Api4\CustomGroup;
+use Civi\Api4\CustomValue;
+use Civi\Api4\Email;
+use Civi\Test\Api4\Traits\TableDropperTrait;
+use Civi\Test\Api4\UnitTestCase;
+use Civi\Api4\Contact;
+
+/**
+ * @group headless
+ */
+class ReplaceTest extends UnitTestCase {
+ use TableDropperTrait;
+
+ /**
+ * Set up baseline for testing
+ */
+ public function setUp() {
+ $tablesToTruncate = [
+ 'civicrm_custom_group',
+ 'civicrm_custom_field',
+ 'civicrm_email',
+ ];
+ $this->dropByPrefix('civicrm_value_replacetest');
+ $this->cleanup(['tablesToTruncate' => $tablesToTruncate]);
+ parent::setUp();
+ }
+
+ public function testEmailReplace() {
+ $cid1 = Contact::create()
+ ->addValue('first_name', 'Lotsa')
+ ->addValue('last_name', 'Emails')
+ ->execute()
+ ->first()['id'];
+ $cid2 = Contact::create()
+ ->addValue('first_name', 'Notso')
+ ->addValue('last_name', 'Many')
+ ->execute()
+ ->first()['id'];
+ $e0 = Email::create()
+ ->setValues(['contact_id' => $cid2, 'email' => 'nosomany@example.com', 'location_type_id' => 1])
+ ->execute()
+ ->first()['id'];
+ $e1 = Email::create()
+ ->setValues(['contact_id' => $cid1, 'email' => 'first@example.com', 'location_type_id' => 1])
+ ->execute()
+ ->first()['id'];
+ $e2 = Email::create()
+ ->setValues(['contact_id' => $cid1, 'email' => 'second@example.com', 'location_type_id' => 1])
+ ->execute()
+ ->first()['id'];
+ $replacement = [
+ ['email' => 'firstedited@example.com', 'id' => $e1],
+ ['contact_id' => $cid1, 'email' => 'third@example.com', 'location_type_id' => 1]
+ ];
+ $replaced = Email::replace()
+ ->setRecords($replacement)
+ ->addWhere('contact_id', '=', $cid1)
+ ->execute();
+ // Should have saved 2 records
+ $this->assertEquals(2, $replaced->count());
+ // Should have deleted email2
+ $this->assertEquals([$e2], $replaced->deleted);
+ // Verify contact now has the new email records
+ $results = Email::get()
+ ->addWhere('contact_id', '=', $cid1)
+ ->execute()
+ ->indexBy('id');
+ $this->assertEquals('firstedited@example.com', $results[$e1]['email']);
+ $this->assertEquals(2, $results->count());
+ $this->assertArrayNotHasKey($e2, (array) $results);
+ $this->assertArrayNotHasKey($e0, (array) $results);
+ unset($results[$e1]);
+ foreach ($results as $result) {
+ $this->assertEquals('third@example.com', $result['email']);
+ }
+ // Validate our other contact's email did not get deleted
+ $c2email = Email::get()
+ ->addWhere('contact_id', '=', $cid2)
+ ->execute()
+ ->first();
+ $this->assertEquals('nosomany@example.com', $c2email['email']);
+ }
+
+ public function testCustomValueReplace() {
+ $customGroup = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', 'replaceTest')
+ ->addValue('extends', 'Contact')
+ ->addValue('is_multiple', TRUE)
+ ->execute()
+ ->first();
+
+ CustomField::create()
+ ->addValue('label', 'Custom1')
+ ->addValue('custom_group_id', $customGroup['id'])
+ ->addValue('html_type', 'String')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'Custom2')
+ ->addValue('custom_group_id', $customGroup['id'])
+ ->addValue('html_type', 'String')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ $cid1 = Contact::create()
+ ->addValue('first_name', 'Lotsa')
+ ->addValue('last_name', 'Data')
+ ->execute()
+ ->first()['id'];
+ $cid2 = Contact::create()
+ ->addValue('first_name', 'Notso')
+ ->addValue('last_name', 'Much')
+ ->execute()
+ ->first()['id'];
+
+ // Contact 2 gets one row
+ CustomValue::create('replaceTest')
+ ->setCheckPermissions(FALSE)
+ ->addValue('Custom1', "2 1")
+ ->addValue('Custom2', "2 1")
+ ->addValue('entity_id', $cid2)
+ ->execute();
+
+ // Create 3 rows for contact 1
+ foreach ([1, 2, 3] as $i) {
+ CustomValue::create('replaceTest')
+ ->setCheckPermissions(FALSE)
+ ->addValue('Custom1', "1 $i")
+ ->addValue('Custom2', "1 $i")
+ ->addValue('entity_id', $cid1)
+ ->execute();
+ }
+
+ $cid1Records = CustomValue::get('replaceTest')
+ ->setCheckPermissions(FALSE)
+ ->addWhere('entity_id', '=', $cid1)
+ ->execute();
+
+ $this->assertCount(3, $cid1Records);
+ $this->assertCount(1, CustomValue::get('replaceTest')->setCheckPermissions(FALSE)->addWhere('entity_id', '=', $cid2)->execute());
+
+ $result = CustomValue::replace('replaceTest')
+ ->addWhere('entity_id', '=', $cid1)
+ ->addRecord(['Custom1' => 'new one', 'Custom2' => 'new two'])
+ ->addRecord(['id' => $cid1Records[0]['id'], 'Custom1' => 'changed one', 'Custom2' => 'changed two'])
+ ->execute();
+
+ $this->assertCount(2, $result);
+ $this->assertCount(2, $result->deleted);
+
+ $newRecords = CustomValue::get('replaceTest')
+ ->setCheckPermissions(FALSE)
+ ->addWhere('entity_id', '=', $cid1)
+ ->execute()
+ ->indexBy('id');
+
+ $this->assertEquals('new one', $newRecords->last()['Custom1']);
+ $this->assertEquals('new two', $newRecords->last()['Custom2']);
+ $this->assertEquals('changed one', $newRecords[$cid1Records[0]['id']]['Custom1']);
+ $this->assertEquals('changed two', $newRecords[$cid1Records[0]['id']]['Custom2']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateContactTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateContactTest.php
new file mode 100644
index 00000000..71265598
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateContactTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\Contact;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * Class UpdateContactTest
+ * @package Civi\Test\Api4\Action
+ * @group headless
+ */
+class UpdateContactTest extends UnitTestCase {
+
+ public function testUpdateWillWork() {
+ $contactId = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Johann')
+ ->addValue('last_name', 'Tester')
+ ->addValue('contact_type', 'Individual')
+ ->execute()
+ ->first()['id'];
+
+ $contact = Contact::update()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('id', '=', $contactId)
+ ->addValue('first_name', 'Testy')
+ ->execute()
+ ->first();
+ $this->assertEquals('Testy', $contact['first_name']);
+ $this->assertEquals('Tester', $contact['last_name']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateCustomValueTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateCustomValueTest.php
new file mode 100644
index 00000000..99a9f011
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Action/UpdateCustomValueTest.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Civi\Test\Api4\Action;
+
+use Civi\Api4\Contact;
+use Civi\Api4\CustomField;
+use Civi\Api4\CustomGroup;
+use \CRM_Core_BAO_CustomValueTable as CustomValueTable;
+
+/**
+ * @group headless
+ */
+class UpdateCustomValueTest extends BaseCustomValueTest {
+
+ public function testGetWithCustomData() {
+
+ $customGroup = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', 'MyContactFields')
+ ->addValue('extends', 'Contact')
+ ->execute()
+ ->first();
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'FavColor')
+ ->addValue('custom_group_id', $customGroup['id'])
+ ->addValue('html_type', 'Text')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ $contactId = Contact::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Red')
+ ->addValue('last_name', 'Tester')
+ ->addValue('contact_type', 'Individual')
+ ->addValue('MyContactFields.FavColor', 'Red')
+ ->execute()
+ ->first()['id'];
+
+ Contact::update()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('id', '=', $contactId)
+ ->addValue('first_name', 'Red')
+ ->addValue('last_name', 'Tester')
+ ->addValue('contact_type', 'Individual')
+ ->addValue('MyContactFields.FavColor', 'Blue')
+ ->execute();
+
+ $result = CustomValueTable::getEntityValues($contactId, 'Contact');
+
+ $this->assertEquals(1, count($result));
+ $this->assertContains('Blue', $result);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/ConformanceTest.json b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/ConformanceTest.json
new file mode 100644
index 00000000..fcaf8966
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/ConformanceTest.json
@@ -0,0 +1,28 @@
+{
+ "Contact": [
+ {
+ "first_name": "Janice",
+ "last_name": "Voss",
+ "contact_type": "Individual"
+ }
+ ],
+ "CustomGroup": [
+ {
+ "name": "MyFavoriteThings",
+ "extends": "Contact"
+ }
+ ],
+ "Event": [
+ {
+ "start_date": "20401010000000",
+ "title": "The Singularity",
+ "event_type_id": "major_historical_event"
+ }
+ ],
+ "Group": [
+ {
+ "name": "the_group",
+ "title": "The Group"
+ }
+ ]
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/DefaultDataSet.json b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/DefaultDataSet.json
new file mode 100644
index 00000000..7d4a91bc
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/DefaultDataSet.json
@@ -0,0 +1,45 @@
+{
+ "Contact": [
+ {
+ "first_name": "Phoney",
+ "last_name": "Contact",
+ "contact_type": "Individual",
+ "@ref": "test_contact_1"
+ },
+ {
+ "first_name": "Second",
+ "last_name": "Test",
+ "contact_type": "Individual",
+ "@ref": "test_contact_2"
+ }
+ ],
+ "Activity": [
+ {
+ "subject": "Test Phone Activity",
+ "activity_type": "Phone Call",
+ "source_contact_id": "@ref test_contact_1.id"
+ },
+ {
+ "subject": "Another Activity",
+ "activity_type": "Meeting",
+ "source_contact_id": "@ref test_contact_1.id",
+ "assignee_contact_id": [
+ "@ref test_contact_1.id",
+ "@ref test_contact_2.id"
+ ]
+ }
+ ],
+ "Phone": [
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "phone": "+35355439483",
+ "location_type_id": "1",
+ "@ref": "test_phone_1"
+ },
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "phone": "+3538733439483",
+ "location_type_id": "2"
+ }
+ ]
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/MultiContactMultiEmail.json b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/MultiContactMultiEmail.json
new file mode 100644
index 00000000..ce3fbcaf
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/MultiContactMultiEmail.json
@@ -0,0 +1,42 @@
+{
+ "Contact": [
+ {
+ "first_name": "First",
+ "last_name": "Contact",
+ "contact_type": "Individual",
+ "@ref": "test_contact_1"
+ },
+ {
+ "first_name": "Second",
+ "last_name": "Contact",
+ "contact_type": "Individual",
+ "@ref": "test_contact_2"
+ }
+ ],
+ "Email": [
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "email": "test_contact_one_home@fakedomain.com",
+ "location_type_id": 1,
+ "@ref": "test_email_1"
+ },
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "email": "test_contact_one_work@fakedomain.com",
+ "location_type_id": 2,
+ "@ref": "test_email_2"
+ },
+ {
+ "contact_id": "@ref test_contact_2.id",
+ "email": "test_contact_two_home@fakedomain.com",
+ "location_type_id": 1,
+ "@ref": "test_email_3"
+ },
+ {
+ "contact_id": "@ref test_contact_2.id",
+ "email": "test_contact_two_work@fakedomain.com",
+ "location_type_id": 2,
+ "@ref": "test_email_4"
+ }
+ ]
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/SingleContact.json b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/SingleContact.json
new file mode 100644
index 00000000..73e7369e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/DataSets/SingleContact.json
@@ -0,0 +1,81 @@
+{
+ "Contact": [
+ {
+ "first_name": "Single",
+ "last_name": "Contact",
+ "contact_type": "Individual",
+ "preferred_communication_method": "1",
+ "@ref": "test_contact_1"
+ }
+ ],
+ "Activity": [
+ {
+ "subject": "Won A Nobel Prize",
+ "activity_type": "Meeting",
+ "source_contact_id": "@ref test_contact_1.id",
+ "@ref": "test_activity_1"
+ },
+ {
+ "subject": "Cleaned The House",
+ "activity_type": "Meeting",
+ "source_contact_id": "@ref test_contact_1.id",
+ "assignee_contact_id": [
+ "@ref test_contact_1.id"
+ ],
+ "@ref": "test_activity_2"
+ }
+ ],
+ "Phone": [
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "phone": "+1111111111111",
+ "location_type_id": 1
+ },
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "phone": "+2222222222222",
+ "location_type_id": 2
+ }
+ ],
+ "Email": [
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "email": "test_contact_home@fakedomain.com",
+ "location_type_id": 1
+ },
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "email": "test_contact_work@fakedomain.com",
+ "location_type_id": 2
+ }
+ ],
+ "Address": [
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "street_address": "123 Sesame St.",
+ "location_type_id": 1
+ }
+ ],
+ "Website": [
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "url": "http://test.com",
+ "website_id": 1
+ }
+ ],
+ "OpenID": [
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "openid": "123",
+ "allowed_to_login": 1,
+ "location_type_id": 1
+ }
+ ],
+ "IM": [
+ {
+ "contact_id": "@ref test_contact_1.id",
+ "name": "123",
+ "location_type_id": 1
+ }
+ ]
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ConformanceTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ConformanceTest.php
new file mode 100644
index 00000000..79fe5b30
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ConformanceTest.php
@@ -0,0 +1,226 @@
+<?php
+
+namespace Civi\Test\Api4\Entity;
+
+use Civi\Api4\Generic\AbstractEntity;
+use Civi\Api4\Entity;
+use Civi\Test\Api4\Service\TestCreationParameterProvider;
+use Civi\Test\Api4\Traits\TableDropperTrait;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class ConformanceTest extends UnitTestCase {
+
+ use TableDropperTrait;
+ use \Civi\Test\Api4\Traits\OptionCleanupTrait {
+ setUp as setUpOptionCleanup;
+ }
+
+ /**
+ * @var TestCreationParameterProvider
+ */
+ protected $creationParamProvider;
+
+ /**
+ * Set up baseline for testing
+ */
+ public function setUp() {
+ $tablesToTruncate = [
+ 'civicrm_custom_group',
+ 'civicrm_custom_field',
+ 'civicrm_group',
+ 'civicrm_event',
+ 'civicrm_participant',
+ ];
+ $this->dropByPrefix('civicrm_value_myfavorite');
+ $this->cleanup(['tablesToTruncate' => $tablesToTruncate]);
+ $this->setUpOptionCleanup();
+ $this->loadDataSet('ConformanceTest');
+ $this->creationParamProvider = \Civi::container()->get('test.param_provider');
+ parent::setUp();
+ // calculateTaxAmount() for contribution triggers a deprecation notice
+ \PHPUnit_Framework_Error_Deprecated::$enabled = FALSE;
+ }
+
+ public function getEntities() {
+ $result = [];
+ $entities = Entity::get()->setCheckPermissions(FALSE)->execute();
+ foreach ($entities as $entity) {
+ if ($entity['name'] != 'Entity') {
+ $result[] = [$entity['name']];
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Fixme: This should use getEntities as a dataProvider but that fails for some reason
+ */
+ public function testConformance() {
+ $entities = $this->getEntities();
+ $this->assertNotEmpty($entities);
+
+ foreach ($entities as $data) {
+ $entity = $data[0];
+ $entityClass = 'Civi\Api4\\' . $entity;
+
+ $this->checkActions($entityClass);
+ $this->checkFields($entityClass, $entity);
+ $id = $this->checkCreation($entity, $entityClass);
+ $this->checkGet($entityClass, $id, $entity);
+ $this->checkUpdateFailsFromCreate($entityClass, $id);
+ $this->checkWrongParamType($entityClass);
+ $this->checkDeleteWithNoId($entityClass);
+ $this->checkDeletion($entityClass, $id);
+ $this->checkPostDelete($entityClass, $id, $entity);
+ }
+ }
+
+ /**
+ * @param string $entityClass
+ * @param $entity
+ */
+ protected function checkFields($entityClass, $entity) {
+ $fields = $entityClass::getFields()
+ ->setCheckPermissions(FALSE)
+ ->setIncludeCustom(FALSE)
+ ->execute()
+ ->indexBy('name');
+
+ $errMsg = sprintf('%s is missing required ID field', $entity);
+ $subset = ['data_type' => 'Integer'];
+
+ $this->assertArraySubset($subset, $fields['id'], $errMsg);
+ }
+
+ /**
+ * @param string $entityClass
+ */
+ protected function checkActions($entityClass) {
+ $actions = $entityClass::getActions()
+ ->setCheckPermissions(FALSE)
+ ->execute()
+ ->indexBy('name');
+
+ $this->assertNotEmpty($actions->getArrayCopy());
+ }
+
+ /**
+ * @param string $entity
+ * @param AbstractEntity|string $entityClass
+ *
+ * @return mixed
+ */
+ protected function checkCreation($entity, $entityClass) {
+ $requiredParams = $this->creationParamProvider->getRequired($entity);
+ $createResult = $entityClass::create()
+ ->setValues($requiredParams)
+ ->setCheckPermissions(FALSE)
+ ->execute()
+ ->first();
+
+ $this->assertArrayHasKey('id', $createResult, "create missing ID");
+ $id = $createResult['id'];
+
+ $this->assertGreaterThanOrEqual(1, $id, "$entity ID not positive");
+
+ return $id;
+ }
+
+ /**
+ * @param AbstractEntity|string $entityClass
+ * @param int $id
+ */
+ protected function checkUpdateFailsFromCreate($entityClass, $id) {
+ $exceptionThrown = '';
+ try {
+ $entityClass::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('id', $id)
+ ->execute();
+ }
+ catch (\API_Exception $e) {
+ $exceptionThrown = $e->getMessage();
+ }
+ $this->assertContains('id', $exceptionThrown);
+ }
+
+ /**
+ * @param AbstractEntity|string $entityClass
+ * @param int $id
+ * @param string $entity
+ */
+ protected function checkGet($entityClass, $id, $entity) {
+ $getResult = $entityClass::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('id', '=', $id)
+ ->execute();
+
+ $errMsg = sprintf('Failed to fetch a %s after creation', $entity);
+ $this->assertEquals(1, count($getResult), $errMsg);
+ }
+
+ /**
+ * @param AbstractEntity|string $entityClass
+ */
+ protected function checkDeleteWithNoId($entityClass) {
+ $exceptionThrown = '';
+ try {
+ $entityClass::delete()
+ ->execute();
+ }
+ catch (\API_Exception $e) {
+ $exceptionThrown = $e->getMessage();
+ }
+ $this->assertContains('required', $exceptionThrown);
+ }
+
+ /**
+ * @param AbstractEntity|string $entityClass
+ */
+ protected function checkWrongParamType($entityClass) {
+ $exceptionThrown = '';
+ try {
+ $entityClass::get()
+ ->setCheckPermissions('nada')
+ ->execute();
+ }
+ catch (\API_Exception $e) {
+ $exceptionThrown = $e->getMessage();
+ }
+ $this->assertContains('checkPermissions', $exceptionThrown);
+ $this->assertContains('type', $exceptionThrown);
+ }
+
+ /**
+ * @param AbstractEntity|string $entityClass
+ * @param int $id
+ */
+ protected function checkDeletion($entityClass, $id) {
+ $deleteResult = $entityClass::delete()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('id', '=', $id)
+ ->execute();
+
+ // should get back an array of deleted id
+ $this->assertEquals([$id], (array) $deleteResult);
+ }
+
+ /**
+ * @param AbstractEntity|string $entityClass
+ * @param int $id
+ * @param string $entity
+ */
+ protected function checkPostDelete($entityClass, $id, $entity) {
+ $getDeletedResult = $entityClass::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('id', '=', $id)
+ ->execute();
+
+ $errMsg = sprintf('Entity "%s" was not deleted', $entity);
+ $this->assertEquals(0, count($getDeletedResult), $errMsg);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ContactJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ContactJoinTest.php
new file mode 100644
index 00000000..392e0466
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ContactJoinTest.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Civi\Test\Api4\Entity;
+
+use Civi\Api4\Contact;
+use Civi\Api4\OptionValue;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class ContactJoinTest extends UnitTestCase {
+
+ public function setUpHeadless() {
+ $relatedTables = [
+ 'civicrm_address',
+ 'civicrm_email',
+ 'civicrm_phone',
+ 'civicrm_openid',
+ 'civicrm_im',
+ 'civicrm_website',
+ 'civicrm_activity',
+ 'civicrm_activity_contact',
+ ];
+
+ $this->cleanup(['tablesToTruncate' => $relatedTables]);
+ $this->loadDataSet('SingleContact');
+
+ return parent::setUpHeadless();
+ }
+
+ public function testContactJoin() {
+
+ $contact = $this->getReference('test_contact_1');
+ $entitiesToTest = ['Address', 'OpenID', 'IM', 'Website', 'Email', 'Phone'];
+
+ foreach ($entitiesToTest as $entity) {
+ $results = civicrm_api4($entity, 'get', [
+ 'where' => [['contact_id', '=', $contact['id']]],
+ 'select' => ['contact.display_name', 'contact.id'],
+ ]);
+ foreach ($results as $result) {
+ $this->assertEquals($contact['id'], $result['contact']['id']);
+ $this->assertEquals($contact['display_name'], $result['contact']['display_name']);
+ }
+ }
+ }
+
+ public function testJoinToPCMWillReturnArray() {
+ $contact = Contact::create()->setValues([
+ 'preferred_communication_method' => [1, 2, 3],
+ 'contact_type' => 'Individual',
+ 'first_name' => 'Test',
+ 'last_name' => 'PCM',
+ ])->execute()->first();
+
+ $fetchedContact = Contact::get()
+ ->addWhere('id', '=', $contact['id'])
+ ->addSelect('preferred_communication_method')
+ ->execute()
+ ->first();
+
+ $this->assertCount(3, $fetchedContact["preferred_communication_method"]);
+ }
+
+ public function testJoinToPCMOptionValueWillShowLabel() {
+ $options = OptionValue::get()
+ ->addWhere('option_group.name', '=', 'preferred_communication_method')
+ ->execute()
+ ->getArrayCopy();
+
+ $optionValues = array_column($options, 'value');
+ $labels = array_column($options, 'label');
+
+ $contact = Contact::create()->setValues([
+ 'preferred_communication_method' => $optionValues,
+ 'contact_type' => 'Individual',
+ 'first_name' => 'Test',
+ 'last_name' => 'PCM',
+ ])->execute()->first();
+
+ $contact2 = Contact::create()->setValues([
+ 'preferred_communication_method' => $optionValues,
+ 'contact_type' => 'Individual',
+ 'first_name' => 'Test',
+ 'last_name' => 'PCM2',
+ ])->execute()->first();
+
+ $contactIds = array_column([$contact, $contact2], 'id');
+
+ $fetchedContact = Contact::get()
+ ->addWhere('id', 'IN', $contactIds)
+ ->addSelect('preferred_communication_method.label')
+ ->execute()
+ ->first();
+
+ $preferredMethod = $fetchedContact['preferred_communication_method'];
+ $returnedLabels = array_column($preferredMethod, 'label');
+
+ $this->assertEquals($labels, $returnedLabels);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/EntityTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/EntityTest.php
new file mode 100644
index 00000000..8f7ecd00
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/EntityTest.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Civi\Test\Api4\Entity;
+
+use Civi\Api4\Entity;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class EntityTest extends UnitTestCase {
+
+ public function testEntityGet() {
+ $result = Entity::get()
+ ->setCheckPermissions(FALSE)
+ ->execute()
+ ->indexBy('name');
+ $this->assertArrayHasKey('Entity', $result,
+ "Entity::get missing itself");
+ $this->assertArrayHasKey('Participant', $result,
+ "Entity::get missing Participant");
+ }
+
+ public function testEntity() {
+ $result = Entity::getActions()
+ ->setCheckPermissions(FALSE)
+ ->execute()
+ ->indexBy('name');
+ $this->assertNotContains(
+ 'create',
+ array_keys((array) $result),
+ "Entity entity has more than basic actions");
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ParticipantTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ParticipantTest.php
new file mode 100644
index 00000000..8236efb0
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Entity/ParticipantTest.php
@@ -0,0 +1,200 @@
+<?php
+
+namespace Civi\Test\Api4\Entity;
+
+use Civi\Api4\Participant;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class ParticipantTest extends UnitTestCase {
+
+ public function setUp() {
+ parent::setUp();
+ $cleanup_params = [
+ 'tablesToTruncate' => [
+ 'civicrm_event',
+ 'civicrm_participant',
+ ],
+ ];
+ $this->cleanup($cleanup_params);
+ }
+
+ public function testGetActions() {
+ $result = Participant::getActions()
+ ->setCheckPermissions(FALSE)
+ ->execute()
+ ->indexBy('name');
+
+ $getParams = $result['get']['params'];
+ $whereDescription = 'Criteria for selecting items.';
+
+ $this->assertEquals(TRUE, $getParams['checkPermissions']['default']);
+ $this->assertEquals($whereDescription, $getParams['where']['description']);
+ }
+
+ public function testGet() {
+ $rows = $this->getRowCount('civicrm_participant');
+ if ($rows > 0) {
+ $this->markTestSkipped('Participant table must be empty');
+ }
+
+ // With no records:
+ $result = Participant::get()->setCheckPermissions(FALSE)->execute();
+ $this->assertEquals(0, $result->count(), "count of empty get is not 0");
+
+ // Check that the $result knows what the inputs were
+ $this->assertEquals('Participant', $result->entity);
+ $this->assertEquals('get', $result->action);
+ $this->assertEquals(4, $result->version);
+
+ // Create some test related records before proceeding
+ $participantCount = 20;
+ $contactCount = 7;
+ $eventCount = 5;
+
+ // All events will either have this number or one less because of the
+ // rotating participation creation method.
+ $expectedFirstEventCount = ceil($participantCount / $eventCount);
+
+ $dummy = [
+ 'contacts' => $this->createEntity([
+ 'type' => 'Individual',
+ 'count' => $contactCount,
+ 'seq' => 1]),
+ 'events' => $this->createEntity([
+ 'type' => 'Event',
+ 'count' => $eventCount,
+ 'seq' => 1]),
+ 'sources' => ['Paddington', 'Springfield', 'Central'],
+ ];
+
+ // - create dummy participants record
+ for ($i = 0; $i < $participantCount; $i++) {
+ $dummy['participants'][$i] = $this->sample([
+ 'type' => 'Participant',
+ 'overrides' => [
+ 'event_id' => $dummy['events'][$i % $eventCount]['id'],
+ 'contact_id' => $dummy['contacts'][$i % $contactCount]['id'],
+ 'source' => $dummy['sources'][$i % 3], // 3 = number of sources
+ ]])['sample_params'];
+
+ Participant::create()
+ ->setValues($dummy['participants'][$i])
+ ->setCheckPermissions(FALSE)
+ ->execute();
+ }
+ $sqlCount = $this->getRowCount('civicrm_participant');
+ $this->assertEquals($participantCount, $sqlCount, "Unexpected count");
+
+ $firstEventId = $dummy['events'][0]['id'];
+ $secondEventId = $dummy['events'][1]['id'];
+ $firstContactId = $dummy['contacts'][0]['id'];
+
+ $firstOnlyResult = Participant::get()
+ ->setCheckPermissions(FALSE)
+ ->addClause('AND', ['event_id', '=', $firstEventId])
+ ->execute();
+
+ $this->assertEquals($expectedFirstEventCount, count($firstOnlyResult),
+ "count of first event is not $expectedFirstEventCount");
+
+ // get first two events using different methods
+ $firstTwo = Participant::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('event_id', 'IN', [$firstEventId, $secondEventId])
+ ->execute();
+
+ $firstResult = $result->first();
+
+ // verify counts
+ // count should either twice the first event count or one less
+ $this->assertLessThanOrEqual(
+ $expectedFirstEventCount * 2,
+ count($firstTwo),
+ "count is too high"
+ );
+
+ $this->assertGreaterThanOrEqual(
+ $expectedFirstEventCount * 2 - 1,
+ count($firstTwo),
+ "count is too low"
+ );
+
+ $firstParticipantResult = Participant::get()
+ ->setCheckPermissions(FALSE)
+ ->addWhere('event_id', '=', $firstEventId)
+ ->addWhere('contact_id', '=', $firstContactId)
+ ->execute();
+
+ $this->assertEquals(1, count($firstParticipantResult), "more than one registration");
+
+ $firstParticipantId = $firstParticipantResult->first()['id'];
+
+ // get a result which excludes $first_participant
+ $otherParticipantResult = Participant::get()
+ ->setCheckPermissions(FALSE)
+ ->setSelect(['id'])
+ ->addClause('NOT', [
+ ['event_id', '=', $firstEventId],
+ ['contact_id', '=', $firstContactId],
+ ]
+ )
+ ->execute()
+ ->indexBy('id');
+
+ // check alternate syntax for NOT
+ $otherParticipantResult2 = Participant::get()
+ ->setCheckPermissions(FALSE)
+ ->setSelect(['id'])
+ ->addClause('NOT', 'AND', [
+ ['event_id', '=', $firstEventId],
+ ['contact_id', '=', $firstContactId],
+ ]
+ )
+ ->execute()
+ ->indexBy('id');
+
+ $this->assertEquals($otherParticipantResult, $otherParticipantResult2);
+
+ $this->assertEquals($participantCount - 1,
+ count($otherParticipantResult),
+ "failed to exclude a single record on complex criteria");
+ // check the record we have excluded is the right one:
+
+ $this->assertFalse(
+ $otherParticipantResult->offsetExists($firstParticipantId),
+ 'excluded wrong record');
+
+ // retrieve a participant record and update some records
+ $patchRecord = [
+ 'source' => "not " . $firstResult['source'],
+ ];
+
+ Participant::update()
+ ->addWhere('event_id', '=', $firstEventId)
+ ->setCheckPermissions(FALSE)
+ ->setLimit(20)
+ ->setValues($patchRecord)
+ ->setCheckPermissions(FALSE)
+ ->execute();
+
+ // - delete some records
+ $secondEventId = $dummy['events'][1]['id'];
+ $deleteResult = Participant::delete()
+ ->addWhere('event_id', '=', $secondEventId)
+ ->setCheckPermissions(FALSE)
+ ->execute();
+ $expectedDeletes = [2, 7, 12, 17];
+ $this->assertEquals($expectedDeletes, (array) $deleteResult,
+ "didn't delete every second record as expected");
+
+ $sqlCount = $this->getRowCount('civicrm_participant');
+ $this->assertEquals(
+ $participantCount - count($expectedDeletes),
+ $sqlCount,
+ "records not gone from database after delete");
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/Action/MockArrayEntity/Get.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/Action/MockArrayEntity/Get.php
new file mode 100644
index 00000000..f276baf6
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/Action/MockArrayEntity/Get.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Civi\Api4\Action\MockArrayEntity;
+
+/**
+ * This class demonstrates how the getRecords method of Basic\Get can be overridden.
+ */
+class Get extends \Civi\Api4\Generic\BasicGetAction {
+
+ public function getRecords() {
+ return [
+ [
+ 'field1' => 1,
+ 'field2' => 'zebra',
+ 'field3' => NULL,
+ 'field4' => [1, 2, 3],
+ 'field5' => 'apple',
+ ],
+ [
+ 'field1' => 2,
+ 'field2' => 'yack',
+ 'field3' => 0,
+ 'field4' => [2, 3, 4],
+ 'field5' => 'banana',
+ 'field6' => '',
+ ],
+ [
+ 'field1' => 3,
+ 'field2' => 'x ray',
+ 'field4' => [3, 4, 5],
+ 'field5' => 'banana',
+ 'field6' => 0,
+ ],
+ [
+ 'field1' => 4,
+ 'field2' => 'wildebeest',
+ 'field3' => 1,
+ 'field4' => [4, 5, 6],
+ 'field5' => 'apple',
+ 'field6' => '0',
+ ],
+ [
+ 'field1' => 5,
+ 'field2' => 'vole',
+ 'field3' => 1,
+ 'field4' => [4, 5, 6],
+ 'field5' => 'apple',
+ 'field6' => 0,
+ ],
+ ];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockArrayEntity.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockArrayEntity.php
new file mode 100644
index 00000000..371df50e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockArrayEntity.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Civi\Api4;
+use Civi\Api4\Generic\BasicGetFieldsAction;
+
+/**
+ * MockArrayEntity entity.
+ *
+ * @method Generic\BasicGetAction get()
+ *
+ * @package Civi\Api4
+ */
+class MockArrayEntity extends Generic\AbstractEntity {
+
+ public static function getFields() {
+ return new BasicGetFieldsAction(static::class, __FUNCTION__, function() {
+ return [];
+ });
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockBasicEntity.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockBasicEntity.php
new file mode 100644
index 00000000..aed11e45
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/Api4/MockBasicEntity.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Civi\Api4;
+
+/**
+ * MockBasicEntity entity.
+ *
+ * @package Civi\Api4
+ */
+class MockBasicEntity extends Generic\AbstractEntity {
+
+ const STORAGE_CLASS = '\\Civi\\Test\\Api4\\Mock\\MockEntityDataStorage';
+
+ /**
+ * @return Generic\BasicGetFieldsAction
+ */
+ public static function getFields() {
+ return new Generic\BasicGetFieldsAction(static::class, __FUNCTION__, function() {
+ return [
+ [
+ 'name' => 'id',
+ 'type' => 'Integer',
+ ],
+ [
+ 'name' => 'group',
+ 'options' => [
+ 'one' => 'One',
+ 'two' => 'Two',
+ ]
+ ],
+ [
+ 'name' => 'color',
+ ],
+ [
+ 'name' => 'shape',
+ ],
+ [
+ 'name' => 'size',
+ ],
+ [
+ 'name' => 'weight',
+ ],
+ ];
+ });
+ }
+
+ /**
+ * @return Generic\BasicGetAction
+ */
+ public static function get() {
+ return new Generic\BasicGetAction('MockBasicEntity', __FUNCTION__, [self::STORAGE_CLASS, 'get']);
+ }
+
+ /**
+ * @return Generic\BasicCreateAction
+ */
+ public static function create() {
+ return new Generic\BasicCreateAction(static::class, __FUNCTION__, [self::STORAGE_CLASS, 'write']);
+ }
+
+ /**
+ * @return Generic\BasicUpdateAction
+ */
+ public static function update() {
+ return new Generic\BasicUpdateAction(self::getEntityName(), __FUNCTION__, 'id', [self::STORAGE_CLASS, 'write']);
+ }
+
+ /**
+ * @return Generic\BasicBatchAction
+ */
+ public static function delete() {
+ return new Generic\BasicBatchAction('MockBasicEntity', __FUNCTION__, 'id', [self::STORAGE_CLASS, 'delete']);
+ }
+
+ /**
+ * @return Generic\BasicBatchAction
+ */
+ public static function batchFrobnicate() {
+ return new Generic\BasicBatchAction('MockBasicEntity', __FUNCTION__, ['id', 'number'], function ($item) {
+ return [
+ 'id' => $item['id'],
+ 'frobnication' => $item['number'] * $item['number'],
+ ];
+ });
+ }
+
+ /**
+ * @return Generic\BasicReplaceAction
+ */
+ public static function replace() {
+ return new Generic\BasicReplaceAction('MockBasicEntity', __FUNCTION__);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockEntityDataStorage.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockEntityDataStorage.php
new file mode 100644
index 00000000..fbb10465
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockEntityDataStorage.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Civi\Test\Api4\Mock;
+
+/**
+ * Simple data backend for mock basic api.
+ */
+class MockEntityDataStorage {
+
+ private static $data = [];
+
+ private static $nextId = 1;
+
+ public static function get() {
+ return self::$data;
+ }
+
+ public static function write($record) {
+ if (empty($record['id'])) {
+ $record['id'] = self::$nextId++;
+ }
+ self::$data[$record['id']] = $record;
+ return $record;
+ }
+
+ public static function delete($record) {
+ unset(self::$data[$record['id']]);
+ return $record;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionBase.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionBase.php
new file mode 100644
index 00000000..e46272d2
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionBase.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Civi\Test\Api4\Mock;
+
+/**
+ * Class TestV4ReflectionBase
+ *
+ * This is the base class.
+ *
+ * @internal
+ */
+class MockV4ReflectionBase {
+ /**
+ * This is the foo property.
+ *
+ * In general, you can do nothing with it.
+ *
+ * @var array
+ */
+ public $foo = [];
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionChild.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionChild.php
new file mode 100644
index 00000000..83966b5c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionChild.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Civi\Test\Api4\Mock;
+
+/**
+ * @inheritDoc
+ */
+class MockV4ReflectionChild extends MockV4ReflectionBase {
+ /**
+ * @inheritDoc
+ *
+ * In the child class, foo has been barred.
+ */
+ public $foo = ['bar' => 1];
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionGrandchild.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionGrandchild.php
new file mode 100644
index 00000000..a2a93a4f
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Mock/MockV4ReflectionGrandchild.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Civi\Test\Api4\Mock;
+
+
+/**
+ * Grandchild class
+ *
+ * This is an extended description.
+ *
+ * There is a line break in this description.
+ *
+ * @inheritdoc
+ */
+class MockV4ReflectionGrandchild extends MockV4ReflectionChild {
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryComplexJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryComplexJoinTest.php
new file mode 100644
index 00000000..0e68843a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryComplexJoinTest.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Civi\Test\Api4\Query;
+
+use Civi\Api4\Query\Api4SelectQuery;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class Api4SelectQueryComplexJoinTest extends UnitTestCase {
+
+ public function setUpHeadless() {
+ $relatedTables = [
+ 'civicrm_address',
+ 'civicrm_email',
+ 'civicrm_phone',
+ 'civicrm_openid',
+ 'civicrm_im',
+ 'civicrm_website',
+ 'civicrm_activity',
+ 'civicrm_activity_contact',
+ ];
+ $this->cleanup(['tablesToTruncate' => $relatedTables]);
+ $this->loadDataSet('SingleContact');
+ return parent::setUpHeadless();
+ }
+
+ public function testWithComplexRelatedEntitySelect() {
+ $query = new Api4SelectQuery('Contact', FALSE);
+ $query->select[] = 'id';
+ $query->select[] = 'display_name';
+ $query->select[] = 'phones.phone';
+ $query->select[] = 'emails.email';
+ $query->select[] = 'emails.location_type.name';
+ $query->select[] = 'created_activities.contact_id';
+ $query->select[] = 'created_activities.activity.subject';
+ $query->select[] = 'created_activities.activity.activity_type.name';
+ $query->where[] = ['first_name', '=', 'Single'];
+ $query->where[] = ['id', '=', $this->getReference('test_contact_1')['id']];
+ $results = $query->run();
+
+ $testActivities = [
+ $this->getReference('test_activity_1'),
+ $this->getReference('test_activity_2'),
+ ];
+ $activitySubjects = array_column($testActivities, 'subject');
+
+ $this->assertCount(1, $results);
+ $firstResult = array_shift($results);
+ $this->assertArrayHasKey('created_activities', $firstResult);
+ $firstCreatedActivity = array_shift($firstResult['created_activities']);
+ $this->assertArrayHasKey('activity', $firstCreatedActivity);
+ $firstActivity = $firstCreatedActivity['activity'];
+ $this->assertContains($firstActivity['subject'], $activitySubjects);
+ $this->assertArrayHasKey('activity_type', $firstActivity);
+ $activityType = $firstActivity['activity_type'];
+ $this->assertArrayHasKey('name', $activityType);
+ }
+
+ public function testWithSelectOfOrphanDeepValues() {
+ $query = new Api4SelectQuery('Contact', FALSE);
+ $query->select[] = 'id';
+ $query->select[] = 'first_name';
+ $query->select[] = 'emails.location_type.name'; // emails not selected
+ $results = $query->run();
+ $firstResult = array_shift($results);
+
+ $this->assertEmpty($firstResult['emails']);
+ }
+
+ public function testOrderDoesNotMatter() {
+ $query = new Api4SelectQuery('Contact', FALSE);
+ $query->select[] = 'id';
+ $query->select[] = 'first_name';
+ $query->select[] = 'emails.location_type.name'; // before emails selection
+ $query->select[] = 'emails.email';
+ $query->where[] = ['emails.email', 'IS NOT NULL'];
+ $results = $query->run();
+ $firstResult = array_shift($results);
+
+ $this->assertNotEmpty($firstResult['emails'][0]['location_type']['name']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryTest.php
new file mode 100644
index 00000000..ca9a5851
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/Api4SelectQueryTest.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Civi\Test\Api4\Query;
+
+use Civi\Api4\Query\Api4SelectQuery;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class Api4SelectQueryTest extends UnitTestCase {
+
+ public function setUpHeadless() {
+ $relatedTables = [
+ 'civicrm_address',
+ 'civicrm_email',
+ 'civicrm_phone',
+ 'civicrm_openid',
+ 'civicrm_im',
+ 'civicrm_website',
+ 'civicrm_activity',
+ 'civicrm_activity_contact',
+ ];
+ $this->cleanup(['tablesToTruncate' => $relatedTables]);
+ $this->loadDataSet('DefaultDataSet');
+ $displayNameFormat = '{contact.first_name}{ }{contact.last_name}';
+ \Civi::settings()->set('display_name_format', $displayNameFormat);
+
+ return parent::setUpHeadless();
+ }
+
+ public function testWithSingleWhereJoin() {
+ $phoneNum = $this->getReference('test_phone_1')['phone'];
+
+ $query = new Api4SelectQuery('Contact', FALSE);
+ $query->where[] = ['phones.phone', '=', $phoneNum];
+ $results = $query->run();
+
+ $this->assertCount(1, $results);
+ }
+
+ public function testOneToManyJoin() {
+ $phoneNum = $this->getReference('test_phone_1')['phone'];
+
+ $query = new Api4SelectQuery('Contact', FALSE);
+ $query->select[] = 'id';
+ $query->select[] = 'first_name';
+ $query->select[] = 'phones.phone';
+ $query->where[] = ['phones.phone', '=', $phoneNum];
+ $results = $query->run();
+
+ $this->assertCount(1, $results);
+ $firstResult = array_shift($results);
+ $this->assertArrayHasKey('phones', $firstResult);
+ $firstPhone = array_shift($firstResult['phones']);
+ $this->assertEquals($phoneNum, $firstPhone['phone']);
+ }
+
+ public function testManyToOneJoin() {
+ $phoneNum = $this->getReference('test_phone_1')['phone'];
+ $contact = $this->getReference('test_contact_1');
+
+ $query = new Api4SelectQuery('Phone', FALSE);
+ $query->select[] = 'id';
+ $query->select[] = 'phone';
+ $query->select[] = 'contact.display_name';
+ $query->select[] = 'contact.first_name';
+ $query->where[] = ['phone', '=', $phoneNum];
+ $results = $query->run();
+
+ $this->assertCount(1, $results);
+ $firstResult = array_shift($results);
+ $this->assertArrayHasKey('contact', $firstResult);
+ $resultContact = $firstResult['contact'];
+ $this->assertEquals($contact['display_name'], $resultContact['display_name']);
+ }
+
+ public function testOneToManyMultipleJoin() {
+ $query = new Api4SelectQuery('Contact', FALSE);
+ $query->select[] = 'id';
+ $query->select[] = 'first_name';
+ $query->select[] = 'phones.phone';
+ $query->where[] = ['first_name', '=', 'Phoney'];
+ $results = $query->run();
+ $result = array_pop($results);
+
+ $this->assertEquals('Phoney', $result['first_name']);
+ $this->assertCount(2, $result['phones']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OneToOneJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OneToOneJoinTest.php
new file mode 100644
index 00000000..ef05f657
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OneToOneJoinTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Civi\Test\Api4\Query;
+
+use Civi\Api4\Contact;
+use Civi\Api4\OptionGroup;
+use Civi\Api4\OptionValue;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * Class OneToOneJoinTest
+ * @package Civi\Test\Api4\Query
+ * @group headless
+ */
+class OneToOneJoinTest extends UnitTestCase {
+
+ public function testOneToOneJoin() {
+ $armenianContact = Contact::create()
+ ->addValue('first_name', 'Contact')
+ ->addValue('last_name', 'One')
+ ->addValue('contact_type', 'Individual')
+ ->addValue('preferred_language', 'hy_AM')
+ ->execute()
+ ->first();
+
+ $basqueContact = Contact::create()
+ ->addValue('first_name', 'Contact')
+ ->addValue('last_name', 'Two')
+ ->addValue('contact_type', 'Individual')
+ ->addValue('preferred_language', 'eu_ES')
+ ->execute()
+ ->first();
+
+ $contacts = Contact::get()
+ ->addWhere('id', 'IN', [$armenianContact['id'], $basqueContact['id']])
+ ->addSelect('preferred_language.label')
+ ->addSelect('last_name')
+ ->execute()
+ ->indexBy('last_name')
+ ->getArrayCopy();
+
+ $this->assertEquals($contacts['One']['preferred_language']['label'], 'Armenian');
+ $this->assertEquals($contacts['Two']['preferred_language']['label'], 'Basque');
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OptionValueJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OptionValueJoinTest.php
new file mode 100644
index 00000000..75d4b977
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/OptionValueJoinTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Civi\Test\Api4\Query;
+
+use Civi\Api4\Query\Api4SelectQuery;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class OptionValueJoinTest extends UnitTestCase {
+
+ public function setUpHeadless() {
+ $relatedTables = [
+ 'civicrm_address',
+ 'civicrm_email',
+ 'civicrm_phone',
+ 'civicrm_openid',
+ 'civicrm_im',
+ 'civicrm_website',
+ 'civicrm_activity',
+ 'civicrm_activity_contact',
+ ];
+
+ $this->cleanup(['tablesToTruncate' => $relatedTables]);
+ $this->loadDataSet('SingleContact');
+
+ return parent::setUpHeadless();
+ }
+
+ public function testCommunicationMethodJoin() {
+ $query = new Api4SelectQuery('Contact', FALSE);
+ $query->select[] = 'first_name';
+ $query->select[] = 'preferred_communication_method.label';
+ $query->where[] = ['preferred_communication_method', 'IS NOT NULL'];
+ $results = $query->run();
+ $first = array_shift($results);
+ $firstPreferredMethod = array_shift($first['preferred_communication_method']);
+
+ $this->assertEquals(
+ 'Phone',
+ $firstPreferredMethod['label']
+ );
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/SelectQueryMultiJoinTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/SelectQueryMultiJoinTest.php
new file mode 100644
index 00000000..860bd785
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Query/SelectQueryMultiJoinTest.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Civi\Test\Api4\Query;
+
+use Civi\Api4\Contact;
+use Civi\Api4\Email;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * Class SelectQueryMultiJoinTest
+ * @package Civi\Test\Api4\Query
+ * @group headless
+ */
+class SelectQueryMultiJoinTest extends UnitTestCase {
+ public function setUpHeadless() {
+ $this->cleanup(['tablesToTruncate' => ['civicrm_contact', 'civicrm_email']]);
+ $this->loadDataSet('MultiContactMultiEmail');
+ return parent::setUpHeadless();
+ }
+
+ public function testOneToManySelect() {
+ $results = Contact::get()
+ ->addSelect('emails.email')
+ ->execute()
+ ->indexBy('id')
+ ->getArrayCopy();
+
+ $firstContactId = $this->getReference('test_contact_1')['id'];
+ $secondContactId = $this->getReference('test_contact_2')['id'];
+
+ $firstContact = $results[$firstContactId];
+ $secondContact = $results[$secondContactId];
+ $firstContactEmails = array_column($firstContact['emails'], 'email');
+ $secondContactEmails = array_column($secondContact['emails'], 'email');
+
+ $expectedFirstEmails = [
+ 'test_contact_one_home@fakedomain.com',
+ 'test_contact_one_work@fakedomain.com',
+ ];
+ $expectedSecondEmails = [
+ 'test_contact_two_home@fakedomain.com',
+ 'test_contact_two_work@fakedomain.com',
+ ];
+
+ $this->assertEquals($expectedFirstEmails, $firstContactEmails);
+ $this->assertEquals($expectedSecondEmails, $secondContactEmails);
+ }
+
+ public function testManyToOneSelect() {
+ $results = Email::get()
+ ->addSelect('contact.display_name')
+ ->execute()
+ ->indexBy('id')
+ ->getArrayCopy();
+
+ $firstEmail = $this->getReference('test_email_1');
+ $secondEmail = $this->getReference('test_email_2');
+ $thirdEmail = $this->getReference('test_email_3');
+ $fourthEmail = $this->getReference('test_email_4');
+ $firstContactEmailIds = [$firstEmail['id'], $secondEmail['id']];
+ $secondContactEmailIds = [$thirdEmail['id'], $fourthEmail['id']];
+
+ foreach ($results as $id => $email) {
+ $displayName = $email['contact']['display_name'];
+ if (in_array($id, $firstContactEmailIds)) {
+ $this->assertEquals('First Contact', $displayName);
+ }
+ elseif (in_array($id, $secondContactEmailIds)) {
+ $this->assertEquals('Second Contact', $displayName);
+ }
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapRealTableTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapRealTableTest.php
new file mode 100644
index 00000000..183d34dc
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapRealTableTest.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Civi\Test\Api4\Service\Schema;
+
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class SchemaMapRealTableTest extends UnitTestCase {
+ public function testAutoloadWillPopulateTablesByDefault() {
+ $map = \Civi::container()->get('schema_map');
+ $this->assertNotEmpty($map->getTables());
+ }
+
+ public function testSimplePathWillExist() {
+ $map = \Civi::container()->get('schema_map');
+ $path = $map->getPath('civicrm_contact', 'emails');
+ $this->assertCount(1, $path);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapperTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapperTest.php
new file mode 100644
index 00000000..04952f7b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/Schema/SchemaMapperTest.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Civi\Test\Api4\Service\Schema;
+
+use Civi\Api4\Service\Schema\Joinable\Joinable;
+use Civi\Api4\Service\Schema\SchemaMap;
+use Civi\Api4\Service\Schema\Table;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class SchemaMapperTest extends UnitTestCase {
+
+ public function testWillHaveNoPathWithNoTables() {
+ $map = new SchemaMap();
+ $this->assertEmpty($map->getPath('foo', 'bar'));
+ }
+
+ public function testWillHavePathWithSingleJump() {
+ $phoneTable = new Table('civicrm_phone');
+ $locationTable = new Table('civicrm_location_type');
+ $link = new Joinable('civicrm_location_type', 'id', 'location');
+ $phoneTable->addTableLink('location_type_id', $link);
+
+ $map = new SchemaMap();
+ $map->addTables([$phoneTable, $locationTable]);
+
+ $this->assertNotEmpty($map->getPath('civicrm_phone', 'location'));
+ }
+
+ public function testWillHavePathWithDoubleJump() {
+ $activity = new Table('activity');
+ $activityContact = new Table('activity_contact');
+ $middleLink = new Joinable('activity_contact', 'activity_id');
+ $contactLink = new Joinable('contact', 'id');
+ $activity->addTableLink('id', $middleLink);
+ $activityContact->addTableLink('contact_id', $contactLink);
+
+ $map = new SchemaMap();
+ $map->addTables([$activity, $activityContact]);
+
+ $this->assertNotEmpty($map->getPath('activity', 'contact'));
+ }
+
+ public function testPathWithTripleJoin() {
+ $first = new Table('first');
+ $second = new Table('second');
+ $third = new Table('third');
+ $first->addTableLink('id', new Joinable('second', 'id'));
+ $second->addTableLink('id', new Joinable('third', 'id'));
+ $third->addTableLink('id', new Joinable('fourth', 'id'));
+
+ $map = new SchemaMap();
+ $map->addTables([$first, $second, $third]);
+
+ $this->assertNotEmpty($map->getPath('first', 'fourth'));
+ }
+
+ public function testCircularReferenceWillNotBreakIt() {
+ $contactTable = new Table('contact');
+ $carTable = new Table('car');
+ $carLink = new Joinable('car', 'id');
+ $ownerLink = new Joinable('contact', 'id');
+ $contactTable->addTableLink('car_id', $carLink);
+ $carTable->addTableLink('owner_id', $ownerLink);
+
+ $map = new SchemaMap();
+ $map->addTables([$contactTable, $carTable]);
+
+ $this->assertEmpty($map->getPath('contact', 'foo'));
+ }
+
+ public function testCannotGoOverJoinLimit() {
+ $first = new Table('first');
+ $second = new Table('second');
+ $third = new Table('third');
+ $fourth = new Table('fourth');
+ $first->addTableLink('id', new Joinable('second', 'id'));
+ $second->addTableLink('id', new Joinable('third', 'id'));
+ $third->addTableLink('id', new Joinable('fourth', 'id'));
+ $fourth->addTableLink('id', new Joinable('fifth', 'id'));
+
+ $map = new SchemaMap();
+ $map->addTables([$first, $second, $third, $fourth]);
+
+ $this->assertEmpty($map->getPath('first', 'fifth'));
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/TestCreationParameterProvider.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/TestCreationParameterProvider.php
new file mode 100644
index 00000000..ef10cea9
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Service/TestCreationParameterProvider.php
@@ -0,0 +1,148 @@
+<?php
+
+namespace Civi\Test\Api4\Service;
+
+use Civi\Api4\Service\Spec\FieldSpec;
+use Civi\Api4\Service\Spec\SpecGatherer;
+use \CRM_Utils_String as StringHelper;
+
+class TestCreationParameterProvider {
+
+ /**
+ * @var SpecGatherer
+ */
+ protected $gatherer;
+
+ /**
+ * @param SpecGatherer $gatherer
+ */
+ public function __construct(SpecGatherer $gatherer) {
+ $this->gatherer = $gatherer;
+ }
+
+ /**
+ * @param $entity
+ *
+ * @return array
+ */
+ public function getRequired($entity) {
+ $createSpec = $this->gatherer->getSpec($entity, 'create', FALSE);
+ $requiredFields = array_merge($createSpec->getRequiredFields(), $createSpec->getConditionalRequiredFields());
+
+ if ($entity === 'Contact') {
+ $requiredFields[] = $createSpec->getFieldByName('first_name');
+ $requiredFields[] = $createSpec->getFieldByName('last_name');
+ }
+
+ $requiredParams = [];
+ foreach ($requiredFields as $requiredField) {
+ $value = $this->getRequiredValue($requiredField);
+ $requiredParams[$requiredField->getName()] = $value;
+ }
+
+ unset($requiredParams['id']);
+
+ return $requiredParams;
+ }
+
+ /**
+ * Attempt to get a value using field option, defaults, FKEntity, or a random
+ * value based on the data type.
+ *
+ * @param FieldSpec $field
+ *
+ * @return mixed
+ * @throws \Exception
+ */
+ private function getRequiredValue(FieldSpec $field) {
+
+ if ($field->getOptions()) {
+ return $this->getOption($field);
+ }
+ elseif ($field->getDefaultValue()) {
+ return $field->getDefaultValue();
+ }
+ elseif ($field->getFkEntity()) {
+ return $this->getFkID($field);
+ }
+
+ $randomValue = $this->getRandomValue($field->getDataType());
+
+ if ($randomValue) {
+ return $randomValue;
+ }
+
+ throw new \Exception('Could not provide default value');
+ }
+
+ /**
+ * @param FieldSpec $field
+ *
+ * @return mixed
+ */
+ private function getOption(FieldSpec $field) {
+ $options = $field->getOptions();
+ $useKeyNames = ['data_type', 'html_type'];
+ $shouldUseKey = in_array($field->getName(), $useKeyNames);
+ $isIdField = substr($field->getName(), -3) === '_id';
+
+ if ($isIdField || $shouldUseKey) {
+ return array_rand($options); // return key (ID)
+ }
+ else {
+ return $options[array_rand($options)];
+ }
+ }
+
+ /**
+ * @param FieldSpec $field
+ *
+ * @return mixed
+ * @throws \Exception
+ */
+ private function getFkID(FieldSpec $field) {
+ $fkEntity = $field->getFkEntity();
+ $params = ['checkPermissions' => FALSE];
+ // Be predictable about what type of contact we select
+ if ($fkEntity === 'Contact') {
+ $params['where'] = [['contact_type', '=', 'Individual']];
+ }
+ $entityList = civicrm_api4($fkEntity, 'get', $params);
+ if ($entityList->count() < 1) {
+ $msg = sprintf('At least one %s is required in test', $fkEntity);
+ throw new \Exception($msg);
+ }
+
+ return $entityList->last()['id'];
+ }
+
+ /**
+ * @param $dataType
+ *
+ * @return int|null|string
+ */
+ private function getRandomValue($dataType) {
+ switch ($dataType) {
+ case 'Boolean':
+ return TRUE;
+
+ case 'Integer':
+ return rand(1, 2000);
+
+ case 'String':
+ return StringHelper::createRandom(10, implode('', range('a', 'z')));
+
+ case 'Text':
+ return StringHelper::createRandom(100, implode('', range('a', 'z')));
+
+ case 'Money':
+ return sprintf('%d.%2d', rand(0, 2000), rand(1, 99));
+
+ case 'Date':
+ return '20100102';
+ }
+
+ return NULL;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/RequestSpecTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/RequestSpecTest.php
new file mode 100644
index 00000000..45aa41b2
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/RequestSpecTest.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Civi\Test\Api4\Spec;
+
+use Civi\Api4\Service\Spec\FieldSpec;
+use Civi\Api4\Service\Spec\RequestSpec;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class RequestSpecTest extends UnitTestCase {
+
+ public function testRequiredFieldFetching() {
+ $spec = new RequestSpec('Contact', 'get');
+ $requiredField = new FieldSpec('name', 'Contact');
+ $requiredField->setRequired(TRUE);
+ $nonRequiredField = new FieldSpec('age', 'Contact', 'Integer');
+ $nonRequiredField->setRequired(FALSE);
+ $spec->addFieldSpec($requiredField);
+ $spec->addFieldSpec($nonRequiredField);
+
+ $requiredFields = $spec->getRequiredFields();
+
+ $this->assertCount(1, $requiredFields);
+ $this->assertEquals('name', array_shift($requiredFields)->getName());
+ }
+
+ public function testGettingFieldNames() {
+ $spec = new RequestSpec('Contact', 'get');
+ $nameField = new FieldSpec('name', 'Contact');
+ $ageField = new FieldSpec('age', 'Contact', 'Integer');
+ $spec->addFieldSpec($nameField);
+ $spec->addFieldSpec($ageField);
+
+ $fieldNames = $spec->getFieldNames();
+
+ $this->assertCount(2, $fieldNames);
+ $this->assertEquals(['name', 'age'], $fieldNames);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecFormatterTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecFormatterTest.php
new file mode 100644
index 00000000..bdb1c459
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecFormatterTest.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Civi\Test\Api4\Spec;
+
+use Civi\Api4\Service\Spec\CustomFieldSpec;
+use Civi\Api4\Service\Spec\FieldSpec;
+use Civi\Api4\Service\Spec\RequestSpec;
+use Civi\Api4\Service\Spec\SpecFormatter;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class SpecFormatterTest extends UnitTestCase {
+
+ public function testSpecToArray() {
+ $spec = new RequestSpec('Contact', 'get');
+ $fieldName = 'last_name';
+ $field = new FieldSpec($fieldName, 'Contact');
+ $spec->addFieldSpec($field);
+ $arraySpec = SpecFormatter::specToArray($spec->getFields());
+
+ $this->assertEquals('String', $arraySpec[$fieldName]['data_type']);
+ }
+
+ /**
+ * @dataProvider arrayFieldSpecProvider
+ *
+ * @param array $fieldData
+ * @param string $expectedName
+ * @param string $expectedType
+ */
+ public function testArrayToField($fieldData, $expectedName, $expectedType) {
+ $field = SpecFormatter::arrayToField($fieldData, 'TestEntity');
+
+ $this->assertEquals($expectedName, $field->getName());
+ $this->assertEquals($expectedType, $field->getDataType());
+ }
+
+ public function testCustomFieldWillBeReturned() {
+ $customGroupId = 1432;
+ $customFieldId = 3333;
+ $name = 'MyFancyField';
+
+ $data = [
+ 'custom_group_id' => $customGroupId,
+ 'custom_group' => ['name' => 'my_group'],
+ 'id' => $customFieldId,
+ 'name' => $name,
+ 'data_type' => 'String',
+ 'html_type' => 'MultiSelect',
+ ];
+
+ /** @var CustomFieldSpec $field */
+ $field = SpecFormatter::arrayToField($data, 'TestEntity');
+
+ $this->assertInstanceOf(CustomFieldSpec::class, $field);
+ $this->assertEquals('my_group', $field->getCustomGroupName());
+ $this->assertEquals($customFieldId, $field->getCustomFieldId());
+ $this->assertEquals(\CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND, $field->getSerialize());
+ }
+
+ /**
+ * @return array
+ */
+ public function arrayFieldSpecProvider() {
+ return [
+ [
+ [
+ 'name' => 'Foo',
+ 'title' => 'Bar',
+ 'type' => \CRM_Utils_Type::T_STRING
+ ],
+ 'Foo',
+ 'String'
+ ],
+ [
+ [
+ 'name' => 'MyField',
+ 'title' => 'Bar',
+ 'type' => \CRM_Utils_Type::T_STRING,
+ 'data_type' => 'Boolean' // this should take precedence
+ ],
+ 'MyField',
+ 'Boolean'
+ ],
+ ];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecGathererTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecGathererTest.php
new file mode 100644
index 00000000..bf5b92b9
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Spec/SpecGathererTest.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace Civi\Test\Api4\Spec;
+
+use Civi\Api4\Service\Spec\FieldSpec;
+use Civi\Api4\Service\Spec\Provider\SpecProviderInterface;
+use Civi\Api4\Service\Spec\RequestSpec;
+use Civi\Api4\Service\Spec\SpecGatherer;
+use Civi\Test\Api4\Traits\OptionCleanupTrait;
+use Civi\Test\Api4\UnitTestCase;
+use Civi\Api4\CustomField;
+use Civi\Api4\CustomGroup;
+use Civi\Test\Api4\Traits\TableDropperTrait;
+use Prophecy\Argument;
+
+/**
+ * @group headless
+ */
+class SpecGathererTest extends UnitTestCase {
+
+ use TableDropperTrait;
+ use OptionCleanupTrait;
+
+ public function setUpHeadless() {
+ $this->dropByPrefix('civicrm_value_favorite');
+ $this->cleanup([
+ 'tablesToTruncate' => [
+ 'civicrm_custom_group',
+ 'civicrm_custom_field'
+ ],
+ ]);
+ return parent::setUpHeadless();
+ }
+
+ public function testBasicFieldsGathering() {
+ $gatherer = new SpecGatherer();
+ $specs = $gatherer->getSpec('Contact', 'get', FALSE);
+ $contactDAO = _civicrm_api3_get_DAO('Contact');
+ $contactFields = $contactDAO::fields();
+ $specFieldNames = $specs->getFieldNames();
+ $contactFieldNames = array_column($contactFields, 'name');
+
+ $this->assertEmpty(array_diff_key($contactFieldNames, $specFieldNames));
+ }
+
+ public function testWithSpecProvider() {
+ $gather = new SpecGatherer();
+
+ $provider = $this->prophesize(SpecProviderInterface::class);
+ $provider->applies('Contact', 'create')->willReturn(TRUE);
+ $provider->modifySpec(Argument::any())->will(function ($args) {
+ /** @var RequestSpec $spec */
+ $spec = $args[0];
+ $spec->addFieldSpec(new FieldSpec('foo', 'Contact'));
+ });
+ $gather->addSpecProvider($provider->reveal());
+
+ $spec = $gather->getSpec('Contact', 'create', FALSE);
+ $fieldNames = $spec->getFieldNames();
+
+ $this->assertContains('foo', $fieldNames);
+ }
+
+ public function testPseudoConstantOptionsWillBeAdded() {
+ $customGroupId = CustomGroup::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('name', 'FavoriteThings')
+ ->addValue('extends', 'Contact')
+ ->execute()
+ ->first()['id'];
+
+ $options = ['r' => 'Red', 'g' => 'Green', 'p' => 'Pink'];
+
+ CustomField::create()
+ ->setCheckPermissions(FALSE)
+ ->addValue('label', 'FavColor')
+ ->addValue('custom_group_id', $customGroupId)
+ ->addValue('options', $options)
+ ->addValue('html_type', 'Select')
+ ->addValue('data_type', 'String')
+ ->execute();
+
+ $gatherer = new SpecGatherer();
+ $spec = $gatherer->getSpec('Contact', 'get', TRUE);
+
+ $regularField = $spec->getFieldByName('contact_type');
+ $this->assertNotEmpty($regularField->getOptions());
+ $this->assertContains('Individual', $regularField->getOptions());
+
+ $customField = $spec->getFieldByName('FavoriteThings.FavColor');
+ $this->assertNotEmpty($customField->getOptions());
+ $this->assertContains('Green', $customField->getOptions());
+ $this->assertEquals('Pink', $customField->getOptions()['p']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/OptionCleanupTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/OptionCleanupTrait.php
new file mode 100644
index 00000000..06f43235
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/OptionCleanupTrait.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Civi\Test\Api4\Traits;
+
+trait OptionCleanupTrait {
+
+ protected $optionGroupMaxId;
+ protected $optionValueMaxId;
+
+ public function setUp() {
+ $this->optionGroupMaxId = \CRM_Core_DAO::singleValueQuery('SELECT MAX(id) FROM civicrm_option_group');
+ $this->optionValueMaxId = \CRM_Core_DAO::singleValueQuery('SELECT MAX(id) FROM civicrm_option_value');
+ }
+
+ public function tearDown() {
+ if ($this->optionValueMaxId) {
+ \CRM_Core_DAO::executeQuery('DELETE FROM civicrm_option_value WHERE id > ' . $this->optionValueMaxId);
+ }
+ if ($this->optionGroupMaxId) {
+ \CRM_Core_DAO::executeQuery('DELETE FROM civicrm_option_group WHERE id > ' . $this->optionGroupMaxId);
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/QueryCounterTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/QueryCounterTrait.php
new file mode 100644
index 00000000..c7e10f1b
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/QueryCounterTrait.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Civi\Test\Api4\Traits;
+
+use \CRM_Utils_Array as ArrayHelper;
+
+trait QueryCounterTrait {
+
+ /**
+ * @var int
+ */
+ protected $startCount = 0;
+
+ /**
+ * Start the query counter
+ */
+ protected function beginQueryCount() {
+ $this->startCount = $this->getCurrentGlobalQueryCount();
+ }
+
+ /**
+ * @return int
+ * The number of queries since the counter was started
+ */
+ protected function getQueryCount() {
+ return $this->getCurrentGlobalQueryCount() - $this->startCount;
+ }
+
+ /**
+ * @return int
+ * @throws \Exception
+ */
+ private function getCurrentGlobalQueryCount() {
+ global $_DB_DATAOBJECT;
+
+ if (!$_DB_DATAOBJECT) {
+ throw new \Exception('Database object not set so cannot count queries');
+ }
+
+ return ArrayHelper::value('RESULTSEQ', $_DB_DATAOBJECT, 0);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TableDropperTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TableDropperTrait.php
new file mode 100644
index 00000000..6e543473
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TableDropperTrait.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Civi\Test\Api4\Traits;
+
+trait TableDropperTrait {
+ /**
+ * @param $prefix
+ */
+ protected function dropByPrefix($prefix) {
+ $sql = "SELECT CONCAT( 'DROP TABLE ', GROUP_CONCAT(table_name) , ';' ) " .
+ "AS statement FROM information_schema.tables " .
+ "WHERE table_name LIKE '%s%%' AND table_schema = DATABASE();";
+ $sql = sprintf($sql, $prefix);
+ $dropTableQuery = \CRM_Core_DAO::executeQuery($sql);
+ $dropTableQuery->fetch();
+ $dropTableQuery = $dropTableQuery->statement;
+
+ if ($dropTableQuery) {
+ \CRM_Core_DAO::executeQuery($dropTableQuery);
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TestDataLoaderTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TestDataLoaderTrait.php
new file mode 100644
index 00000000..1db22090
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Traits/TestDataLoaderTrait.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Civi\Test\Api4\Traits;
+
+/**
+ * This probably should be a separate class
+ */
+trait TestDataLoaderTrait {
+
+ /**
+ * @var array
+ * References to entities used for loading test data
+ */
+ protected $references;
+
+ /**
+ * Creates entities from a JSON data set
+ *
+ * @param $path
+ */
+ protected function loadDataSet($path) {
+ if (!file_exists($path)) {
+ $path = __DIR__ . '/../DataSets/' . $path . '.json';
+ }
+
+ $dataSet = json_decode(file_get_contents($path), TRUE);
+ foreach ($dataSet as $entityName => $entities) {
+ foreach ($entities as $entityValues) {
+
+ $entityValues = $this->replaceReferences($entityValues);
+
+ $params = ['values' => $entityValues, 'checkPermissions' => FALSE];
+ $result = civicrm_api4($entityName, 'create', $params);
+ if (isset($entityValues['@ref'])) {
+ $this->references[$entityValues['@ref']] = $result->first();
+ }
+ }
+ }
+ }
+
+ /**
+ * @param $name
+ *
+ * @return null|mixed
+ */
+ protected function getReference($name) {
+ return isset($this->references[$name]) ? $this->references[$name] : NULL;
+ }
+
+ /**
+ * @param array $entityValues
+ *
+ * @return array
+ */
+ private function replaceReferences($entityValues) {
+ foreach ($entityValues as $name => $value) {
+ if (is_array($value)) {
+ $entityValues[$name] = $this->replaceReferences($value);
+ }
+ elseif (substr($value, 0, 4) === '@ref') {
+ $referenceName = substr($value, 5);
+ list ($reference, $property) = explode('.', $referenceName);
+ $entityValues[$name] = $this->references[$reference][$property];
+ }
+ }
+ return $entityValues;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/UnitTestCase.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/UnitTestCase.php
new file mode 100644
index 00000000..4f872d12
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/UnitTestCase.php
@@ -0,0 +1,235 @@
+<?php
+
+namespace Civi\Test\Api4;
+
+use Civi\Test\Api4\Traits\TestDataLoaderTrait;
+use Civi\Test\HeadlessInterface;
+use Civi\Test\TransactionalInterface;
+
+/**
+ * @group headless
+ */
+class UnitTestCase extends \PHPUnit_Framework_TestCase implements HeadlessInterface, TransactionalInterface {
+
+ use TestDataLoaderTrait;
+
+ /**
+ * @see CiviUnitTestCase
+ *
+ * @param string $name
+ * @param array $data
+ * @param string $dataName
+ */
+ public function __construct($name = NULL, array $data = [], $dataName = '') {
+ parent::__construct($name, $data, $dataName);
+ error_reporting(E_ALL & ~E_NOTICE);
+ }
+
+ public function setUpHeadless() {
+ return \Civi\Test::headless()->installMe(__DIR__)->apply();
+ }
+
+ /**
+ * Tears down the fixture, for example, closes a network connection.
+ *
+ * This method is called after a test is executed.
+ */
+ public function tearDown() {
+ parent::tearDown();
+ }
+
+ /**
+ * Quick clean by emptying tables created for the test.
+ *
+ * @param array $params
+ */
+ public function cleanup($params) {
+ $params += [
+ 'tablesToTruncate' => [],
+ ];
+ \CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 0;");
+ foreach ($params['tablesToTruncate'] as $table) {
+ \Civi::log()->info('truncating: ' . $table);
+ $sql = "TRUNCATE TABLE $table";
+ \CRM_Core_DAO::executeQuery($sql);
+ }
+ \CRM_Core_DAO::executeQuery("SET FOREIGN_KEY_CHECKS = 1;");
+ }
+
+ /**
+ * Quick record counter
+ *
+ * @param string $table_name
+ * @returns int record count
+ */
+ public function getRowCount($table_name) {
+ $sql = "SELECT count(id) FROM $table_name";
+ return (int) \CRM_Core_DAO::singleValueQuery($sql);
+ }
+
+ /**
+ * Create sample entities (using V3 for now).
+ *
+ * @param array $params (type, seq, overrides, count)
+ * @return array (either single, or array of array if count >1)
+ */
+ public static function createEntity($params) {
+ $params += [
+ 'count' => 1,
+ 'seq' => 0,
+ ];
+ $entities = [];
+ $entity = NULL;
+ for ($i = 0; $i < $params['count']; $i++) {
+ $params['seq']++;
+ $data = self::sample($params);
+ $api_params = ['sequential' => 1] + $data['sample_params'];
+ $result = civicrm_api3($data['entity'], 'create', $api_params);
+ if ($result['is_error']) {
+ throw new \Exception("creating $data[entity] failed");
+ }
+ $entity = $result['values'][0];
+ if (!($entity['id'] > 0)) {
+ throw new \Exception("created entity is malformed");
+ }
+ $entities[] = $entity;
+ }
+ return $params['count'] == 1 ? $entity : $entities;
+ }
+
+ /**
+ * Helper function for creating sample entities.
+ *
+ * Depending on the supplied sequence integer, plucks values from the dummy data.
+ * Constructs a foreign entity when an ID is required but isn't supplied in the overrides.
+ *
+ * Inspired by CiviUnitTestCase::
+ * @todo - extract this function to own class and share with CiviUnitTestCase?
+ * @param array $params
+ * - type: string roughly matching entity type
+ * - seq: (optional) int sequence number for the values of this type
+ * - overrides: (optional) array of fill in parameters
+ *
+ * @return array
+ * - entity: string API entity type (usually the type supplied except for contact subtypes)
+ * - sample_params: array API sample_params properties of sample entity
+ */
+ public static function sample($params) {
+ $params += [
+ 'seq' => 0,
+ 'overrides' => [],
+ ];
+ $type = $params['type'];
+ // sample data - if field is array then chosed based on `seq`
+ $sample_params = [];
+ if (in_array($type, ['Individual', 'Organization', 'Household'])) {
+ $sample_params['contact_type'] = $type;
+ $entity = 'Contact';
+ }
+ else {
+ $entity = $type;
+ }
+ // use the seq to pluck a set of params out
+ foreach (self::sampleData($type) as $key => $value) {
+ if (is_array($value)) {
+ $sample_params[$key] = $value[$params['seq'] % count($value)];
+ }
+ else {
+ $sample_params[$key] = $value;
+ }
+ }
+ if ($type == 'Individual') {
+ $sample_params['email'] = strtolower(
+ $sample_params['first_name'] . '_' . $sample_params['last_name'] . '@civicrm.org'
+ );
+ $sample_params['prefix_id'] = 3;
+ $sample_params['suffix_id'] = 3;
+ }
+ if (!count($sample_params)) {
+ throw new \Exception("unknown sample type: $type");
+ }
+ $sample_params = $params['overrides'] + $sample_params;
+ // make foreign enitiies if they haven't been supplied
+ foreach ($sample_params as $key => $value) {
+ if (substr($value, 0, 6) === 'dummy.') {
+ $foreign_entity = self::createEntity([
+ 'type' => substr($value, 6),
+ 'seq' => $params['seq']]);
+ $sample_params[$key] = $foreign_entity['id'];
+ }
+ }
+ return compact("entity", "sample_params");
+ }
+
+ /**
+ * Provider of sample data.
+ *
+ * @return array
+ * Array values represent a set of allowable items.
+ * Strings in the form "dummy.Entity" require creating a foreign entity first.
+ */
+ public static function sampleData($type) {
+ $data = [
+ 'Individual' => [
+ // The number of values in each list need to be coprime numbers to not have duplicates
+ 'first_name' => ['Anthony', 'Joe', 'Terrence', 'Lucie', 'Albert', 'Bill', 'Kim'],
+ 'middle_name' => ['J.', 'M.', 'P', 'L.', 'K.', 'A.', 'B.', 'C.', 'D', 'E.', 'Z.'],
+ 'last_name' => ['Anderson', 'Miller', 'Smith', 'Collins', 'Peterson'],
+ 'contact_type' => 'Individual',
+ ],
+ 'Organization' => [
+ 'organization_name' => [
+ 'Unit Test Organization',
+ 'Acme',
+ 'Roberts and Sons',
+ 'Cryo Space Labs',
+ 'Sharper Pens',
+ ],
+ ],
+ 'Household' => [
+ 'household_name' => ['Unit Test household'],
+ ],
+ 'Event' => [
+ 'title' => 'Annual CiviCRM meet',
+ 'summary' => 'If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now',
+ 'description' => 'This event is intended to give brief idea about progess of CiviCRM and giving solutions to common user issues',
+ 'event_type_id' => 1,
+ 'is_public' => 1,
+ 'start_date' => 20081021,
+ 'end_date' => 20081023,
+ 'is_online_registration' => 1,
+ 'registration_start_date' => 20080601,
+ 'registration_end_date' => 20081015,
+ 'max_participants' => 100,
+ 'event_full_text' => 'Sorry! We are already full',
+ 'is_monetary' => 0,
+ 'is_active' => 1,
+ 'is_show_location' => 0,
+ ],
+ 'Participant' => [
+ 'event_id' => 'dummy.Event',
+ 'contact_id' => 'dummy.Individual',
+ 'status_id' => 2,
+ 'role_id' => 1,
+ 'register_date' => 20070219,
+ 'source' => 'Wimbeldon',
+ 'event_level' => 'Payment',
+ ],
+ 'Contribution' => [
+ 'contact_id' => 'dummy.Individual',
+ 'financial_type_id' => 1, // donation, 2 = member, 3 = campaign contribution, 4=event
+ 'total_amount' => 7.3,
+ ],
+ 'Activity' => [
+ //'activity_type_id' => 1,
+ 'subject' => 'unit testing',
+ 'source_contact_id' => 'dummy.Individual',
+ ],
+ ];
+ if ($type == 'Contact') {
+ $type = 'Individual';
+ }
+ return $data[$type];
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ArrayInsertionServiceTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ArrayInsertionServiceTest.php
new file mode 100644
index 00000000..462f07cc
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ArrayInsertionServiceTest.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Civi\Test\Api4\Utils;
+
+use Civi\Api4\Utils\ArrayInsertionUtil;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class ArrayInsertionServiceTest extends UnitTestCase {
+
+ public function testInsertWillWork() {
+ $arr = [];
+ $path = ['foo' => FALSE, 'bar' => FALSE];
+ $inserter = new ArrayInsertionUtil();
+ $inserter::insert($arr, $path, ['LALA']);
+
+ $expected = [
+ 'foo' => [
+ 'bar' => 'LALA'
+ ],
+ ];
+
+ $this->assertEquals($expected, $arr);
+ }
+
+ public function testInsertionOfContactEmailLocation() {
+ $contacts = [
+ [
+ 'id' => 1,
+ 'first_name' => 'Jim'
+ ],
+ [
+ 'id' => 2,
+ 'first_name' => 'Karen'
+ ],
+ ];
+ $emails = [
+ [
+ 'email' => 'jim@jim.com',
+ 'id' => 2,
+ '_parent_id' => 1
+ ],
+ ];
+ $locationTypes = [
+ [
+ 'name' => 'Home',
+ 'id' => 3,
+ '_parent_id' => 2
+ ],
+ ];
+
+ $emailPath = ['emails' => TRUE];
+ $locationPath = ['emails' => TRUE, 'location' => FALSE];
+ $inserter = new ArrayInsertionUtil();
+
+ foreach ($contacts as &$contact) {
+ $inserter::insert($contact, $emailPath, $emails);
+ $inserter::insert($contact, $locationPath, $locationTypes);
+ }
+
+ $locationType = $contacts[0]['emails'][0]['location']['name'];
+ $this->assertEquals('Home', $locationType);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ReflectionUtilsTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ReflectionUtilsTest.php
new file mode 100644
index 00000000..d94de337
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/Utils/ReflectionUtilsTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Civi\Test\Api4\Utils;
+
+use Civi\Api4\Utils\ReflectionUtils;
+use Civi\Test\Api4\Mock\MockV4ReflectionGrandchild;
+use Civi\Test\Api4\UnitTestCase;
+
+/**
+ * @group headless
+ */
+class ReflectionUtilsTest extends UnitTestCase {
+
+ /**
+ * Test that class annotations are returned across @inheritDoc
+ */
+ public function testGetDocBlockForClass() {
+ $grandChild = new MockV4ReflectionGrandchild();
+ $reflection = new \ReflectionClass($grandChild);
+ $doc = ReflectionUtils::getCodeDocs($reflection);
+
+ $this->assertEquals(TRUE, $doc['internal']);
+ $this->assertEquals('Grandchild class', $doc['description']);
+
+ $expectedComment = 'This is an extended description.
+
+There is a line break in this description.
+
+This is the base class.';
+
+ $this->assertEquals($expectedComment, $doc['comment']);
+ }
+
+ /**
+ * Test that property annotations are returned across @inheritDoc
+ */
+ public function testGetDocBlockForProperty() {
+ $grandChild = new MockV4ReflectionGrandchild();
+ $reflection = new \ReflectionClass($grandChild);
+ $doc = ReflectionUtils::getCodeDocs($reflection->getProperty('foo'), 'Property');
+
+ $this->assertEquals('This is the foo property.', $doc['description']);
+ $this->assertEquals("In the child class, foo has been barred.\n\nIn general, you can do nothing with it.", $doc['comment']);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/bootstrap.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/bootstrap.php
new file mode 100644
index 00000000..1c28e611
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/phpunit/bootstrap.php
@@ -0,0 +1,56 @@
+<?php
+
+define('CIVICRM_CONTAINER_CACHE', 'never');
+ini_set('memory_limit', '2G');
+ini_set('safe_mode', 0);
+$bootCode = cv('php:boot --level=classloader', 'phpcode');
+eval($bootCode);
+
+preg_match('/require_once\s*\'(.*)\'/', $bootCode, $matches);
+$loader = require sprintf('%s/vendor/autoload.php', $matches[1]);
+$loader->addPsr4('Civi\\Test\\Api4\\', __DIR__);
+$loader->addPsr4('Civi\\Api4\\', __DIR__ . '/Mock/Api4');
+
+/**
+ * Call the "cv" command.
+ *
+ * @param string $cmd
+ * The rest of the command to send.
+ * @param string $decode
+ * Ex: 'json' or 'phpcode'.
+ * @return string
+ * Response output (if the command executed normally).
+ * @throws \RuntimeException
+ * If the command terminates abnormally.
+ */
+function cv($cmd, $decode = 'json') {
+ $cmd = 'cv ' . $cmd;
+ $descriptorSpec = [0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => STDERR];
+ $oldOutput = getenv('CV_OUTPUT');
+ putenv("CV_OUTPUT=json");
+ $process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__);
+ putenv("CV_OUTPUT=$oldOutput");
+ fclose($pipes[0]);
+ $result = stream_get_contents($pipes[1]);
+ fclose($pipes[1]);
+ if (proc_close($process) !== 0) {
+ throw new RuntimeException("Command failed ($cmd):\n$result");
+ }
+ switch ($decode) {
+ case 'raw':
+ return $result;
+
+ case 'phpcode':
+ // If the last output is /*PHPCODE*/, then we managed to complete execution.
+ if (substr(trim($result), 0, 12) !== "/*BEGINPHP*/" || substr(trim($result), -10) !== "/*ENDPHP*/") {
+ throw new \RuntimeException("Command failed ($cmd):\n$result");
+ }
+ return $result;
+
+ case 'json':
+ return json_decode($result, 1);
+
+ default:
+ throw new RuntimeException("Bad decoder format ($decode)");
+ }
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/services.xml b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/services.xml
new file mode 100644
index 00000000..220ba910
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/tests/services.xml
@@ -0,0 +1,10 @@
+<container xmlns="http://symfony.com/schema/dic/services"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+
+ <services>
+ <service id="test.param_provider" class="Civi\Test\Api4\Service\TestCreationParameterProvider">
+ <argument type="service" id="spec_gatherer"/>
+ </service>
+ </services>
+</container>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/xml/Menu/api4.xml b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/xml/Menu/api4.xml
new file mode 100644
index 00000000..1973f234
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/xml/Menu/api4.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<menu>
+ <item>
+ <path>civicrm/ajax/api4</path>
+ <page_callback>CRM_Api4_Page_AJAX</page_callback>
+ <access_arguments>access CiviCRM</access_arguments>
+ </item>
+ <item>
+ <path>civicrm/api4</path>
+ <page_callback>CRM_Api4_Page_Api4Explorer</page_callback>
+ <title>CiviCRM</title>
+ <access_arguments>access CiviCRM</access_arguments>
+ </item>
+</menu>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php
new file mode 100644
index 00000000..2dca9cca
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php
@@ -0,0 +1,486 @@
+<?php
+
+/**
+ * @file Copyright iATS Payments (c) 2014.
+ * @author Alan Dixon
+ *
+ * This file is a part of CiviCRM published extension.
+ *
+ * This extension is free software; you can copy, modify, and distribute it
+ * under the terms of the GNU Affero General Public License
+ * Version 3, 19 November 2007.
+ *
+ * It is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License with this program; if not, see http://www.gnu.org/licenses/
+ *
+ * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the iATS_Service_Request object
+ */
+
+/**
+ *
+ */
+class CRM_Core_Payment_iATSService extends CRM_Core_Payment {
+
+ /**
+ * We only need one instance of this object. So we use the singleton
+ * pattern and cache the instance in this variable.
+ *
+ * @var object
+ * @static
+ */
+ static private $_singleton = NULL;
+
+ /**
+ * Constructor.
+ *
+ * @param string $mode
+ * the mode of operation: live or test.
+ *
+ * @param array $paymentProcessor
+ */
+ public function __construct($mode, &$paymentProcessor, &$paymentForm = NULL, $force = FALSE) {
+ $this->_paymentProcessor = $paymentProcessor;
+ $this->_processorName = ts('iATS Payments');
+
+ // Get merchant data from config.
+ $config = CRM_Core_Config::singleton();
+ // Live or test.
+ $this->_profile['mode'] = $mode;
+ // We only use the domain of the configured url, which is different for NA vs. UK.
+ $this->_profile['iats_domain'] = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
+ }
+
+ /**
+ *
+ */
+ static public function &singleton($mode, &$paymentProcessor, &$paymentForm = NULL, $force = FALSE) {
+ $processorName = $paymentProcessor['name'];
+ if (self::$_singleton[$processorName] === NULL) {
+ self::$_singleton[$processorName] = new CRM_Core_Payment_iATSService($mode, $paymentProcessor);
+ }
+ return self::$_singleton[$processorName];
+ }
+
+ /**
+ * Get the iATS configuration values or value.
+ *
+ * Mangle the days settings to make it easier to test if it is set.
+ */
+ protected function getSettings($key = '') {
+ static $settings = array();
+ if (empty($settings)) {
+ try {
+ $settings = civicrm_api3('Setting', 'getvalue', array('name' => 'iats_settings'));
+ if (empty($settings['days'])) {
+ $settings['days'] = array('-1');
+ }
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ // Assume no settings exist, use safest fallback.
+ $settings = array('days' => array('-1'));
+ }
+ }
+ return (empty($key) ? $settings : (empty($settings[$key]) ? '' : $settings[$key]));
+ }
+
+ /**
+ * Override the default way of testing if a method is supported to enable admin configuration of certain
+ * functions.
+ * Where certain functions currently only means updateSubscriptionBillingInfo, which we'll allow for credit cards.
+ *
+ * Core says this method is now deprecated, so I might need to change this in the future, but this is how it is used now.
+ */
+ public function isSupported($method) {
+ switch($method) {
+ case 'updateSubscriptionBillingInfo':
+ if ('CRM_Core_Payment_iATSServiceACHEFT' == CRM_Utils_System::getClassName($this)) {
+ return FALSE;
+ }
+ elseif (!CRM_Core_Permission::check('access CiviContribution')) {
+ // disable self-service update of billing info if the admin has not allowed it
+ if (FALSE == $this->getSettings('enable_update_subscription_billing_info')) {
+ return FALSE;
+ }
+ }
+ break;
+ }
+ // this is the default method
+ return method_exists(CRM_Utils_System::getClassName($this), $method);
+ }
+
+ /**
+ * The first payment date is configurable when setting up back office recurring payments.
+ * For iATSPayments, this is also true for front-end recurring payments.
+ *
+ * @return bool
+ */
+ public function supportsFutureRecurStartDate() {
+ return TRUE;
+ }
+
+ /**
+ *
+ */
+ public function doDirectPayment(&$params) {
+
+ if (!$this->_profile) {
+ return self::error('Unexpected error, missing profile');
+ }
+ // Use the iATSService object for interacting with iATS. Recurring contributions go through a more complex process.
+ require_once "CRM/iATS/iATSService.php";
+ $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID'];
+ $methodType = $isRecur ? 'customer' : 'process';
+ $method = $isRecur ? 'create_credit_card_customer' : 'cc';
+ $iats = new iATS_Service_Request(array('type' => $methodType, 'method' => $method, 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
+ $request = $this->convertParams($params, $method);
+ $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
+ $credentials = array(
+ 'agentCode' => $this->_paymentProcessor['user_name'],
+ 'password' => $this->_paymentProcessor['password'],
+ );
+ // Get the API endpoint URL for the method's transaction mode.
+ // TODO: enable override of the default url in the request object
+ // $url = $this->_paymentProcessor['url_site'];.
+ // Make the soap request.
+ $response = $iats->request($credentials, $request);
+ if (!$isRecur) {
+ // Process the soap response into a readable result, logging any credit card transactions.
+ $result = $iats->result($response);
+ if ($result['status']) {
+ // Success.
+ $params['contribution_status_id'] = 1;
+ // For versions >= 4.6.6, the proper key.
+ $params['payment_status_id'] = 1;
+ $params['trxn_id'] = trim($result['remote_id']) . ':' . time();
+ $params['gross_amount'] = $params['amount'];
+ return $params;
+ }
+ else {
+ return self::error($result['reasonMessage']);
+ }
+ }
+ else {
+ // Save the client info in my custom table, then (maybe) run the transaction.
+ $customer = $iats->result($response, FALSE);
+ // print_r($customer);
+ if ($customer['status']) {
+ $processresult = $response->PROCESSRESULT;
+ $customer_code = (string) $processresult->CUSTOMERCODE;
+ $exp = sprintf('%02d%02d', ($params['year'] % 100), $params['month']);
+ $email = '';
+ if (isset($params['email'])) {
+ $email = $params['email'];
+ }
+ elseif (isset($params['email-5'])) {
+ $email = $params['email-5'];
+ }
+ elseif (isset($params['email-Primary'])) {
+ $email = $params['email-Primary'];
+ }
+ $query_params = array(
+ 1 => array($customer_code, 'String'),
+ 2 => array($request['customerIPAddress'], 'String'),
+ 3 => array($exp, 'String'),
+ 4 => array($params['contactID'], 'Integer'),
+ 5 => array($email, 'String'),
+ 6 => array($params['contributionRecurID'], 'Integer'),
+ );
+ CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_customer_codes
+ (customer_code, ip, expiry, cid, email, recur_id) VALUES (%1, %2, %3, %4, %5, %6)", $query_params);
+ // Test for admin setting that limits allowable transaction days
+ $allow_days = $this->getSettings('days');
+ // Also test for a specific recieve date request that is not today.
+ $receive_date_request = CRM_Utils_Array::value('receive_date', $params);
+ $today = date('Ymd');
+ // If the receive_date is set to sometime today, unset it.
+ if (!empty($receive_date_request) && 0 === strpos($receive_date_request, $today)) {
+ unset($receive_date_request);
+ }
+ // Normally, run the (first) transaction immediately, unless the admin setting is in force or a specific request is being made.
+ if (max($allow_days) <= 0 && empty($receive_date_request)) {
+ $iats = new iATS_Service_Request(array('type' => 'process', 'method' => 'cc_with_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
+ $request = array('invoiceNum' => $params['invoiceID']);
+ $request['total'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount']));
+ $request['customerCode'] = $customer_code;
+ $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
+ $response = $iats->request($credentials, $request);
+ $result = $iats->result($response);
+ if ($result['status']) {
+ // Add a time string to iATS short authentication string to ensure uniqueness and provide helpful referencing.
+ $update = array(
+ 'trxn_id' => trim($result['remote_id']) . ':' . time(),
+ 'gross_amount' => $params['amount'],
+ 'payment_status_id' => '1',
+ );
+ // Setting the next_sched_contribution_date param doesn't do anything, commented out, work around in setRecurReturnParams
+ $params = $this->setRecurReturnParams($params, $update);
+ return $params;
+ }
+ else {
+ return self::error($result['reasonMessage']);
+ }
+ }
+ // I've got a schedule to adhere to!
+ else {
+ // Note that the admin general setting restricting allowable days will overwrite any specific request.
+ $next_sched_contribution_timestamp = (max($allow_days) > 0) ? _iats_contributionrecur_next(time(), $allow_days)
+ : strtotime($params['receive_date']);
+ // set the receieve time to 3:00 am for a better admin experience
+ $update = array(
+ 'payment_status_id' => 'Pending',
+ 'receive_date' => date('Ymd', $next_sched_contribution_timestamp) . '030000',
+ );
+ $params = $this->setRecurReturnParams($params, $update);
+ return $params;
+ }
+ return self::error('Unexpected error');
+ }
+ else {
+ return self::error($customer['reasonMessage']);
+ }
+ }
+ }
+
+ /**
+ * support corresponding CiviCRM method
+ */
+ public function changeSubscriptionAmount(&$message = '', $params = array()) {
+ // $userAlert = ts('You have modified this recurring contribution.');
+ // CRM_Core_Session::setStatus($userAlert, ts('Warning'), 'alert');
+ return TRUE;
+ }
+
+ /**
+ * support corresponding CiviCRM method
+ */
+ public function cancelSubscription(&$message = '', $params = array()) {
+ $userAlert = ts('You have cancelled this recurring contribution.');
+ CRM_Core_Session::setStatus($userAlert, ts('Warning'), 'alert');
+ return TRUE;
+ }
+
+ /**
+ * Set additional fields when editing the schedule.
+ *
+ * Note: this doesn't completely replace the form hook, which is still
+ * in use for additional changes, and to support 4.6.
+ * e.g. the commented out fields below don't work properly here.
+ */
+ public function getEditableRecurringScheduleFields() {
+ return array('amount',
+ 'installments',
+ 'next_sched_contribution_date',
+// 'contribution_status_id',
+// 'start_date',
+// 'is_email_receipt',
+ );
+ }
+
+ /*
+ * Set a useful message at the top of the schedule editing form
+ */
+ public function getRecurringScheduleUpdateHelpText() {
+ return 'Use this form to change the amount or number of installments for this recurring contribution. You can not change the contribution frequency.<br />You can also modify the next scheduled contribution date, and whether or not the recipient will get email receipts for each contribution.<br />You have an option to notify the donor of these changes.';
+ }
+
+ /**
+ *
+ */
+ public function &error($error = NULL) {
+ $e = CRM_Core_Error::singleton();
+ if (is_object($error)) {
+ $e->push($error->getResponseCode(),
+ 0, NULL,
+ $error->getMessage()
+ );
+ }
+ elseif ($error && is_numeric($error)) {
+ $e->push($error,
+ 0, NULL,
+ $this->errorString($error)
+ );
+ }
+ elseif (is_string($error)) {
+ $e->push(9002,
+ 0, NULL,
+ $error
+ );
+ }
+ else {
+ $e->push(9001, 0, NULL, "Unknown System Error.");
+ }
+ return $e;
+ }
+
+ /**
+ * This function checks to see if we have the right config values.
+ *
+ * @return string
+ * The error message if any
+ */
+ public function checkConfig() {
+ $error = array();
+
+ if (empty($this->_paymentProcessor['user_name'])) {
+ $error[] = ts('Agent Code is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
+ }
+
+ if (empty($this->_paymentProcessor['password'])) {
+ $error[] = ts('Password is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
+ }
+
+ if (!empty($error)) {
+ return implode('<p>', $error);
+ }
+ else {
+ return NULL;
+ }
+ }
+
+ /**
+ * Convert the values in the civicrm params to the request array with keys as expected by iATS.
+ *
+ * @param array $params
+ * @param string $method
+ *
+ * @return array
+ */
+ protected function convertParams($params, $method) {
+ $request = array();
+ $convert = array(
+ 'firstName' => 'billing_first_name',
+ 'lastName' => 'billing_last_name',
+ 'address' => 'street_address',
+ 'city' => 'city',
+ 'state' => 'state_province',
+ 'zipCode' => 'postal_code',
+ 'country' => 'country',
+ 'invoiceNum' => 'invoiceID',
+ 'creditCardNum' => 'credit_card_number',
+ 'cvv2' => 'cvv2',
+ );
+
+ foreach ($convert as $r => $p) {
+ if (isset($params[$p])) {
+ $request[$r] = htmlspecialchars($params[$p]);
+ }
+ }
+ $request['creditCardExpiry'] = sprintf('%02d/%02d', $params['month'], ($params['year'] % 100));
+ $request['total'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount']));
+ // Place for ugly hacks.
+ switch ($method) {
+ case 'cc_create_customer_code':
+ $request['ccNum'] = $request['creditCardNum'];
+ unset($request['creditCardNum']);
+ $request['ccExp'] = $request['creditCardExpiry'];
+ unset($request['creditCardExpiry']);
+ break;
+
+ case 'cc_with_customer_code':
+ foreach (array('creditCardNum', 'creditCardExpiry', 'mop') as $key) {
+ if (isset($request[$key])) {
+ unset($request[$key]);
+ }
+ }
+ break;
+ }
+ if (!empty($params['credit_card_type'])) {
+ $mop = array(
+ 'Visa' => 'VISA',
+ 'MasterCard' => 'MC',
+ 'Amex' => 'AMX',
+ 'Discover' => 'DSC',
+ );
+ $request['mop'] = $mop[$params['credit_card_type']];
+ }
+ // print_r($request); print_r($params); die();
+ return $request;
+ }
+
+ /*
+ * Implement the ability to update the billing info for recurring contributions,
+ * This functionality will apply to back-end and front-end,
+ * so it's only enabled when configured as on via the iATS admin settings.
+ * The default isSupported method is overridden above to achieve this.
+ */
+ public function updateSubscriptionBillingInfo(&$message = '', $params = array()) {
+ require_once('CRM/iATS/Form/IATSCustomerUpdateBillingInfo.php');
+
+ $fakeForm = new IATSCustomerUpdateBillingInfo();
+ $fakeForm->updatedBillingInfo = $params;
+ try {
+ $fakeForm->postProcess();
+ }
+ catch (Exception $error) { // what could go wrong?
+ $message = $error->getMessage();
+ CRM_Core_Session::setStatus($message, ts('Warning'), 'alert');
+ $e = CRM_Core_Error::singleton();
+ return $e;
+ }
+ if ('OK' == $fakeForm->getAuthorizationResult()) {
+ return TRUE;
+ }
+ $message = $fakeForm->getResultMessage();
+ CRM_Core_Session::setStatus($message, ts('Warning'), 'alert');
+ $e = CRM_Core_Error::singleton();
+ return $e;
+ }
+
+ /*
+ * Set the return params for recurring contributions.
+ *
+ * Implemented as a function so I can do some cleanup and implement
+ * the ability to set a future start date for recurring contributions.
+ * This functionality will apply to back-end and front-end,
+ * As enabled when configured via the iATS admin settings.
+ *
+ * This function will alter the recurring schedule as an intended side effect.
+ * and return the modified the params.
+ */
+ protected function setRecurReturnParams($params, $update) {
+ // Merge in the updates
+ $params = array_merge($params, $update);
+ // If the recurring record already exists, let's fix the next contribution and start dates,
+ // in case core isn't paying attention.
+ // We also set the schedule to 'in-progress' (even for ACH/EFT when the first one hasn't been verified),
+ // because we want the recurring job to run for this schedule.
+ if (!empty($params['contributionRecurID'])) {
+ $recur_id = $params['contributionRecurID'];
+ $recur_update = array(
+ 'id' => $recur_id,
+ 'contribution_status_id' => 'In Progress',
+ );
+ // use the receive date to set the next sched contribution date.
+ // By default, it's empty, unless we've got a future start date.
+ if (empty($update['receive_date'])) {
+ $next = strtotime('+' . $params['frequency_interval'] . ' ' . $params['frequency_unit']);
+ $recur_update['next_sched_contribution_date'] = date('Ymd', $next) . '030000';
+ }
+ else {
+ $recur_update['start_date'] = $recur_update['next_sched_contribution_date'] = $update['receive_date'];
+ // If I've got a monthly schedule, let's set the cycle_day for niceness
+ if ('month' == $params['frequency_interval']) {
+ $recur_update['cycle_day'] = date('j', strtotime($recur_update['start_date']));
+ }
+ }
+ try {
+ $result = civicrm_api3('ContributionRecur', 'create', $recur_update);
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ // Not a critical error, just log and continue.
+ $error = $e->getMessage();
+ Civi::log()->info('Unexpected error updating the next scheduled contribution date for id {id}: {error}', array('id' => $recur_id, 'error' => $error));
+ }
+ }
+ else {
+ Civi::log()->info('Unexpectedly unable to update the next scheduled contribution date, missing id.');
+ }
+ return $params;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php
new file mode 100644
index 00000000..a93c32b2
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php
@@ -0,0 +1,334 @@
+<?php
+
+/**
+ * @file
+ * Copyright iATS Payments (c) 2014.
+ * @author Alan Dixon
+ *
+ * This file is a part of CiviCRM published extension.
+ *
+ * This extension is free software; you can copy, modify, and distribute it
+ * under the terms of the GNU Affero General Public License
+ * Version 3, 19 November 2007.
+ *
+ * It is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License with this program; if not, see http://www.gnu.org/licenses/
+ *
+ * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the iATS_Service_Request object
+ */
+
+/**
+ *
+ */
+class CRM_Core_Payment_iATSServiceACHEFT extends CRM_Core_Payment_iATSService {
+
+ /**
+ * We only need one instance of this object. So we use the singleton
+ * pattern and cache the instance in this variable.
+ *
+ * @var object
+ * @static
+ */
+ static private $_singleton = NULL;
+
+ /**
+ * Constructor.
+ *
+ * @param string $mode
+ * the mode of operation: live or test.
+ * @param array $paymentProcessor
+ */
+ public function __construct($mode, &$paymentProcessor) {
+ $this->_paymentProcessor = $paymentProcessor;
+ $this->_processorName = ts('iATS Payments ACHEFT');
+
+ // Live or test.
+ $this->_profile['mode'] = $mode;
+ // We only use the domain of the configured url, which is different for NA vs. UK.
+ $this->_profile['iats_domain'] = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
+ }
+
+ /**
+ *
+ */
+ static public function &singleton($mode, &$paymentProcessor, &$paymentForm = NULL, $force = FALSE) {
+ $processorName = $paymentProcessor['name'];
+ if (self::$_singleton[$processorName] === NULL) {
+ self::$_singleton[$processorName] = new CRM_Core_Payment_iATSServiceACHEFT($mode, $paymentProcessor);
+ }
+ return self::$_singleton[$processorName];
+ }
+
+ /**
+ *
+ */
+ public function doDirectPayment(&$params) {
+
+ if (!$this->_profile) {
+ return self::error('Unexpected error, missing profile');
+ }
+ // Use the iATSService object for interacting with iATS, mostly the same for recurring contributions.
+ require_once "CRM/iATS/iATSService.php";
+ // We handle both one-time and recurring ACH/EFT
+ $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID'];
+ $methodType = $isRecur ? 'customer' : 'process';
+ $method = $isRecur ? 'create_acheft_customer_code' : 'acheft';
+ $iats = new iATS_Service_Request(array('type' => $methodType, 'method' => $method, 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
+ $request = $this->convertParams($params, $method);
+ $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
+ $credentials = array(
+ 'agentCode' => $this->_paymentProcessor['user_name'],
+ 'password' => $this->_paymentProcessor['password'],
+ );
+ // Make the soap request.
+ $response = $iats->request($credentials, $request);
+ if (!$isRecur) {
+ // Process the soap response into a readable result, logging any transaction.
+ $result = $iats->result($response);
+ if ($result['status']) {
+ // Always set pending status.
+ $params['contribution_status_id'] = 2;
+ // For future versions, the proper key.
+ $params['payment_status_id'] = 2;
+ $params['trxn_id'] = trim($result['remote_id']) . ':' . time();
+ $params['gross_amount'] = $params['amount'];
+ // Core assumes that a pending result will have no transaction id, but we have a useful one.
+ if (!empty($params['contributionID'])) {
+ $contribution_update = array('id' => $params['contributionID'], 'trxn_id' => $params['trxn_id']);
+ try {
+ $result = civicrm_api3('Contribution', 'create', $contribution_update);
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ // Not a critical error, just log and continue.
+ $error = $e->getMessage();
+ Civi::log()->info('Unexpected error adding the trxn_id for contribution id {id}: {error}', array('id' => $recur_id, 'error' => $error));
+ }
+ }
+ return $params;
+ }
+ else {
+ return self::error($result['reasonMessage']);
+ }
+ }
+ else {
+ // Save the client info in my custom table
+ $customer = $iats->result($response);
+ if (!$customer['status']) {
+ return self::error($customer['reasonMessage']);
+ }
+ else {
+ $processresult = $response->PROCESSRESULT;
+ $customer_code = (string) $processresult->CUSTOMERCODE;
+ // $exp = sprintf('%02d%02d', ($params['year'] % 100), $params['month']);.
+ $exp = '0000';
+ $email = '';
+ if (isset($params['email'])) {
+ $email = $params['email'];
+ }
+ elseif (isset($params['email-5'])) {
+ $email = $params['email-5'];
+ }
+ elseif (isset($params['email-Primary'])) {
+ $email = $params['email-Primary'];
+ }
+ $query_params = array(
+ 1 => array($customer_code, 'String'),
+ 2 => array($request['customerIPAddress'], 'String'),
+ 3 => array($exp, 'String'),
+ 4 => array($params['contactID'], 'Integer'),
+ 5 => array($email, 'String'),
+ 6 => array($params['contributionRecurID'], 'Integer'),
+ );
+ CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_customer_codes
+ (customer_code, ip, expiry, cid, email, recur_id) VALUES (%1, %2, %3, %4, %5, %6)", $query_params);
+ $allow_days = $this->getSettings('days');
+ // Also test for a specific recieve date request that is not today.
+ $receive_date_request = CRM_Utils_Array::value('receive_date', $params);
+ $today = date('Ymd');
+ // If the receive_date is set to sometime today, unset it.
+ if (!empty($receive_date_request) && 0 === strpos($receive_date_request, $today)) {
+ unset($receive_date_request);
+ }
+ // Normally, run the (first) transaction immediately, unless the admin setting is in force or a specific request is being made.
+ if (max($allow_days) <= 0 && empty($receive_date_request)) {
+ $iats = new iATS_Service_Request(array('type' => 'process', 'method' => 'acheft_with_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
+ $request = array('invoiceNum' => $params['invoiceID']);
+ $request['total'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount']));
+ $request['customerCode'] = $customer_code;
+ $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
+ $response = $iats->request($credentials, $request);
+ $result = $iats->result($response);
+ if ($result['status']) {
+ // Add a time string to iATS short authentication string to ensure uniqueness and provide helpful referencing.
+ $update = array(
+ 'trxn_id' => trim($result['remote_id']) . ':' . time(),
+ 'gross_amount' => $params['amount'],
+ 'payment_status_id' => 2
+ );
+ // Setting the next_sched_contribution_date param setting is not doing anything, commented out.
+ $this->setRecurReturnParams($params, $update);
+ // Core assumes that a pending result will have no transaction id, but we have a useful one.
+ if (!empty($params['contributionID'])) {
+ $contribution_update = array('id' => $params['contributionID'], 'trxn_id' => $update['trxn_id']);
+ try {
+ $result = civicrm_api3('Contribution', 'create', $contribution_update);
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ // Not a critical error, just log and continue.
+ $error = $e->getMessage();
+ Civi::log()->info('Unexpected error adding the trxn_id for contribution id {id}: {error}', array('id' => $recur_id, 'error' => $error));
+ }
+ }
+ return $params;
+ }
+ else {
+ return self::error($result['reasonMessage']);
+ }
+ }
+ // Otherwise, I have a schedule to adhere to.
+ else {
+ // Note that the admin general setting restricting allowable days may update a specific request.
+ $receive_timestamp = empty($receive_date_request) ? time() : strtotime($receive_date_request);
+ $next_sched_contribution_timestamp = (max($allow_days) > 0) ? _iats_contributionrecur_next($receive_timestamp, $allow_days)
+ : $receive_timestamp;
+ // set the receieve time to 3:00 am for a better admin experience
+ $update = array(
+ 'payment_status_id' => 2,
+ 'receive_date' => date('Ymd', $next_sched_contribution_timestamp) . '030000',
+ );
+ $this->setRecurReturnParams($params, $update);
+ return $params;
+ }
+ }
+ return $params;
+ }
+ }
+
+ /**
+ *
+ */
+ public function changeSubscriptionAmount(&$message = '', $params = array()) {
+ $userAlert = ts('You have updated the amount of this recurring contribution.');
+ CRM_Core_Session::setStatus($userAlert, ts('Warning'), 'alert');
+ return TRUE;
+ }
+
+ /**
+ *
+ */
+ public function cancelSubscription(&$message = '', $params = array()) {
+ $userAlert = ts('You have cancelled this recurring contribution.');
+ CRM_Core_Session::setStatus($userAlert, ts('Warning'), 'alert');
+ return TRUE;
+ }
+
+ /**
+ *
+ */
+ public function &error($error = NULL) {
+ $e = CRM_Core_Error::singleton();
+ if (is_object($error)) {
+ $e->push($error->getResponseCode(),
+ 0, NULL,
+ $error->getMessage()
+ );
+ }
+ elseif ($error && is_numeric($error)) {
+ $e->push($error,
+ 0, NULL,
+ $this->errorString($error)
+ );
+ }
+ elseif (is_string($error)) {
+ $e->push(9002,
+ 0, NULL,
+ $error
+ );
+ }
+ else {
+ $e->push(9001, 0, NULL, "Unknown System Error.");
+ }
+ return $e;
+ }
+
+ /**
+ * Are back office payments supported.
+ *
+ * @return bool
+ */
+ protected function supportsBackOffice() {
+ return TRUE;
+ }
+
+ /**
+ * This function checks to see if we have the right config values.
+ *
+ * @param string $mode
+ * the mode we are operating in (live or test)
+ *
+ * @return string the error message if any
+ *
+ * @public
+ */
+ public function checkConfig() {
+ $error = array();
+
+ if (empty($this->_paymentProcessor['user_name'])) {
+ $error[] = ts('Agent Code is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
+ }
+
+ if (empty($this->_paymentProcessor['password'])) {
+ $error[] = ts('Password is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
+ }
+
+ if (!empty($error)) {
+ return implode('<p>', $error);
+ }
+ else {
+ return NULL;
+ }
+ }
+
+ /**
+ * Convert the values in the civicrm params to the request array with keys as expected by iATS.
+ */
+ public function convertParams($params, $method) {
+ $request = array();
+ $convert = array(
+ 'firstName' => 'billing_first_name',
+ 'lastName' => 'billing_last_name',
+ 'address' => 'street_address',
+ 'city' => 'city',
+ 'state' => 'state_province',
+ 'zipCode' => 'postal_code',
+ 'country' => 'country',
+ 'invoiceNum' => 'invoiceID',
+ /* 'accountNum' => 'bank_account_number', */
+ 'accountType' => 'bank_account_type',
+ );
+
+ foreach ($convert as $r => $p) {
+ if (isset($params[$p])) {
+ $request[$r] = $params[$p];
+ }
+ }
+ $request['total'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount']));
+ // Place for ugly hacks.
+ switch ($method) {
+ case 'acheft':
+ case 'create_acheft_customer_code':
+ case 'acheft_create_customer_code':
+ // Add bank number + transit to account number
+ // TODO: verification?
+ $request['accountNum'] = preg_replace('/^0-9]/', '', $params['bank_identification_number'] . $params['bank_account_number']);
+ break;
+ }
+ return $request;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceSWIPE.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceSWIPE.php
new file mode 100644
index 00000000..decbcdd0
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceSWIPE.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file Copyright iATS Payments (c) 2014.
+ * @author Alan Dixon
+ *
+ * This file is a part of CiviCRM published extension.
+ *
+ * This extension is free software; you can copy, modify, and distribute it
+ * under the terms of the GNU Affero General Public License
+ * Version 3, 19 November 2007.
+ *
+ * It is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License with this program; if not, see http://www.gnu.org/licenses/
+ *
+ * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the iATS_Service_Request object
+ */
+
+/**
+ *
+ */
+class CRM_Core_Payment_iATSServiceSWIPE extends CRM_Core_Payment_iATSService {
+
+ /**
+ * We only need one instance of this object. So we use the singleton
+ * pattern and cache the instance in this variable.
+ *
+ * @var object
+ * @static
+ */
+ static private $_singleton = NULL;
+
+ /**
+ * Constructor.
+ *
+ * @param string $mode
+ * the mode of operation: live or test.
+ *
+ * @return void
+ */
+ public function __construct($mode, &$paymentProcessor) {
+ $this->_paymentProcessor = $paymentProcessor;
+ $this->_processorName = ts('iATS Payments SWIPE');
+
+ // Get merchant data from config.
+ $config = CRM_Core_Config::singleton();
+ // Live or test.
+ $this->_profile['mode'] = $mode;
+ // We only use the domain of the configured url, which is different for NA vs. UK.
+ $this->_profile['iats_domain'] = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
+ }
+
+ /**
+ *
+ */
+ static public function &singleton($mode, &$paymentProcessor, &$paymentForm = NULL, $force = FALSE) {
+ $processorName = $paymentProcessor['name'];
+ if (self::$_singleton[$processorName] === NULL) {
+ self::$_singleton[$processorName] = new CRM_Core_Payment_iATSServiceSWIPE($mode, $paymentProcessor);
+ }
+ return self::$_singleton[$processorName];
+ }
+
+ /**
+ *
+ */
+ public function validatePaymentInstrument($values, &$errors) {
+ // Override the default and don't do any validation because my values are encrypted.
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceUKDD.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceUKDD.php
new file mode 100644
index 00000000..16d827ce
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceUKDD.php
@@ -0,0 +1,341 @@
+<?php
+
+/**
+ * @file Copyright iATS Payments (c) 2014.
+ * @author Alan Dixon
+ *
+ * This file is a part of CiviCRM published extension.
+ *
+ * This extension is free software; you can copy, modify, and distribute it
+ * under the terms of the GNU Affero General Public License
+ * Version 3, 19 November 2007.
+ *
+ * It is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License with this program; if not, see http://www.gnu.org/licenses/
+ *
+ * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the iATS_Service_Request object
+ * for UK Direct Debit Recurring contributions ONLY
+ */
+
+/**
+ *
+ */
+class CRM_Core_Payment_iATSServiceUKDD extends CRM_Core_Payment {
+
+ /**
+ * We only need one instance of this object. So we use the singleton
+ * pattern and cache the instance in this variable.
+ *
+ * @var object
+ * @static
+ */
+ static private $_singleton = NULL;
+
+ /**
+ * Constructor.
+ *
+ * @param string $mode
+ * the mode of operation: live or test.
+ *
+ * @return void
+ */
+ public function __construct($mode, &$paymentProcessor) {
+ $this->_paymentProcessor = $paymentProcessor;
+ $this->_processorName = ts('iATS Payments UK Direct Debit');
+
+ // Get merchant data from config.
+ $config = CRM_Core_Config::singleton();
+ // Live or test.
+ $this->_profile['mode'] = $mode;
+ // We only use the domain of the configured url, which is different for NA vs. UK.
+ $this->_profile['iats_domain'] = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
+ }
+
+ /**
+ *
+ */
+ static public function &singleton($mode, &$paymentProcessor, &$paymentForm = NULL, $force = FALSE) {
+ $processorName = $paymentProcessor['name'];
+ if (self::$_singleton[$processorName] === NULL) {
+ self::$_singleton[$processorName] = new CRM_Core_Payment_iATSServiceUKDD($mode, $paymentProcessor);
+ }
+ return self::$_singleton[$processorName];
+ }
+
+ /**
+ * Function checkParams.
+ */
+ public function checkParams($params) {
+ if (!$this->_profile) {
+ return self::error('Unexpected error, missing profile');
+ }
+ $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID'];
+ if (!$isRecur) {
+ return self::error('Not a recurring contribution: you can only use UK Direct Debit with a recurring contribution.');
+ }
+ if ('GBP' != $params['currencyID']) {
+ return self::error(ts('Invalid currency %1, must by GBP', array(1 => $params['currencyID'])));
+ }
+ if (empty($params['installments'])) {
+ return self::error(ts('You must specify the number of installments, open-ended contributions are not allowed.'));
+ }
+ elseif (1 >= $params['installments']) {
+ return self::error(ts('You must specify a number of installments greater than 1.'));
+ }
+ }
+
+ /**
+ *
+ */
+ public function getSchedule($params) {
+ // Convert params recurring information into iATS equivalents.
+ $scheduleType = NULL;
+ $paymentsRecur = $params['installments'] - 1;
+ // IATS requires begin and end date, calculated here
+ // to be converted to date format later
+ // begin date has to be more than 12 days from now, not checked here.
+ $beginTime = strtotime($beginDate = $params['payer_validate_start_date']);
+ $date = getdate($beginTime);
+ $interval = $params['frequency_interval'] ? $params['frequency_interval'] : 1;
+ switch ($params['frequency_unit']) {
+ case 'week':
+ if (1 != $interval) {
+ return self::error(ts('You can only choose each week on a weekly schedule.'));
+ }
+ $scheduleType = 'Weekly';
+ $scheduleDate = $date['wday'] + 1;
+ $endTime = $beginTime + ($paymentsRecur * 7 * 24 * 60 * 60);
+ break;
+
+ case 'month':
+ $scheduleType = 'Monthly';
+ $scheduleDate = $date['mday'];
+ if (3 == $interval) {
+ $scheduleType = 'Quarterly';
+ $scheduleDate = '';
+ }
+ elseif (1 != $interval) {
+ return self::error(ts('You can only choose monthly or every three months (quarterly) for a monthly schedule.'));
+ }
+ $date['mon'] += ($interval * $paymentsRecur);
+ while ($date['mon'] > 12) {
+ $date['mon'] -= 12;
+ $date['year'] += 1;
+ }
+ $endTime = mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']);
+ break;
+
+ case 'year':
+ if (1 != $interval) {
+ return self::error(ts('You can only choose each year for a yearly schedule.'));
+ }
+ $scheduleType = 'Yearly';
+ $scheduleDate = '';
+ $date['year'] += $paymentsRecur;
+ $endTime = mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']);
+ break;
+
+ default:
+ return self::error(ts('Invalid frequency unit: %1', array(1 => $params['frequency_unit'])));
+ break;
+
+ }
+ $endDate = date('c', $endTime);
+ $beginDate = date('c', $beginTime);
+ return array('scheduleType' => $scheduleType, 'scheduleDate' => $scheduleDate, 'endDate' => $endDate, 'beginDate' => $beginDate);
+ }
+
+ /**
+ *
+ */
+ public function doDirectPayment(&$params) {
+ $error = $this->checkParams($params);
+ if (!empty($error)) {
+ return $error;
+ }
+ // $params['start_date'] = $params['receive_date'];
+ // use the iATSService object for interacting with iATS.
+ require_once "CRM/iATS/iATSService.php";
+ $iats = new iATS_Service_Request(array('type' => 'customer', 'method' => 'direct_debit_create_acheft_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
+ $schedule = $this->getSchedule($params);
+ // Assume an error object to return.
+ if (!is_array($schedule)) {
+ return $schedule;
+ }
+ $request = array_merge($this->convertParamsCreateCustomerCode($params), $schedule);
+ $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
+ $request['customerCode'] = '';
+ $request['accountType'] = 'CHECKING';
+ $credentials = array(
+ 'agentCode' => $this->_paymentProcessor['user_name'],
+ 'password' => $this->_paymentProcessor['password'],
+ );
+ // Get the API endpoint URL for the method's transaction mode.
+ // TODO: enable override of the default url in the request object
+ // $url = $this->_paymentProcessor['url_site'];.
+ // Make the soap request.
+ $response = $iats->request($credentials, $request);
+ // Process the soap response into a readable result.
+ $result = $iats->result($response);
+ // drupal_set_message('<pre>'.print_r($result,TRUE).'</pre>');.
+ if ($result['status']) {
+ // Always pending.
+ $params['contribution_status_id'] = 2;
+ // For future versions, the proper key.
+ $params['payment_status_id'] = 2;
+ $params['trxn_id'] = trim($result['remote_id']) . ':' . time();
+ $params['gross_amount'] = $params['amount'];
+ // Save the client info in my custom table
+ // Allow further manipulation of the arguments via custom hooks,.
+ $customer_code = $result['CUSTOMERCODE'];
+ if (isset($params['email'])) {
+ $email = $params['email'];
+ }
+ elseif (isset($params['email-5'])) {
+ $email = $params['email-5'];
+ }
+ elseif (isset($params['email-Primary'])) {
+ $email = $params['email-Primary'];
+ }
+ $query_params = array(
+ 1 => array($customer_code, 'String'),
+ 2 => array($request['customerIPAddress'], 'String'),
+ 3 => array('', 'String'),
+ 4 => array($params['contactID'], 'Integer'),
+ 5 => array($email, 'String'),
+ 6 => array($params['contributionRecurID'], 'Integer'),
+ );
+ // drupal_set_message('<pre>'.print_r($query_params,TRUE).'</pre>');.
+ CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_customer_codes
+ (customer_code, ip, expiry, cid, email, recur_id) VALUES (%1, %2, %3, %4, %5, %6)", $query_params);
+ // Save their payer validation data in civicrm_iats_ukdd_validate.
+ $query_params = array(
+ 1 => array($customer_code, 'String'),
+ 2 => array($params['payer_validate_reference'], 'String'),
+ 3 => array($params['contactID'], 'Integer'),
+ 4 => array($params['contributionRecurID'], 'Integer'),
+ 5 => array($params['payer_validate_declaration'], 'Integer'),
+ 6 => array(date('c'), 'String'),
+ );
+ // drupal_set_message('<pre>'.print_r($query_params,TRUE).'</pre>');.
+ CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_ukdd_validate
+ (customer_code, acheft_reference_num, cid, recur_id, validated, validated_datetime) VALUES (%1, %2, %3, %4, %5, %6)", $query_params);
+ // Set the status of the initial contribution to pending (currently is redundant), and the date to what I'm asking iATS for.
+ $params['contribution_status_id'] = 2;
+ $params['start_date'] = $params['payer_validate_start_date'];
+ // Optimistically set this date, even though CiviCRM will likely not do anything with it yet - I'll change it with my pre hook in the meanwhile
+ // $params['receive_date'] = strtotime($params['payer_validate_start_date']);
+ // also set next_sched_contribution, though it won't be used.
+ $params['next_sched_contribution'] = strtotime($params['payer_validate_start_date'] . ' + ' . $params['frequency_interval'] . ' ' . $params['frequency_unit']);
+ return $params;
+ }
+ else {
+ return self::error($result['reasonMessage']);
+ }
+ }
+
+ /**
+ * TODO: requires custom link
+ * function changeSubscriptionAmount(&$message = '', $params = array()) {
+ * $userAlert = ts('You have updated the amount of this recurring contribution.');
+ * CRM_Core_Session::setStatus($userAlert, ts('Warning'), 'alert');
+ * return TRUE;
+ * } .
+ */
+ public function &error($error = NULL) {
+ $e = CRM_Core_Error::singleton();
+ if (is_object($error)) {
+ $e->push($error->getResponseCode(),
+ 0, NULL,
+ $error->getMessage()
+ );
+ }
+ elseif ($error && is_numeric($error)) {
+ $e->push($error,
+ 0, NULL,
+ $this->errorString($error)
+ );
+ }
+ elseif (is_string($error)) {
+ $e->push(9002,
+ 0, NULL,
+ $error
+ );
+ }
+ else {
+ $e->push(9001, 0, NULL, "Unknown System Error.");
+ }
+ return $e;
+ }
+
+ /**
+ * This function checks to see if we have the right config values.
+ *
+ * @param string $mode
+ * the mode we are operating in (live or test)
+ *
+ * @return string the error message if any
+ *
+ * @public
+ */
+ public function checkConfig() {
+ $error = array();
+
+ if (empty($this->_paymentProcessor['user_name'])) {
+ $error[] = ts('Agent Code is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
+ }
+
+ if (empty($this->_paymentProcessor['password'])) {
+ $error[] = ts('Password is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
+ }
+ if (empty($this->_paymentProcessor['signature'])) {
+ $error[] = ts('Service User Number (SUN) is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
+ }
+ $iats_domain = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
+ if ('www.uk.iatspayments.com' != $iats_domain) {
+ $error[] = ts('You can only use this payment processor with a UK iATS account');
+ }
+ if (!empty($error)) {
+ return implode('<p>', $error);
+ }
+ else {
+ return NULL;
+ }
+ }
+
+ /**
+ * Convert the values in the civicrm params to the request array with keys as expected by iATS.
+ */
+ public function convertParamsCreateCustomerCode($params) {
+ $request = array();
+ $convert = array(
+ 'firstName' => 'billing_first_name',
+ 'lastName' => 'billing_last_name',
+ 'address' => 'street_address',
+ 'city' => 'city',
+ 'state' => 'state_province',
+ 'zipCode' => 'postal_code',
+ 'country' => 'country',
+ 'ACHEFTReferenceNum' => 'payer_validate_reference',
+ 'accountCustomerName' => 'account_holder',
+ 'email' => 'email',
+ 'recurring' => 'is_recur',
+ 'amount' => 'amount',
+ );
+
+ foreach ($convert as $r => $p) {
+ if (isset($params[$p])) {
+ $request[$r] = $params[$p];
+ }
+ }
+ // Account custom name is first name + last name, truncated to a maximum of 30 chars.
+ $request['accountNum'] = trim($params['bank_identification_number']) . trim($params['bank_account_number']);
+ return $request;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerLink.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerLink.php
new file mode 100644
index 00000000..39e22b92
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerLink.php
@@ -0,0 +1,233 @@
+<?php
+
+/**
+ * @file
+ */
+
+require_once 'CRM/Core/Form.php';
+
+/**
+ * Form controller class.
+ *
+ * @see http://wiki.civicrm.org/confluence/display/CRMDOC43/QuickForm+Reference
+ */
+class CRM_iATS_Form_IATSCustomerLink extends CRM_Core_Form {
+
+ private $iats_result = array();
+
+ /**
+ * Get the field names and labels expected by iATS CustomerLink,
+ * and the corresponding fields in CiviCRM.
+ *
+ * @return array
+ */
+ public function getFields() {
+ $civicrm_fields = array(
+ 'firstName' => 'billing_first_name',
+ 'lastName' => 'billing_last_name',
+ 'address' => 'street_address',
+ 'city' => 'city',
+ 'state' => 'state_province',
+ 'zipCode' => 'postal_code',
+ 'creditCardNum' => 'credit_card_number',
+ 'creditCardExpiry' => 'credit_card_expiry',
+ 'mop' => 'credit_card_type',
+ );
+ // When querying using CustomerLink.
+ $iats_fields = array(
+ // FLN.
+ 'creditCardCustomerName' => 'CSTN',
+ 'address' => 'ADD',
+ 'city' => 'CTY',
+ 'state' => 'ST',
+ 'zipCode' => 'ZC',
+ 'creditCardNum' => 'CCN',
+ 'creditCardExpiry' => 'EXP',
+ 'mop' => 'MP',
+ );
+ $labels = array(
+ // 'firstName' => 'First Name',
+ // 'lastName' => 'Last Name',.
+ 'creditCardCustomerName' => 'Name on Card',
+ 'address' => 'Street Address',
+ 'city' => 'City',
+ 'state' => 'State or Province',
+ 'zipCode' => 'Postal Code or Zip Code',
+ 'creditCardNum' => 'Credit Card Number',
+ 'creditCardExpiry' => 'Credit Card Expiry Date',
+ 'mop' => 'Credit Card Type',
+ );
+ return array($civicrm_fields, $iats_fields, $labels);
+ }
+
+ /**
+ *
+ */
+ protected function getCustomerCodeDetail($params) {
+ require_once "CRM/iATS/iATSService.php";
+ $credentials = iATS_Service_Request::credentials($params['paymentProcessorId'], $params['is_test']);
+ $iats_service_params = array('type' => 'customer', 'iats_domain' => $credentials['domain'], 'method' => 'get_customer_code_detail');
+ $iats = new iATS_Service_Request($iats_service_params);
+ // print_r($iats); die();
+ $request = array('customerCode' => $params['customerCode']);
+ // Make the soap request.
+ $response = $iats->request($credentials, $request);
+ // note: don't log this to the iats_response table.
+ $customer = $iats->result($response, FALSE);
+ if (empty($customer['ac1'])) {
+ $alert = ts('Unable to retrieve card details from iATS.<br />%1', array(1 => $customer['AUTHORIZATIONRESULT']));
+ throw new Exception($alert);
+ }
+ // This is a SimpleXMLElement Object.
+ $ac1 = $customer['ac1'];
+ $card = get_object_vars($ac1->CC);
+ return $customer + $card;
+ }
+
+ /**
+ *
+ */
+ protected function updateCreditCardCustomer($params) {
+ require_once "CRM/iATS/iATSService.php";
+ $credentials = iATS_Service_Request::credentials($params['paymentProcessorId'], $params['is_test']);
+ unset($params['paymentProcessorId']);
+ unset($params['is_test']);
+ unset($params['domain']);
+ $iats_service_params = array('type' => 'customer', 'iats_domain' => $credentials['domain'], 'method' => 'update_credit_card_customer');
+ $iats = new iATS_Service_Request($iats_service_params);
+ // print_r($iats); die();
+ $params['updateCreditCardNum'] = (0 < strlen($params['creditCardNum']) && (FALSE === strpos($params['creditCardNum'], '*'))) ? 1 : 0;
+ if (empty($params['updateCreditCardNum'])) {
+ unset($params['creditCardNum']);
+ unset($params['updateCreditCardNum']);
+ }
+ $params['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
+ foreach (array('qfKey', 'entryURL', 'firstName', 'lastName', '_qf_default', '_qf_IATSCustomerLink_submit') as $key) {
+ if (isset($params[$key])) {
+ unset($params[$key]);
+ }
+ }
+ // Make the soap request.
+ $response = $iats->request($credentials, $params);
+ // note: don't log this to the iats_response table.
+ $this->iats_result = $iats->result($response, TRUE);
+ return $this->iats_result;
+ }
+
+ /**
+ * Get an appropriate message for the user after an update is attempted.
+ */
+ protected function getResultMessage() {
+ $message = array();
+ foreach($this->iats_result as $key => $value) {
+ $message[] = strtolower($key).": $value";
+ }
+ return '<pre>'.implode('<br />',$message).'</pre>';
+ }
+
+ /**
+ * Test whether the update was successful
+ */
+ public function getAuthorizationResult() {
+ return $this->iats_result['AUTHORIZATIONRESULT'];
+ }
+
+ /**
+ *
+ */
+ public function buildQuickForm() {
+
+ list($civicrm_fields, $iats_fields, $labels) = $this->getFields();
+ $cid = CRM_Utils_Request::retrieve('cid', 'Integer');
+ $customerCode = CRM_Utils_Request::retrieve('customerCode', 'String');
+ $paymentProcessorId = CRM_Utils_Request::retrieve('paymentProcessorId', 'Positive');
+ $is_test = CRM_Utils_Request::retrieve('is_test', 'Integer');
+ $defaults = array(
+ 'cid' => $cid,
+ 'customerCode' => $customerCode,
+ 'paymentProcessorId' => $paymentProcessorId,
+ 'is_test' => $is_test,
+ );
+ // Get my current values from iATS as defaults.
+ if (empty($_POST)) {
+ try {
+ $customer = $this->getCustomerCodeDetail($defaults);
+ }
+ catch (Exception $e) {
+ CRM_Core_Session::setStatus($e->getMessage(), ts('Warning'), 'alert');
+ return;
+ }
+ foreach (array_keys($labels) as $name) {
+ $iats_field = $iats_fields[$name];
+ if (is_string($customer[$iats_field])) {
+ $defaults[$name] = $customer[$iats_field];
+ }
+ }
+ }
+ // I don't need cid, but it allows the back button to work.
+ $this->add('hidden', 'cid');
+ foreach ($labels as $name => $label) {
+ $this->add('text', $name, $label);
+ }
+ $this->add('hidden', 'customerCode');
+ $this->add('hidden', 'paymentProcessorId');
+ $this->add('hidden', 'is_test');
+ $this->setDefaults($defaults);
+ $this->addButtons(array(
+ array(
+ 'type' => 'submit',
+ 'name' => ts('Submit'),
+ 'isDefault' => TRUE,
+ ),
+ array(
+ 'type' => 'cancel',
+ 'name' => ts('Back'),
+ ),
+ ));
+ // Export form elements.
+ $this->assign('elementNames', $this->getRenderableElementNames());
+ parent::buildQuickForm();
+ }
+
+ /**
+ *
+ */
+ public function postProcess() {
+ $values = $this->exportValues();
+ // Send update to iATS
+ $this->updateCreditCardCustomer($values);
+ CRM_Core_Session::setStatus($this->getResultMessage(), 'Card Update Result');
+ if ('OK' == $this->getAuthorizationResult()) {
+ // Update my copy of the expiry date.
+ list($month, $year) = explode('/', $values['creditCardExpiry']);
+ $exp = sprintf('%02d%02d', $year, $month);
+ $query_params = array(
+ 1 => array($values['customerCode'], 'String'),
+ 2 => array($exp, 'String'),
+ );
+ CRM_Core_DAO::executeQuery("UPDATE civicrm_iats_customer_codes SET expiry = %2 WHERE customer_code = %1", $query_params);
+ }
+ parent::postProcess();
+ }
+
+ /**
+ * Get the fields/elements defined in this form.
+ *
+ * @return array (string)
+ */
+ public function getRenderableElementNames() {
+ // The _elements list includes some items which should not be
+ // auto-rendered in the loop -- such as "qfKey" and "buttons". These
+ // items don't have labels. We'll identify renderable by filtering on
+ // the 'label'.
+ $elementNames = array();
+ foreach ($this->_elements as $element) {
+ $label = $element->getLabel();
+ if (!empty($label)) {
+ $elementNames[] = $element->getName();
+ }
+ }
+ return $elementNames;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerUpdateBillingInfo.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerUpdateBillingInfo.php
new file mode 100644
index 00000000..03dd92a3
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerUpdateBillingInfo.php
@@ -0,0 +1,52 @@
+<?php
+
+class IATSCustomerUpdateBillingInfo extends CRM_iATS_Form_IATSCustomerLink {
+
+ public $updatedBillingInfo;
+
+ public function __construct() {
+ // no need to call all the form init stuff, we're a fake form
+ }
+
+ public function exportValues($elementList = NULL, $filterInternal = FALSE) {
+
+ $ubi = $this->updatedBillingInfo;
+ // updatedBillingInfo array changed sometime after 4.7.27
+ $crid = !empty($ubi['crid']) ? $ubi['crid'] : $ubi['recur_id'];
+ if (empty($crid)) {
+ $alert = ts('This system is unable to perform self-service updates to credit cards. Please contact the administrator of this site.');
+ throw new Exception($alert);
+ }
+ $mop = array(
+ 'Visa' => 'VISA',
+ 'MasterCard' => 'MC',
+ 'Amex' => 'AMX',
+ 'Discover' => 'DSC',
+ );
+
+ $dao = CRM_Core_DAO::executeQuery("SELECT cr.payment_processor_id, cc.customer_code, cc.cid
+ FROM civicrm_contribution_recur cr
+ LEFT JOIN civicrm_iats_customer_codes cc ON cr.id = cc.recur_id
+ WHERE cr.id=%1", array(1 => array($crid, 'Int')));
+ $dao->fetch();
+
+ $values = array(
+ 'cid' => $dao->cid,
+ 'customerCode' => $dao->customer_code,
+ 'paymentProcessorId' => $dao->payment_processor_id,
+ 'is_test' => 0,
+ 'creditCardCustomerName' => "{$ubi['first_name']} " . (!empty($ubi['middle_name']) ? "{$ubi['middle_name']} " : '') . $ubi['last_name'],
+ 'address' => $ubi['street_address'],
+ 'city' => $ubi['city'],
+ 'state' => CRM_Core_DAO::singleValueQuery("SELECT abbreviation FROM civicrm_state_province WHERE id=%1", array(1 => array($ubi['state_province_id'], 'Int'))),
+ 'zipCode' => $ubi['postal_code'],
+ 'creditCardNum' => $ubi['credit_card_number'],
+ 'creditCardExpiry' => sprintf('%02d/%02d', $ubi['month'], $ubi['year'] % 100),
+ 'mop' => $mop[$ubi['credit_card_type']],
+ );
+
+ return $values;
+
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSOneTimeCharge.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSOneTimeCharge.php
new file mode 100644
index 00000000..f4a26a4d
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IATSOneTimeCharge.php
@@ -0,0 +1,253 @@
+<?php
+
+/**
+ * @file
+ */
+
+require_once 'CRM/Core/Form.php';
+
+/**
+ * Form controller class.
+ *
+ * @see http://wiki.civicrm.org/confluence/display/CRMDOC43/QuickForm+Reference
+ * A form to generate new one-time charges on an existing recurring schedule.
+ */
+class CRM_iATS_Form_IATSOneTimeCharge extends CRM_Core_Form {
+
+ /**
+ *
+ */
+ public function getFields() {
+ $civicrm_fields = array(
+ 'firstName' => 'billing_first_name',
+ 'lastName' => 'billing_last_name',
+ 'address' => 'street_address',
+ 'city' => 'city',
+ 'state' => 'state_province',
+ 'zipCode' => 'postal_code',
+ 'creditCardNum' => 'credit_card_number',
+ 'creditCardExpiry' => 'credit_card_expiry',
+ 'mop' => 'credit_card_type',
+ );
+ // When querying using CustomerLink.
+ $iats_fields = array(
+ // FLN.
+ 'creditCardCustomerName' => 'CSTN',
+ 'address' => 'ADD',
+ 'city' => 'CTY',
+ 'state' => 'ST',
+ 'zipCode' => 'ZC',
+ 'creditCardNum' => 'CCN',
+ 'creditCardExpiry' => 'EXP',
+ 'mop' => 'MP',
+ );
+ $labels = array(
+ // 'firstName' => 'First Name',
+ // 'lastName' => 'Last Name',.
+ 'creditCardCustomerName' => 'Name on Card',
+ 'address' => 'Street Address',
+ 'city' => 'City',
+ 'state' => 'State or Province',
+ 'zipCode' => 'Postal Code or Zip Code',
+ 'creditCardNum' => 'Credit Card Number',
+ 'creditCardExpiry' => 'Credit Card Expiry Date',
+ 'mop' => 'Credit Card Type',
+ );
+ return array($civicrm_fields, $iats_fields, $labels);
+ }
+
+ /**
+ *
+ */
+ protected function getCustomerCodeDetail($params) {
+ require_once "CRM/iATS/iATSService.php";
+ $credentials = iATS_Service_Request::credentials($params['paymentProcessorId'], $params['is_test']);
+ $iats_service_params = array('type' => 'customer', 'iats_domain' => $credentials['domain'], 'method' => 'get_customer_code_detail');
+ $iats = new iATS_Service_Request($iats_service_params);
+ // print_r($iats); die();
+ $request = array('customerCode' => $params['customerCode']);
+ // Make the soap request.
+ $response = $iats->request($credentials, $request);
+ // note: don't log this to the iats_response table.
+ $customer = $iats->result($response, FALSE);
+ // print_r($customer); die();
+ if (empty($customer['ac1'])) {
+ $alert = ts('Unable to retrieve card details from iATS.<br />%1', array(1 => $customer['AUTHORIZATIONRESULT']));
+ throw new Exception($alert);
+ }
+ // This is a SimpleXMLElement Object.
+ $ac1 = $customer['ac1'];
+ $card = get_object_vars($ac1->CC);
+ return $customer + $card;
+ }
+
+ /**
+ *
+ */
+ protected function processCreditCardCustomer($values) {
+ // Generate another (possibly) recurring contribution, matching our recurring template with submitted value.
+ $is_recurrence = !empty($values['is_recurrence']);
+ $total_amount = $values['amount'];
+ $contribution_template = _iats_civicrm_getContributionTemplate(array('contribution_recur_id' => $values['crid']));
+ $contact_id = $values['cid'];
+ $hash = md5(uniqid(rand(), TRUE));
+ $contribution_recur_id = $values['crid'];
+ $payment_processor_id = $values['paymentProcessorId'];
+ $type = _iats_civicrm_is_iats($payment_processor_id);
+ $subtype = substr($type, 11);
+ // i.e. now.
+ $receive_date = date("YmdHis", time());
+ $contribution = array(
+ 'version' => 3,
+ 'contact_id' => $contact_id,
+ 'receive_date' => $receive_date,
+ 'total_amount' => $total_amount,
+ 'contribution_recur_id' => $contribution_recur_id,
+ 'invoice_id' => $hash,
+ 'contribution_status_id' => 2, /* initialize as pending, so we can run completetransaction after taking the money */
+ 'payment_processor' => $payment_processor_id,
+ 'is_test' => $values['is_test'], /* propagate the is_test value from the form */
+ );
+ foreach (array('payment_instrument_id', 'currency', 'financial_type_id') as $key) {
+ $contribution[$key] = $contribution_template[$key];
+ }
+ $options = array(
+ 'is_email_receipt' => (empty($values['is_email_receipt']) ? '0' : '1'),
+ 'customer_code' => $values['customerCode'],
+ 'subtype' => $subtype,
+ );
+ if ($is_recurrence) {
+ $contribution['source'] = "iATS Payments $subtype Recurring Contribution (id=$contribution_recur_id)";
+ // We'll use the repeattransaction if the total amount is the same
+ $original_contribution_id = ($contribution_template['total_amount'] == $total_amount) ? $contribution_template['original_contribution_id'] : NULL;
+ }
+ else {
+ $original_contribution_id = NULL;
+ unset($contribution['contribution_recur_id']);
+ $contribution['source'] = "iATS Payments $subtype One-Time Contribution (using id=$contribution_recur_id)";
+ }
+ // Now all the hard work in this function, recycled from the original recurring payment job.
+ $result = _iats_process_contribution_payment($contribution, $options, $original_contribution_id);
+ return $result;
+ }
+
+ /**
+ *
+ */
+ public function buildQuickForm() {
+
+ list($civicrm_fields, $iats_fields, $labels) = $this->getFields();
+ $this->add('hidden', 'cid');
+ $this->add('hidden', 'crid');
+ $this->add('hidden', 'customerCode');
+ $this->add('hidden', 'paymentProcessorId');
+ $this->add('hidden', 'is_test');
+ $cid = CRM_Utils_Request::retrieve('cid', 'Integer');
+ $crid = CRM_Utils_Request::retrieve('crid', 'Integer');
+ $customerCode = CRM_Utils_Request::retrieve('customerCode', 'String');
+ $paymentProcessorId = CRM_Utils_Request::retrieve('paymentProcessorId', 'Positive');
+ $is_test = CRM_Utils_Request::retrieve('is_test', 'Integer');
+ $is_recurrence = CRM_Utils_Request::retrieve('is_recurrence', 'Integer');
+ $defaults = array(
+ 'cid' => $cid,
+ 'crid' => $crid,
+ 'customerCode' => $customerCode,
+ 'paymentProcessorId' => $paymentProcessorId,
+ 'is_test' => $is_test,
+ 'is_recurrence' => 1,
+ );
+ $this->setDefaults($defaults);
+ /* always show lots of detail about the card about to be charged or just charged */
+ try {
+ $customer = $this->getCustomerCodeDetail($defaults);
+ }
+ catch (Exception $e) {
+ CRM_Core_Session::setStatus($e->getMessage(), ts('Warning'), 'alert');
+ return;
+ }
+ foreach ($labels as $name => $label) {
+ $iats_field = $iats_fields[$name];
+ if (is_string($customer[$iats_field])) {
+ $this->add('static', $name, $label, $customer[$iats_field]);
+ }
+ }
+ // todo: show past charges/dates ?
+ // Add form elements.
+ $this->addMoney(
+ // Field name.
+ 'amount',
+ // Field label.
+ 'Amount',
+ TRUE, NULL, FALSE
+ );
+ $this->add(
+ // Field type.
+ 'checkbox',
+ // Field name.
+ 'is_email_receipt',
+ ts('Automated email receipt for this contribution.')
+ );
+ $this->add(
+ // Field type.
+ 'checkbox',
+ // Field name.
+ 'is_recurrence',
+ ts('Create this as a contribution in the recurring series.')
+ );
+ $this->addButtons(array(
+ array(
+ 'type' => 'submit',
+ 'name' => ts('Charge this card'),
+ 'isDefault' => TRUE,
+ ),
+ array(
+ 'type' => 'cancel',
+ 'name' => ts('Back'),
+ ),
+ ));
+
+ // Export form elements.
+ $this->assign('elementNames', $this->getRenderableElementNames());
+ // If necessary, warn the user about the nature of what they are about to do.
+ if (0 !== $is_recurrence) { // this if is not working!
+ $message = ts('The contribution created by this form will be saved as contribution in the existing recurring series unless you uncheck the corresponding setting.'); // , $type, $options);.
+ CRM_Core_Session::setStatus($message, 'One-Time Charge');
+ }
+ parent::buildQuickForm();
+ }
+
+ /**
+ *
+ */
+ public function postProcess() {
+ $values = $this->exportValues();
+ // print_r($values); die();
+ // send charge request to iATS.
+ $result = $this->processCreditCardCustomer($values);
+ $message = print_r($result, TRUE);
+ // , $type, $options);.
+ CRM_Core_Session::setStatus($message, 'Customer Card Charged');
+ parent::postProcess();
+ }
+
+ /**
+ * Get the fields/elements defined in this form.
+ *
+ * @return array (string)
+ */
+ public function getRenderableElementNames() {
+ // The _elements list includes some items which should not be
+ // auto-rendered in the loop -- such as "qfKey" and "buttons". These
+ // items don't have labels. We'll identify renderable by filtering on
+ // the 'label'.
+ $elementNames = array();
+ foreach ($this->_elements as $element) {
+ $label = $element->getLabel();
+ if (!empty($label)) {
+ $elementNames[] = $element->getName();
+ }
+ }
+ return $elementNames;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IatsSettings.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IatsSettings.php
new file mode 100644
index 00000000..96bf910f
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/IatsSettings.php
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * @file
+ */
+
+require_once 'CRM/Core/Form.php';
+
+/**
+ * Form controller class.
+ *
+ * @see http://wiki.civicrm.org/confluence/display/CRMDOC43/QuickForm+Reference
+ */
+class CRM_iATS_Form_IatsSettings extends CRM_Core_Form {
+
+ /**
+ *
+ */
+ public function buildQuickForm() {
+
+ // Add form elements.
+ $this->add(
+ // Field type.
+ 'text',
+ // Field name.
+ 'email_recurring_failure_report',
+ ts('Email Recurring Contribution failure reports to this Email address')
+ );
+ $this->addRule('email_recurring_failure_report', ts('Email address is not a valid format.'), 'email');
+ $this->add(
+ // Field type.
+ 'text',
+ // Field name.
+ 'recurring_failure_threshhold',
+ ts('When failure count is equal to or greater than this number, push the next scheduled contribution date forward')
+ );
+ $this->addRule('recurring_failure_threshhold', ts('Threshhold must be a positive integer.'), 'integer');
+ $receipt_recurring_options = array('0' => 'Never', '1' => 'Always', '2' => 'As set for a specific Contribution Series');
+ $this->add(
+ // Field type.
+ 'select',
+ // Field name.
+ 'receipt_recurring',
+ ts('Email receipt for a Contribution in a Recurring Series'),
+ $receipt_recurring_options
+ );
+
+ $this->add(
+ // Field type.
+ 'checkbox',
+ // Field name.
+ 'no_edit_extra',
+ ts('Disable extra edit fields for Recurring Contributions')
+ );
+
+ $this->add(
+ // Field type.
+ 'checkbox',
+ // Field name.
+ 'enable_update_subscription_billing_info',
+ ts('Enable self-service updates to recurring contribution Contact Billing Info.')
+ );
+
+ /* These checkboxes are not yet implemented, ignore for now
+ $this->add(
+ 'checkbox', // field type
+ 'import_quick', // field name
+ ts('Import one-time/new iATS transactions into CiviCRM (e.g. "mobile").')
+ );
+
+ $this->add(
+ 'checkbox', // field type
+ 'import_recur', // field name
+ ts('Import recurring iATS transactions into CiviCRM for known series (e.g. "iATS managed recurring").')
+ );
+
+ $this->add(
+ 'checkbox', // field type
+ 'import_series', // field name
+ ts('Allow creation of new recurring series from iATS imports. (e.g. "mobile recurring")')
+ );
+ */
+
+ $this->add(
+ 'checkbox',
+ 'enable_public_future_recurring_start',
+ ts('Enable public selection of future recurring start dates.')
+ );
+
+ $days = array('-1' => 'disabled');
+ for ($i = 1; $i <= 28; $i++) {
+ $days["$i"] = "$i";
+ }
+ $attr = array(
+ 'size' => 29,
+ 'style' => 'width:150px',
+ 'required' => FALSE,
+ );
+ $day_select = $this->add(
+ // Field type.
+ 'select',
+ // Field name.
+ 'days',
+ ts('Restrict allowable days of the month for Recurring Contributions'),
+ $days,
+ FALSE,
+ $attr
+ );
+
+ $day_select->setMultiple(TRUE);
+ $day_select->setSize(29);
+ $this->addButtons(array(
+ array(
+ 'type' => 'submit',
+ 'name' => ts('Submit'),
+ 'isDefault' => TRUE,
+ ),
+ ));
+
+ $result = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
+ $defaults = (empty($result)) ? array() : $result;
+ if (empty($defaults['recurring_failure_threshhold'])) {
+ $defaults['recurring_failure_threshhold'] = 3;
+ }
+ $this->setDefaults($defaults);
+ $this->addButtons(array(
+ array(
+ 'type' => 'submit',
+ 'name' => ts('Submit'),
+ 'isDefault' => TRUE,
+ ),
+ ));
+
+ // Export form elements.
+ $this->assign('elementNames', $this->getRenderableElementNames());
+ parent::buildQuickForm();
+ }
+
+ /**
+ *
+ */
+ public function postProcess() {
+ $values = $this->exportValues();
+ foreach (array('qfKey', '_qf_default', '_qf_IatsSettings_submit', 'entryURL') as $key) {
+ if (isset($values[$key])) {
+ unset($values[$key]);
+ }
+ }
+ CRM_Core_BAO_Setting::setItem($values, 'iATS Payments Extension', 'iats_settings');
+ parent::postProcess();
+ }
+
+ /**
+ * Get the fields/elements defined in this form.
+ *
+ * @return array (string)
+ */
+ public function getRenderableElementNames() {
+ // The _elements list includes some items which should not be
+ // auto-rendered in the loop -- such as "qfKey" and "buttons". These
+ // items don't have labels. We'll identify renderable by filtering on
+ // the 'label'.
+ $elementNames = array();
+ foreach ($this->_elements as $element) {
+ $label = $element->getLabel();
+ if (!empty($label)) {
+ $elementNames[] = $element->getName();
+ }
+ }
+ return $elementNames;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.mgd.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.mgd.php
new file mode 100644
index 00000000..16743f18
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.mgd.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * This file declares a managed database record of type "ReportTemplate".
+ */
+
+// The record will be automatically inserted, updated, or deleted from the
+// database as appropriate. For more details, see "hook_civicrm_managed" at:
+// http://wiki.civicrm.org/confluence/display/CRMDOC42/Hook+Reference
+return array(
+ 0 =>
+ array(
+ 'name' => 'CRM_iATS_Form_Report_ContributeDetail',
+ 'entity' => 'ReportTemplate',
+ 'params' =>
+ array(
+ 'version' => 3,
+ 'label' => 'iATS Payments - Contribution Reconciliation',
+ 'description' => 'Donor Report (Detail) Report with extra iATS Reconciliation fields.',
+ 'class_name' => 'CRM_iATS_Form_Report_ContributeDetail',
+ 'report_url' => 'com.iatspayments.com/contributedetail',
+ 'component' => 'CiviContribute',
+ ),
+ ),
+);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.php
new file mode 100644
index 00000000..0c357028
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.php
@@ -0,0 +1,986 @@
+<?php
+/*
+ * A simple modified copy of the core Contribution Detail report with iATS verification detail added
+ */
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2018
+ */
+class CRM_iATS_Form_Report_ContributeDetail extends CRM_Report_Form {
+
+ protected $_summary = NULL;
+
+ protected $_softFrom = NULL;
+
+ protected $noDisplayContributionOrSoftColumn = FALSE;
+
+ protected $_customGroupExtends = array(
+ 'Contact',
+ 'Individual',
+ 'Contribution',
+ );
+
+ protected $groupConcatTested = TRUE;
+
+ static private $_iats_transaction_types = array(
+ 'VISA' => 'Visa',
+ 'ACHEFT' => 'ACH/EFT',
+ 'UNKNOW' => 'Uknown',
+ 'MC' => 'MasterCard',
+ 'AMX' => 'AMEX',
+ 'DSC' => 'Discover',
+ );
+
+ /**
+ * This report has been optimised for group filtering.
+ *
+ * CRM-19170
+ *
+ * @var bool
+ */
+ protected $groupFilterNotOptimised = FALSE;
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->_autoIncludeIndexedFieldsAsOrderBys = 1;
+ // Check if CiviCampaign is a) enabled and b) has active campaigns
+ $config = CRM_Core_Config::singleton();
+ $campaignEnabled = in_array("CiviCampaign", $config->enableComponents);
+ if ($campaignEnabled) {
+ $getCampaigns = CRM_Campaign_BAO_Campaign::getPermissionedCampaigns(NULL, NULL, TRUE, FALSE, TRUE);
+ $this->activeCampaigns = $getCampaigns['campaigns'];
+ asort($this->activeCampaigns);
+ }
+
+ $this->_columns = array_merge($this->getColumns('Contact', array(
+ 'order_bys_defaults' => array('sort_name' => 'ASC '),
+ 'fields_defaults' => array('sort_name'),
+ 'fields_excluded' => array('id'),
+ 'fields_required' => array('id'),
+ 'filters_defaults' => array('is_deleted' => 0),
+ 'no_field_disambiguation' => TRUE,
+ )), array(
+ 'civicrm_email' => array(
+ 'dao' => 'CRM_Core_DAO_Email',
+ 'fields' => array(
+ 'email' => array(
+ 'title' => ts('Donor Email'),
+ 'default' => TRUE,
+ ),
+ ),
+ 'grouping' => 'contact-fields',
+ ),
+ 'civicrm_line_item' => array(
+ 'dao' => 'CRM_Price_DAO_LineItem',
+ ),
+ 'civicrm_phone' => array(
+ 'dao' => 'CRM_Core_DAO_Phone',
+ 'fields' => array(
+ 'phone' => array(
+ 'title' => ts('Donor Phone'),
+ 'default' => TRUE,
+ 'no_repeat' => TRUE,
+ ),
+ ),
+ 'grouping' => 'contact-fields',
+ ),
+ 'civicrm_contribution' => array(
+ 'dao' => 'CRM_Contribute_DAO_Contribution',
+ 'fields' => array(
+ 'contribution_id' => array(
+ 'name' => 'id',
+ 'no_display' => TRUE,
+ 'required' => TRUE,
+ ),
+ 'list_contri_id' => array(
+ 'name' => 'id',
+ 'title' => ts('Contribution ID'),
+ ),
+ 'financial_type_id' => array(
+ 'title' => ts('Financial Type'),
+ 'default' => TRUE,
+ ),
+ 'contribution_status_id' => array(
+ 'title' => ts('Contribution Status'),
+ ),
+ 'contribution_page_id' => array(
+ 'title' => ts('Contribution Page'),
+ ),
+ 'source' => array(
+ 'title' => ts('Source'),
+ ),
+ 'payment_instrument_id' => array(
+ 'title' => ts('Payment Type'),
+ ),
+ 'check_number' => array(
+ 'title' => ts('Check Number'),
+ ),
+ 'currency' => array(
+ 'required' => TRUE,
+ 'no_display' => TRUE,
+ ),
+ 'trxn_id' => NULL,
+ 'receive_date' => array('default' => TRUE),
+ 'receipt_date' => NULL,
+ 'total_amount' => array(
+ 'title' => ts('Amount'),
+ 'required' => TRUE,
+ 'statistics' => array('sum' => ts('Amount')),
+ ),
+ 'non_deductible_amount' => array(
+ 'title' => ts('Non-deductible Amount'),
+ ),
+ 'fee_amount' => NULL,
+ 'net_amount' => NULL,
+ 'contribution_or_soft' => array(
+ 'title' => ts('Contribution OR Soft Credit?'),
+ 'dbAlias' => "'Contribution'",
+ ),
+ 'soft_credits' => array(
+ 'title' => ts('Soft Credits'),
+ 'dbAlias' => "NULL",
+ ),
+ 'soft_credit_for' => array(
+ 'title' => ts('Soft Credit For'),
+ 'dbAlias' => "NULL",
+ ),
+ ),
+ 'filters' => array(
+ 'contribution_or_soft' => array(
+ 'title' => ts('Contribution OR Soft Credit?'),
+ 'clause' => "(1)",
+ 'operatorType' => CRM_Report_Form::OP_SELECT,
+ 'type' => CRM_Utils_Type::T_STRING,
+ 'options' => array(
+ 'contributions_only' => ts('Contributions Only'),
+ 'soft_credits_only' => ts('Soft Credits Only'),
+ 'both' => ts('Both'),
+ ),
+ ),
+ 'receive_date' => array('operatorType' => CRM_Report_Form::OP_DATE),
+ 'contribution_source' => array(
+ 'title' => ts('Source'),
+ 'name' => 'source',
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ 'currency' => array(
+ 'title' => ts('Currency'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => CRM_Core_OptionGroup::values('currencies_enabled'),
+ 'default' => NULL,
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ 'non_deductible_amount' => array(
+ 'title' => ts('Non-deductible Amount'),
+ ),
+ 'financial_type_id' => array(
+ 'title' => ts('Financial Type'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes(),
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ 'contribution_page_id' => array(
+ 'title' => ts('Contribution Page'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => CRM_Contribute_PseudoConstant::contributionPage(),
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ 'payment_instrument_id' => array(
+ 'title' => ts('Payment Type'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => CRM_Contribute_PseudoConstant::paymentInstrument(),
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ 'contribution_status_id' => array(
+ 'title' => ts('Contribution Status'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => CRM_Contribute_PseudoConstant::contributionStatus(),
+ 'default' => array(1),
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ 'total_amount' => array('title' => ts('Contribution Amount')),
+ ),
+ 'order_bys' => array(
+ 'financial_type_id' => array('title' => ts('Financial Type')),
+ 'contribution_status_id' => array('title' => ts('Contribution Status')),
+ 'payment_instrument_id' => array('title' => ts('Payment Method')),
+ 'receive_date' => array('title' => ts('Date Received')),
+ ),
+ 'group_bys' => array(
+ 'contribution_id' => array(
+ 'name' => 'id',
+ 'required' => TRUE,
+ 'title' => ts('Contribution'),
+ ),
+ ),
+ 'grouping' => 'contri-fields',
+ ),
+ 'civicrm_contribution_soft' => array(
+ 'dao' => 'CRM_Contribute_DAO_ContributionSoft',
+ 'fields' => array(
+ 'soft_credit_type_id' => array('title' => ts('Soft Credit Type')),
+ ),
+ 'filters' => array(
+ 'soft_credit_type_id' => array(
+ 'title' => ts('Soft Credit Type'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => CRM_Core_OptionGroup::values('soft_credit_type'),
+ 'default' => NULL,
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ ),
+ ),
+ 'civicrm_financial_trxn' => array(
+ 'dao' => 'CRM_Financial_DAO_FinancialTrxn',
+ 'fields' => array(
+ 'card_type_id' => array(
+ 'title' => ts('Credit Card Type'),
+ ),
+ ),
+ 'filters' => array(
+ 'card_type_id' => array(
+ 'title' => ts('Credit Card Type'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => CRM_Financial_DAO_FinancialTrxn::buildOptions('card_type_id'),
+ 'default' => NULL,
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ ),
+ ),
+ 'civicrm_batch' => array(
+ 'dao' => 'CRM_Batch_DAO_EntityBatch',
+ 'grouping' => 'contri-fields',
+ 'fields' => array(
+ 'batch_id' => array(
+ 'name' => 'batch_id',
+ 'title' => ts('Batch Name'),
+ ),
+ ),
+ 'filters' => array(
+ 'bid' => array(
+ 'title' => ts('Batch Name'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => CRM_Batch_BAO_Batch::getBatches(),
+ 'type' => CRM_Utils_Type::T_INT,
+ 'dbAlias' => 'batch_civireport.batch_id',
+ ),
+ ),
+ ),
+ 'civicrm_contribution_ordinality' => array(
+ 'dao' => 'CRM_Contribute_DAO_Contribution',
+ 'alias' => 'cordinality',
+ 'filters' => array(
+ 'ordinality' => array(
+ 'title' => ts('Contribution Ordinality'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => array(
+ 0 => 'First by Contributor',
+ 1 => 'Second or Later by Contributor',
+ ),
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ ),
+ ),
+ 'civicrm_note' => array(
+ 'dao' => 'CRM_Core_DAO_Note',
+ 'fields' => array(
+ 'contribution_note' => array(
+ 'name' => 'note',
+ 'title' => ts('Contribution Note'),
+ ),
+ ),
+ 'filters' => array(
+ 'note' => array(
+ 'name' => 'note',
+ 'title' => ts('Contribution Note'),
+ 'operator' => 'like',
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ ),
+ ),
+ )) + $this->addAddressFields(FALSE);
+ // The tests test for this variation of the sort_name field. Don't argue with the tests :-).
+ $this->_columns['civicrm_contact']['fields']['sort_name']['title'] = ts('Donor Name');
+ $this->_groupFilter = TRUE;
+ $this->_tagFilter = TRUE;
+
+ // If we have active campaigns add those elements to both the fields and filters
+ if ($campaignEnabled && !empty($this->activeCampaigns)) {
+ $this->_columns['civicrm_contribution']['fields']['campaign_id'] = array(
+ 'title' => ts('Campaign'),
+ 'default' => 'false',
+ );
+ $this->_columns['civicrm_contribution']['filters']['campaign_id'] = array(
+ 'title' => ts('Campaign'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => $this->activeCampaigns,
+ 'type' => CRM_Utils_Type::T_INT,
+ );
+ $this->_columns['civicrm_contribution']['order_bys']['campaign_id'] = array('title' => ts('Campaign'));
+ }
+
+ $this->_currencyColumn = 'civicrm_contribution_currency';
+
+
+ // self::$contributionStatus = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id');
+ // $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus();
+ $this->_columns['civicrm_iats_journal'] = array(
+ 'fields' =>
+ array(
+ 'id' => array('title' => 'CiviCRM Journal Id', 'default' => TRUE),
+ 'iats_id' => array('title' => 'iATS Journal Id', 'default' => TRUE),
+ 'tnid' => array('title' => 'Transaction ID', 'default' => TRUE),
+ 'tntyp' => array('title' => 'Transaction type', 'default' => TRUE),
+ 'agt' => array('title' => 'Client/Agent code', 'default' => TRUE),
+ 'cstc' => array('title' => 'Customer code', 'default' => TRUE),
+ 'inv' => array('title' => 'Invoice Reference', 'default' => TRUE),
+ 'dtm' => array('title' => 'Transaction date', 'default' => TRUE),
+ 'amt' => array('title' => 'Amount', 'default' => TRUE),
+ 'rst' => array('title' => 'Result string', 'default' => TRUE),
+ 'dtm' => array('title' => 'Transaction Date Time', 'default' => TRUE),
+ 'status_id' => array('title' => 'Payment Status', 'default' => TRUE),
+ ),
+ 'order_bys' =>
+ array(
+ 'id' => array('title' => ts('CiviCRM Journal Id'), 'default' => TRUE, 'default_order' => 'DESC'),
+ 'iats_id' => array('title' => ts('iATS Journal Id')),
+ 'dtm' => array('title' => ts('Transaction Date Time')),
+ ),
+ 'filters' =>
+ array(
+ 'dtm' => array(
+ 'title' => 'Transaction date',
+ 'operatorType' => CRM_Report_Form::OP_DATE,
+ 'type' => CRM_Utils_Type::T_DATE,
+ ),
+ 'inv' => array(
+ 'title' => 'Invoice Reference',
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ 'amt' => array(
+ 'title' => 'Amount',
+ 'operatorType' => CRM_Report_Form::OP_FLOAT,
+ 'type' => CRM_Utils_Type::T_FLOAT
+ ),
+ 'tntyp' => array(
+ 'title' => 'Type',
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => self::$_iats_transaction_types,
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ 'rst' => array(
+ 'title' => 'Result string',
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ 'status_id' => array(
+ 'title' => ts('iATS Journal Payment Status'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => CRM_Contribute_PseudoConstant::contributionStatus(),
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ ),
+ );
+ parent::__construct();
+ }
+
+ /**
+ * Set the FROM clause for the report.
+ */
+ public function from() {
+ $this->setFromBase('civicrm_contact');
+ $this->_from .= "
+ INNER JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']}
+ ON {$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_contribution']}.contact_id
+ AND {$this->_aliases['civicrm_contribution']}.is_test = 0";
+
+ if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+ 'both'
+ ) {
+ $this->_from .= "\n LEFT JOIN civicrm_contribution_soft contribution_soft_civireport
+ ON contribution_soft_civireport.contribution_id = {$this->_aliases['civicrm_contribution']}.id";
+ }
+ elseif (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+ 'soft_credits_only'
+ ) {
+ $this->_from .= "\n INNER JOIN civicrm_contribution_soft contribution_soft_civireport
+ ON contribution_soft_civireport.contribution_id = {$this->_aliases['civicrm_contribution']}.id";
+ }
+ $this->appendAdditionalFromJoins();
+ }
+
+ /**
+ * @param $rows
+ *
+ * @return array
+ */
+ public function statistics(&$rows) {
+ $statistics = parent::statistics($rows);
+
+ $totalAmount = $average = $fees = $net = array();
+ $count = 0;
+ $select = "
+ SELECT COUNT({$this->_aliases['civicrm_contribution']}.total_amount ) as count,
+ SUM( {$this->_aliases['civicrm_contribution']}.total_amount ) as amount,
+ ROUND(AVG({$this->_aliases['civicrm_contribution']}.total_amount), 2) as avg,
+ {$this->_aliases['civicrm_contribution']}.currency as currency,
+ SUM( {$this->_aliases['civicrm_contribution']}.fee_amount ) as fees,
+ SUM( {$this->_aliases['civicrm_contribution']}.net_amount ) as net
+ ";
+
+ $group = "\nGROUP BY {$this->_aliases['civicrm_contribution']}.currency";
+ $sql = "{$select} {$this->_from} {$this->_where} {$group}";
+ $dao = CRM_Core_DAO::executeQuery($sql);
+ $this->addToDeveloperTab($sql);
+
+ while ($dao->fetch()) {
+ $totalAmount[] = CRM_Utils_Money::format($dao->amount, $dao->currency) . " (" . $dao->count . ")";
+ $fees[] = CRM_Utils_Money::format($dao->fees, $dao->currency);
+ $net[] = CRM_Utils_Money::format($dao->net, $dao->currency);
+ $average[] = CRM_Utils_Money::format($dao->avg, $dao->currency);
+ $count += $dao->count;
+ }
+ $statistics['counts']['amount'] = array(
+ 'title' => ts('Total Amount (Contributions)'),
+ 'value' => implode(', ', $totalAmount),
+ 'type' => CRM_Utils_Type::T_STRING,
+ );
+ $statistics['counts']['count'] = array(
+ 'title' => ts('Total Contributions'),
+ 'value' => $count,
+ );
+ $statistics['counts']['fees'] = array(
+ 'title' => ts('Fees'),
+ 'value' => implode(', ', $fees),
+ 'type' => CRM_Utils_Type::T_STRING,
+ );
+ $statistics['counts']['net'] = array(
+ 'title' => ts('Net'),
+ 'value' => implode(', ', $net),
+ 'type' => CRM_Utils_Type::T_STRING,
+ );
+ $statistics['counts']['avg'] = array(
+ 'title' => ts('Average'),
+ 'value' => implode(', ', $average),
+ 'type' => CRM_Utils_Type::T_STRING,
+ );
+
+ // Stats for soft credits
+ if ($this->_softFrom &&
+ CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) !=
+ 'contributions_only'
+ ) {
+ $totalAmount = $average = array();
+ $count = 0;
+ $select = "
+SELECT COUNT(contribution_soft_civireport.amount ) as count,
+ SUM(contribution_soft_civireport.amount ) as amount,
+ ROUND(AVG(contribution_soft_civireport.amount), 2) as avg,
+ {$this->_aliases['civicrm_contribution']}.currency as currency";
+ $sql = "
+{$select}
+{$this->_softFrom}
+GROUP BY {$this->_aliases['civicrm_contribution']}.currency";
+ $dao = CRM_Core_DAO::executeQuery($sql);
+ $this->addToDeveloperTab($sql);
+ while ($dao->fetch()) {
+ $totalAmount[] = CRM_Utils_Money::format($dao->amount, $dao->currency) . " (" .
+ $dao->count . ")";
+ $average[] = CRM_Utils_Money::format($dao->avg, $dao->currency);
+ $count += $dao->count;
+ }
+ $statistics['counts']['softamount'] = array(
+ 'title' => ts('Total Amount (Soft Credits)'),
+ 'value' => implode(', ', $totalAmount),
+ 'type' => CRM_Utils_Type::T_STRING,
+ );
+ $statistics['counts']['softcount'] = array(
+ 'title' => ts('Total Soft Credits'),
+ 'value' => $count,
+ );
+ $statistics['counts']['softavg'] = array(
+ 'title' => ts('Average (Soft Credits)'),
+ 'value' => implode(', ', $average),
+ 'type' => CRM_Utils_Type::T_STRING,
+ );
+ }
+
+ return $statistics;
+ }
+
+ /**
+ * This function appears to have been overrriden for the purposes of facilitating soft credits in the report.
+ *
+ * The report appears to have 2 different functions:
+ * 1) contribution report
+ * 2) soft credit report - showing a row per 'payment engagement' (payment or soft credit). There is a separate
+ * soft credit report as well.
+ *
+ * Somewhat confusingly this report returns multiple rows per contribution when soft credits are included. It feels
+ * like there is a case to split it into 2 separate reports.
+ *
+ * Soft credit functionality is not currently unit tested for this report.
+ */
+ public function postProcess() {
+ // get the acl clauses built before we assemble the query
+ $this->buildACLClause($this->_aliases['civicrm_contact']);
+
+ $this->beginPostProcess();
+ // CRM-18312 - display soft_credits and soft_credits_for column
+ // when 'Contribution or Soft Credit?' column is not selected
+ if (empty($this->_params['fields']['contribution_or_soft'])) {
+ $this->_params['fields']['contribution_or_soft'] = 1;
+ $this->noDisplayContributionOrSoftColumn = TRUE;
+ }
+
+ if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+ 'contributions_only' &&
+ !empty($this->_params['fields']['soft_credit_type_id'])
+ ) {
+ unset($this->_params['fields']['soft_credit_type_id']);
+ if (!empty($this->_params['soft_credit_type_id_value'])) {
+ $this->_params['soft_credit_type_id_value'] = array();
+ }
+ }
+
+ // 1. use main contribution query to build temp table 1
+ $sql = $this->buildQuery();
+ $tempQuery = "CREATE TEMPORARY TABLE civireport_contribution_detail_temp1 {$this->_databaseAttributes} AS {$sql}";
+ $this->addToDeveloperTab($tempQuery);
+ CRM_Core_DAO::executeQuery($tempQuery);
+ $this->setPager();
+
+ // 2. customize main contribution query for soft credit, and build temp table 2 with soft credit contributions only
+ $this->softCreditFrom();
+ // also include custom group from if included
+ // since this might be included in select
+ $this->customDataFrom();
+
+ $select = str_ireplace('contribution_civireport.total_amount', 'contribution_soft_civireport.amount', $this->_select);
+ $select = str_ireplace("'Contribution' as", "'Soft Credit' as", $select);
+ // We really don't want to join soft credit in if not required.
+ if (!empty($this->_groupBy) && !$this->noDisplayContributionOrSoftColumn) {
+ $this->_groupBy .= ', contribution_soft_civireport.amount';
+ }
+ // we inner join with temp1 to restrict soft contributions to those in temp1 table
+ $sql = "{$select} {$this->_from} {$this->_where} {$this->_groupBy}";
+ $tempQuery = "CREATE TEMPORARY TABLE civireport_contribution_detail_temp2 {$this->_databaseAttributes} AS {$sql}";
+ $this->addToDeveloperTab($tempQuery);
+ CRM_Core_DAO::executeQuery($tempQuery);
+ if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+ 'soft_credits_only'
+ ) {
+ // revise pager : prev, next based on soft-credits only
+ $this->setPager();
+ }
+
+ // copy _from for later use of stats calculation for soft credits, and reset $this->_from to main query
+ $this->_softFrom = $this->_from;
+
+ // simple reset of ->_from
+ $this->from();
+
+ // also include custom group from if included
+ // since this might be included in select
+ $this->customDataFrom();
+
+ // 3. Decide where to populate temp3 table from
+ if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+ 'contributions_only'
+ ) {
+ $tempQuery = "(SELECT * FROM civireport_contribution_detail_temp1)";
+ }
+ elseif (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+ 'soft_credits_only'
+ ) {
+ $tempQuery = "(SELECT * FROM civireport_contribution_detail_temp2)";
+ }
+ else {
+ $tempQuery = "
+(SELECT * FROM civireport_contribution_detail_temp1)
+UNION ALL
+(SELECT * FROM civireport_contribution_detail_temp2)";
+ }
+
+ // 4. build temp table 3
+ $sql = "CREATE TEMPORARY TABLE civireport_contribution_detail_temp3 {$this->_databaseAttributes} AS {$tempQuery}";
+ $this->addToDeveloperTab($sql);
+ CRM_Core_DAO::executeQuery($sql);
+
+ // 6. show result set from temp table 3
+ $rows = array();
+ $sql = "SELECT * FROM civireport_contribution_detail_temp3 $this->_orderBy";
+ $this->buildRows($sql, $rows);
+
+ // format result set.
+ $this->formatDisplay($rows, FALSE);
+
+ // assign variables to templates
+ $this->doTemplateAssignment($rows);
+ // do print / pdf / instance stuff if needed
+ $this->endPostProcess($rows);
+ }
+
+ /**
+ * Alter display of rows.
+ *
+ * Iterate through the rows retrieved via SQL and make changes for display purposes,
+ * such as rendering contacts as links.
+ *
+ * @param array $rows
+ * Rows generated by SQL, with an array for each row.
+ */
+ public function alterDisplay(&$rows) {
+ $checkList = array();
+ $entryFound = FALSE;
+ $display_flag = $prev_cid = $cid = 0;
+ $contributionTypes = CRM_Contribute_PseudoConstant::financialType();
+ $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus();
+ $paymentInstruments = CRM_Contribute_PseudoConstant::paymentInstrument();
+ $contributionPages = CRM_Contribute_PseudoConstant::contributionPage();
+ $batches = CRM_Batch_BAO_Batch::getBatches();
+ foreach ($rows as $rowNum => $row) {
+ if (!empty($this->_noRepeats) && $this->_outputMode != 'csv') {
+ // don't repeat contact details if its same as the previous row
+ if (array_key_exists('civicrm_contact_id', $row)) {
+ if ($cid = $row['civicrm_contact_id']) {
+ if ($rowNum == 0) {
+ $prev_cid = $cid;
+ }
+ else {
+ if ($prev_cid == $cid) {
+ $display_flag = 1;
+ $prev_cid = $cid;
+ }
+ else {
+ $display_flag = 0;
+ $prev_cid = $cid;
+ }
+ }
+
+ if ($display_flag) {
+ foreach ($row as $colName => $colVal) {
+ // Hide repeats in no-repeat columns, but not if the field's a section header
+ if (in_array($colName, $this->_noRepeats) &&
+ !array_key_exists($colName, $this->_sections)
+ ) {
+ unset($rows[$rowNum][$colName]);
+ }
+ }
+ }
+ $entryFound = TRUE;
+ }
+ }
+ }
+
+ if (CRM_Utils_Array::value('civicrm_contribution_contribution_or_soft', $rows[$rowNum]) ==
+ 'Contribution'
+ ) {
+ unset($rows[$rowNum]['civicrm_contribution_soft_soft_credit_type_id']);
+ }
+
+ $entryFound = $this->alterDisplayContactFields($row, $rows, $rowNum, 'contribution/detail', ts('View Contribution Details')) ? TRUE : $entryFound;
+ // convert donor sort name to link
+ if (array_key_exists('civicrm_contact_sort_name', $row) &&
+ !empty($rows[$rowNum]['civicrm_contact_sort_name']) &&
+ array_key_exists('civicrm_contact_id', $row)
+ ) {
+ $url = CRM_Utils_System::url("civicrm/contact/view",
+ 'reset=1&cid=' . $row['civicrm_contact_id'],
+ $this->_absoluteUrl
+ );
+ $rows[$rowNum]['civicrm_contact_sort_name_link'] = $url;
+ $rows[$rowNum]['civicrm_contact_sort_name_hover'] = ts("View Contact Summary for this Contact.");
+ }
+
+ if ($value = CRM_Utils_Array::value('civicrm_contribution_financial_type_id', $row)) {
+ $rows[$rowNum]['civicrm_contribution_financial_type_id'] = $contributionTypes[$value];
+ $entryFound = TRUE;
+ }
+ if ($value = CRM_Utils_Array::value('civicrm_contribution_contribution_status_id', $row)) {
+ $rows[$rowNum]['civicrm_contribution_contribution_status_id'] = $contributionStatus[$value];
+ $entryFound = TRUE;
+ }
+ if ($value = CRM_Utils_Array::value('civicrm_contribution_contribution_page_id', $row)) {
+ $rows[$rowNum]['civicrm_contribution_contribution_page_id'] = $contributionPages[$value];
+ $entryFound = TRUE;
+ }
+ if ($value = CRM_Utils_Array::value('civicrm_contribution_payment_instrument_id', $row)) {
+ $rows[$rowNum]['civicrm_contribution_payment_instrument_id'] = $paymentInstruments[$value];
+ $entryFound = TRUE;
+ }
+ if (!empty($row['civicrm_batch_batch_id'])) {
+ $rows[$rowNum]['civicrm_batch_batch_id'] = CRM_Utils_Array::value($row['civicrm_batch_batch_id'], $batches);
+ $entryFound = TRUE;
+ }
+ if (!empty($row['civicrm_financial_trxn_card_type_id'])) {
+ $rows[$rowNum]['civicrm_financial_trxn_card_type_id'] = $this->getLabels($row['civicrm_financial_trxn_card_type_id'], 'CRM_Financial_DAO_FinancialTrxn', 'card_type_id');
+ $entryFound = TRUE;
+ }
+
+ // Contribution amount links to viewing contribution
+ if (($value = CRM_Utils_Array::value('civicrm_contribution_total_amount', $row)) &&
+ CRM_Core_Permission::check('access CiviContribute')
+ ) {
+ $url = CRM_Utils_System::url("civicrm/contact/view/contribution",
+ "reset=1&id=" . $row['civicrm_contribution_contribution_id'] .
+ "&cid=" . $row['civicrm_contact_id'] .
+ "&action=view&context=contribution&selectedChild=contribute",
+ $this->_absoluteUrl
+ );
+ $rows[$rowNum]['civicrm_contribution_total_amount_link'] = $url;
+ $rows[$rowNum]['civicrm_contribution_total_amount_hover'] = ts("View Details of this Contribution.");
+ $entryFound = TRUE;
+ }
+
+ // convert campaign_id to campaign title
+ if (array_key_exists('civicrm_contribution_campaign_id', $row)) {
+ if ($value = $row['civicrm_contribution_campaign_id']) {
+ $rows[$rowNum]['civicrm_contribution_campaign_id'] = $this->activeCampaigns[$value];
+ $entryFound = TRUE;
+ }
+ }
+
+ // soft credits
+ if (array_key_exists('civicrm_contribution_soft_credits', $row) &&
+ 'Contribution' ==
+ CRM_Utils_Array::value('civicrm_contribution_contribution_or_soft', $rows[$rowNum]) &&
+ array_key_exists('civicrm_contribution_contribution_id', $row)
+ ) {
+ $query = "
+SELECT civicrm_contact_id, civicrm_contact_sort_name, civicrm_contribution_total_amount_sum, civicrm_contribution_currency
+FROM civireport_contribution_detail_temp2
+WHERE civicrm_contribution_contribution_id={$row['civicrm_contribution_contribution_id']}";
+ $this->addToDeveloperTab($query);
+ $dao = CRM_Core_DAO::executeQuery($query);
+ $string = '';
+ $separator = ($this->_outputMode !== 'csv') ? "<br/>" : ' ';
+ while ($dao->fetch()) {
+ $url = CRM_Utils_System::url("civicrm/contact/view", 'reset=1&cid=' .
+ $dao->civicrm_contact_id);
+ $string = $string . ($string ? $separator : '') .
+ "<a href='{$url}'>{$dao->civicrm_contact_sort_name}</a> " .
+ CRM_Utils_Money::format($dao->civicrm_contribution_total_amount_sum, $dao->civicrm_contribution_currency);
+ }
+ $rows[$rowNum]['civicrm_contribution_soft_credits'] = $string;
+ }
+
+ if (array_key_exists('civicrm_contribution_soft_credit_for', $row) &&
+ 'Soft Credit' ==
+ CRM_Utils_Array::value('civicrm_contribution_contribution_or_soft', $rows[$rowNum]) &&
+ array_key_exists('civicrm_contribution_contribution_id', $row)
+ ) {
+ $query = "
+SELECT civicrm_contact_id, civicrm_contact_sort_name
+FROM civireport_contribution_detail_temp1
+WHERE civicrm_contribution_contribution_id={$row['civicrm_contribution_contribution_id']}";
+ $this->addToDeveloperTab($query);
+ $dao = CRM_Core_DAO::executeQuery($query);
+ $string = '';
+ while ($dao->fetch()) {
+ $url = CRM_Utils_System::url("civicrm/contact/view", 'reset=1&cid=' .
+ $dao->civicrm_contact_id);
+ $string = $string .
+ "\n<a href='{$url}'>{$dao->civicrm_contact_sort_name}</a>";
+ }
+ $rows[$rowNum]['civicrm_contribution_soft_credit_for'] = $string;
+ }
+
+ // CRM-18312 - hide 'contribution_or_soft' column if unchecked.
+ if (!empty($this->noDisplayContributionOrSoftColumn)) {
+ unset($rows[$rowNum]['civicrm_contribution_contribution_or_soft']);
+ unset($this->_columnHeaders['civicrm_contribution_contribution_or_soft']);
+ }
+
+ //convert soft_credit_type_id into label
+ if (array_key_exists('civicrm_contribution_soft_soft_credit_type_id', $rows[$rowNum])) {
+ $rows[$rowNum]['civicrm_contribution_soft_soft_credit_type_id'] = CRM_Core_PseudoConstant::getLabel(
+ 'CRM_Contribute_BAO_ContributionSoft',
+ 'soft_credit_type_id',
+ $row['civicrm_contribution_soft_soft_credit_type_id']
+ );
+ }
+
+ $entryFound = $this->alterDisplayAddressFields($row, $rows, $rowNum, 'contribute/detail', 'List all contribution(s) for this ') ? TRUE : $entryFound;
+
+ // skip looking further in rows, if first row itself doesn't
+ // have the column we need
+ if (!$entryFound) {
+ break;
+ }
+ $lastKey = $rowNum;
+ }
+ }
+
+ public function sectionTotals() {
+
+ // Reports using order_bys with sections must populate $this->_selectAliases in select() method.
+ if (empty($this->_selectAliases)) {
+ return;
+ }
+
+ if (!empty($this->_sections)) {
+ // build the query with no LIMIT clause
+ $select = str_ireplace('SELECT SQL_CALC_FOUND_ROWS ', 'SELECT ', $this->_select);
+ $sql = "{$select} {$this->_from} {$this->_where} {$this->_groupBy} {$this->_having} {$this->_orderBy}";
+
+ // pull section aliases out of $this->_sections
+ $sectionAliases = array_keys($this->_sections);
+
+ $ifnulls = array();
+ foreach (array_merge($sectionAliases, $this->_selectAliases) as $alias) {
+ $ifnulls[] = "ifnull($alias, '') as $alias";
+ }
+ $this->_select = "SELECT " . implode(", ", $ifnulls);
+ $this->_select = CRM_Contact_BAO_Query::appendAnyValueToSelect($ifnulls, $sectionAliases);
+
+ /* Group (un-limited) report by all aliases and get counts. This might
+ * be done more efficiently when the contents of $sql are known, ie. by
+ * overriding this method in the report class.
+ */
+
+ $addtotals = '';
+
+ if (array_search("civicrm_contribution_total_amount", $this->_selectAliases) !==
+ FALSE
+ ) {
+ $addtotals = ", sum(civicrm_contribution_total_amount) as sumcontribs";
+ $showsumcontribs = TRUE;
+ }
+
+ $query = $this->_select .
+ "$addtotals, count(*) as ct from civireport_contribution_detail_temp3 group by " .
+ implode(", ", $sectionAliases);
+ // initialize array of total counts
+ $sumcontribs = $totals = array();
+ $dao = CRM_Core_DAO::executeQuery($query);
+ $this->addToDeveloperTab($query);
+ while ($dao->fetch()) {
+
+ // let $this->_alterDisplay translate any integer ids to human-readable values.
+ $rows[0] = $dao->toArray();
+ $this->alterDisplay($rows);
+ $row = $rows[0];
+
+ // add totals for all permutations of section values
+ $values = array();
+ $i = 1;
+ $aliasCount = count($sectionAliases);
+ foreach ($sectionAliases as $alias) {
+ $values[] = $row[$alias];
+ $key = implode(CRM_Core_DAO::VALUE_SEPARATOR, $values);
+ if ($i == $aliasCount) {
+ // the last alias is the lowest-level section header; use count as-is
+ $totals[$key] = $dao->ct;
+ if ($showsumcontribs) {
+ $sumcontribs[$key] = $dao->sumcontribs;
+ }
+ }
+ else {
+ // other aliases are higher level; roll count into their total
+ $totals[$key] = (array_key_exists($key, $totals)) ? $totals[$key] + $dao->ct : $dao->ct;
+ if ($showsumcontribs) {
+ $sumcontribs[$key] = array_key_exists($key, $sumcontribs) ? $sumcontribs[$key] + $dao->sumcontribs : $dao->sumcontribs;
+ }
+ }
+ }
+ }
+ if ($showsumcontribs) {
+ $totalandsum = array();
+ // ts exception to avoid having ts("%1 %2: %3")
+ $title = '%1 contributions / soft-credits: %2';
+
+ if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+ 'contributions_only'
+ ) {
+ $title = '%1 contributions: %2';
+ }
+ elseif (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+ 'soft_credits_only'
+ ) {
+ $title = '%1 soft-credits: %2';
+ }
+ foreach ($totals as $key => $total) {
+ $totalandsum[$key] = ts($title, array(
+ 1 => $total,
+ 2 => CRM_Utils_Money::format($sumcontribs[$key]),
+ ));
+ }
+ $this->assign('sectionTotals', $totalandsum);
+ }
+ else {
+ $this->assign('sectionTotals', $totals);
+ }
+ }
+ }
+
+ /**
+ * Generate the from clause as it relates to the soft credits.
+ */
+ public function softCreditFrom() {
+
+ $this->_from = "
+ FROM civireport_contribution_detail_temp1 temp1_civireport
+ INNER JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']}
+ ON temp1_civireport.civicrm_contribution_contribution_id = {$this->_aliases['civicrm_contribution']}.id
+ INNER JOIN civicrm_contribution_soft contribution_soft_civireport
+ ON contribution_soft_civireport.contribution_id = {$this->_aliases['civicrm_contribution']}.id
+ INNER JOIN civicrm_contact {$this->_aliases['civicrm_contact']}
+ ON {$this->_aliases['civicrm_contact']}.id = contribution_soft_civireport.contact_id
+ {$this->_aclFrom}
+ ";
+
+ $this->appendAdditionalFromJoins();
+ }
+
+ /**
+ * Append the joins that are required regardless of context.
+ */
+ public function appendAdditionalFromJoins() {
+ if (!empty($this->_params['ordinality_value'])) {
+ $this->_from .= "
+ INNER JOIN (SELECT c.id, IF(COUNT(oc.id) = 0, 0, 1) AS ordinality FROM civicrm_contribution c LEFT JOIN civicrm_contribution oc ON c.contact_id = oc.contact_id AND oc.receive_date < c.receive_date GROUP BY c.id) {$this->_aliases['civicrm_contribution_ordinality']}
+ ON {$this->_aliases['civicrm_contribution_ordinality']}.id = {$this->_aliases['civicrm_contribution']}.id";
+ }
+ $this->joinPhoneFromContact();
+ $this->joinAddressFromContact();
+ $this->joinEmailFromContact();
+
+ // include contribution note
+ if (!empty($this->_params['fields']['contribution_note']) ||
+ !empty($this->_params['note_value'])
+ ) {
+ $this->_from .= "
+ LEFT JOIN civicrm_note {$this->_aliases['civicrm_note']}
+ ON ( {$this->_aliases['civicrm_note']}.entity_table = 'civicrm_contribution' AND
+ {$this->_aliases['civicrm_contribution']}.id = {$this->_aliases['civicrm_note']}.entity_id )";
+ }
+ //for contribution batches
+ if (!empty($this->_params['fields']['batch_id']) ||
+ !empty($this->_params['bid_value'])
+ ) {
+ $this->_from .= "
+ LEFT JOIN civicrm_entity_financial_trxn eft
+ ON eft.entity_id = {$this->_aliases['civicrm_contribution']}.id AND
+ eft.entity_table = 'civicrm_contribution'
+ LEFT JOIN civicrm_entity_batch {$this->_aliases['civicrm_batch']}
+ ON ({$this->_aliases['civicrm_batch']}.entity_id = eft.financial_trxn_id
+ AND {$this->_aliases['civicrm_batch']}.entity_table = 'civicrm_financial_trxn')";
+ }
+ // for credit card type
+ $this->addFinancialTrxnFromClause();
+ // for iats journal data
+ $this->addIatsJournalFromClause();
+ }
+
+ public function addIatsJournalFromClause() {
+ if ($this->isTableSelected('civicrm_iats_journal')) {
+ $this->_from .= "
+ LEFT JOIN civicrm_iats_journal {$this->_aliases['civicrm_iats_journal']}
+ ON {$this->_aliases['civicrm_contribution']}.invoice_id = {$this->_aliases['civicrm_iats_journal']}.inv \n";
+ }
+ }
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.mgd.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.mgd.php
new file mode 100644
index 00000000..2d2af53e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.mgd.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * This file declares a managed database record of type "ReportTemplate".
+ */
+
+// The record will be automatically inserted, updated, or deleted from the
+// database as appropriate. For more details, see "hook_civicrm_managed" at:
+// http://wiki.civicrm.org/confluence/display/CRMDOC42/Hook+Reference
+return array(
+ 0 =>
+ array(
+ 'name' => 'CRM_iATS_Form_Report_Journal',
+ 'entity' => 'ReportTemplate',
+ 'params' =>
+ array(
+ 'version' => 3,
+ 'label' => 'iATS Payments - Journal',
+ 'description' => 'iATS Payments - Journal Report',
+ 'class_name' => 'CRM_iATS_Form_Report_Journal',
+ 'report_url' => 'com.iatspayments.com/journal',
+ 'component' => 'CiviContribute',
+ ),
+ ),
+);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.php
new file mode 100644
index 00000000..e6c52d52
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.php
@@ -0,0 +1,116 @@
+<?php
+
+require_once('CRM/Report/Form.php');
+
+/**
+ * @file
+ */
+
+/**
+ *
+ * $Id$
+ */
+class CRM_iATS_Form_Report_Journal extends CRM_Report_Form {
+
+ // protected $_customGroupExtends = array('Contact');
+
+ /* static private $processors = array();
+ static private $version = array();
+ static private $financial_types = array();
+ static private $prefixes = array(); */
+ static private $contributionStatus = array();
+ static private $transaction_types = array(
+ 'VISA' => 'Visa',
+ 'ACHEFT' => 'ACH/EFT',
+ 'UNKNOW' => 'Uknown',
+ 'MC' => 'MasterCard',
+ 'AMX' => 'AMEX',
+ 'DSC' => 'Discover',
+ );
+
+ /**
+ *
+ */
+ public function __construct() {
+ self::$contributionStatus = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id');
+ $this->_columns = array(
+ 'civicrm_iats_journal' =>
+ array(
+ 'fields' =>
+ array(
+ 'id' => array('title' => 'CiviCRM Journal Id', 'default' => TRUE),
+ 'iats_id' => array('title' => 'iATS Journal Id', 'default' => TRUE),
+ 'tnid' => array('title' => 'Transaction ID', 'default' => TRUE),
+ 'tntyp' => array('title' => 'Transaction type', 'default' => TRUE),
+ 'agt' => array('title' => 'Client/Agent code', 'default' => TRUE),
+ 'cstc' => array('title' => 'Customer code', 'default' => TRUE),
+ 'inv' => array('title' => 'Invoice Reference', 'default' => TRUE),
+ 'dtm' => array('title' => 'Transaction date', 'default' => TRUE),
+ 'amt' => array('title' => 'Amount', 'default' => TRUE),
+ 'rst' => array('title' => 'Result string', 'default' => TRUE),
+ 'dtm' => array('title' => 'Transaction Date Time', 'default' => TRUE),
+ 'status_id' => array('title' => 'Payment Status', 'default' => TRUE),
+ ),
+ 'order_bys' =>
+ array(
+ 'id' => array('title' => ts('CiviCRM Journal Id'), 'default' => TRUE, 'default_order' => 'DESC'),
+ 'iats_id' => array('title' => ts('iATS Journal Id')),
+ 'dtm' => array('title' => ts('Transaction Date Time')),
+ ),
+ 'filters' =>
+ array(
+ 'dtm' => array(
+ 'title' => 'Transaction date',
+ 'operatorType' => CRM_Report_Form::OP_DATE,
+ 'type' => CRM_Utils_Type::T_DATE,
+ ),
+ 'inv' => array(
+ 'title' => 'Invoice Reference',
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ 'amt' => array(
+ 'title' => 'Amount',
+ 'operatorType' => CRM_Report_Form::OP_FLOAT,
+ 'type' => CRM_Utils_Type::T_FLOAT
+ ),
+ 'tntyp' => array(
+ 'title' => 'Type',
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => self::$transaction_types,
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ 'agt' => array(
+ 'title' => 'Client/Agent code',
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ 'rst' => array(
+ 'title' => 'Result string',
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ 'status_id' => array(
+ 'title' => ts('Payment Status'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => self::$contributionStatus,
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ ),
+ ),
+ );
+ parent::__construct();
+ }
+
+ /**
+ *
+ */
+ public function getTemplateName() {
+ return 'CRM/Report/Form.tpl';
+ }
+
+ /**
+ *
+ */
+ public function from() {
+ $this->_from = "FROM civicrm_iats_journal {$this->_aliases['civicrm_iats_journal']}";
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.mgd.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.mgd.php
new file mode 100644
index 00000000..507c4343
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.mgd.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * This file declares a managed database record of type "ReportTemplate".
+ */
+
+// The record will be automatically inserted, updated, or deleted from the
+// database as appropriate. For more details, see "hook_civicrm_managed" at:
+// http://wiki.civicrm.org/confluence/display/CRMDOC42/Hook+Reference
+return array(
+ 0 =>
+ array(
+ 'name' => 'CRM_iATS_Form_Report_Recur',
+ 'entity' => 'ReportTemplate',
+ 'params' =>
+ array(
+ 'version' => 3,
+ 'label' => 'iATS Payments - Recurring Contributions',
+ 'description' => 'iATS Payments - Recurring Contributions Report',
+ 'class_name' => 'CRM_iATS_Form_Report_Recur',
+ 'report_url' => 'com.iatspayments.com/recur',
+ 'component' => 'CiviContribute',
+ ),
+ ),
+);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.php
new file mode 100644
index 00000000..c5bbd1b7
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.php
@@ -0,0 +1,483 @@
+<?php
+
+/**
+ * @file
+ * +--------------------------------------------------------------------+
+ * | CiviCRM version 4.4 |
+ * +--------------------------------------------------------------------+
+ * | Copyright CiviCRM LLC (c) 2004-2013 |
+ * +--------------------------------------------------------------------+
+ * | This file is a part of CiviCRM. |
+ * | |
+ * | CiviCRM is free software; you can copy, modify, and distribute it |
+ * | under the terms of the GNU Affero General Public License |
+ * | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ * | |
+ * | CiviCRM is distributed in the hope that it will be useful, but |
+ * | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ * | See the GNU Affero General Public License for more details. |
+ * | |
+ * | You should have received a copy of the GNU Affero General Public |
+ * | License and the CiviCRM Licensing Exception along |
+ * | with this program; if not, contact CiviCRM LLC |
+ * | at info[AT]civicrm[DOT]org. If you have questions about the |
+ * | GNU Affero General Public License or the licensing of CiviCRM, |
+ * | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ * +--------------------------------------------------------------------+.
+ */
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2013
+ * $Id$
+ */
+class CRM_iATS_Form_Report_Recur extends CRM_Report_Form {
+
+ protected $_customGroupExtends = array('Contact');
+
+ static private $nscd_fid = '';
+ static private $processors = array();
+ static private $version = array();
+ static private $financial_types = array();
+ static private $prefixes = array();
+ static private $contributionStatus = array();
+
+ /**
+ *
+ */
+ public function __construct() {
+
+ self::$nscd_fid = _iats_civicrm_nscd_fid();
+ self::$version = _iats_civicrm_domain_info('version');
+ self::$financial_types = (self::$version[0] <= 4 && self::$version[1] <= 2) ? array() : CRM_Contribute_PseudoConstant::financialType();
+ if (self::$version[0] <= 4 && self::$version[1] < 4) {
+ self::$prefixes = CRM_Core_PseudoConstant::individualPrefix();
+ self::$contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus();
+ }
+ else {
+ self::$prefixes = CRM_Contact_BAO_Contact::buildOptions('individual_prefix_id');
+ self::$contributionStatus = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id');
+ }
+
+ $params = array('version' => 3, 'sequential' => 1, 'is_test' => 0, 'return.name' => 1);
+ $result = civicrm_api('PaymentProcessor', 'get', $params);
+ foreach ($result['values'] as $pp) {
+ self::$processors[$pp['id']] = $pp['name'];
+ }
+ $this->_columns = array(
+ 'civicrm_contact' => array(
+ 'dao' => 'CRM_Contact_DAO_Contact',
+ 'order_bys' => array(
+ 'sort_name' => array(
+ 'title' => ts("Last name, First name"),
+ ),
+ ),
+ 'fields' => array(
+ 'first_name' => array(
+ 'title' => ts('First Name'),
+ ),
+ 'last_name' => array(
+ 'title' => ts('Last Name'),
+ ),
+ 'prefix_id' => array(
+ 'title' => ts('Prefix'),
+ ),
+ 'sort_name' => array(
+ 'title' => ts('Contact Name'),
+ 'no_repeat' => TRUE,
+ 'default' => TRUE,
+ ),
+ 'id' => array(
+ 'no_display' => TRUE,
+ 'required' => TRUE,
+ ),
+ ),
+ ),
+ 'civicrm_email' => array(
+ 'dao' => 'CRM_Core_DAO_Email',
+ 'order_bys' => array(
+ 'email' => array(
+ 'title' => ts('Email'),
+ ),
+ ),
+ 'fields' => array(
+ 'email' => array(
+ 'title' => ts('Email'),
+ 'no_repeat' => TRUE,
+ ),
+ ),
+ 'grouping' => 'contact-fields',
+ ),
+ 'civicrm_phone' => array(
+ 'dao' => 'CRM_Core_DAO_Phone',
+ 'fields' => array(
+ 'phone' => array(
+ 'title' => ts('Phone'),
+ 'no_repeat' => TRUE,
+ ),
+ ),
+ 'grouping' => 'contact-fields',
+ ),
+ 'civicrm_contribution' => array(
+ 'dao' => 'CRM_Contribute_DAO_Contribution',
+ 'fields' => array(
+ 'id' => array(
+ // 'no_display' => TRUE,.
+ 'title' => ts('Contribution ID(s)'),
+ 'required' => TRUE,
+ 'dbAlias' => "GROUP_CONCAT(contribution_civireport.id SEPARATOR ', ')",
+ ),
+ 'total_amount' => array(
+ 'title' => ts('Amount Contributed to date'),
+ 'required' => TRUE,
+ 'statistics' => array(
+ 'sum' => ts("Total Amount contributed"),
+ ),
+ ),
+ ),
+ 'filters' => array(
+ 'total_amount' => array(
+ 'title' => ts('Total Amount'),
+ 'operatorType' => CRM_Report_Form::OP_FLOAT,
+ 'type' => CRM_Utils_Type::T_FLOAT,
+ ),
+ ),
+ ),
+ 'civicrm_iats_customer_codes' =>
+ array(
+ 'dao' => 'CRM_Contribute_DAO_Contribution',
+ 'order_bys' => array(
+ 'expiry' => array(
+ 'title' => ts("Expiry Date"),
+ ),
+ ),
+ 'fields' =>
+ array(
+ 'customer_code' => array('title' => 'customer code', 'default' => TRUE),
+ 'expiry' => array('title' => 'Expiry Date', 'default' => TRUE),
+ ),
+ ),
+ 'civicrm_contribution_recur' => array(
+ 'dao' => 'CRM_Contribute_DAO_ContributionRecur',
+ 'order_bys' => array(
+ 'id' => array(
+ 'title' => ts("Series ID"),
+ ),
+ 'amount' => array(
+ 'title' => ts("Current Amount"),
+ ),
+ 'start_date' => array(
+ 'title' => ts('Start Date'),
+ ),
+ 'modified_date' => array(
+ 'title' => ts('Modified Date'),
+ ),
+ self::$nscd_fid => array(
+ 'title' => ts('Next Scheduled Contribution Date'),
+ ),
+ 'cycle_day' => array(
+ 'title' => ts('Cycle Day'),
+ ),
+ 'failure_count' => array(
+ 'title' => ts('Failure Count'),
+ ),
+ 'payment_processor_id' => array(
+ 'title' => ts('Payment Processor'),
+ ),
+ ),
+ 'fields' => array(
+ 'id' => array(
+ // 'no_display' => TRUE,.
+ 'required' => TRUE,
+ 'title' => ts("Series ID"),
+ ),
+ 'recur_id' => array(
+ 'name' => 'id',
+ 'title' => ts('Series ID'),
+ ),
+ 'invoice_id' => array(
+ 'title' => ts('Invoice ID'),
+ 'default' => FALSE,
+ ),
+ 'currency' => array(
+ 'title' => ts("Currency"),
+ ),
+ 'amount' => array(
+ 'title' => ts('Amount'),
+ 'default' => TRUE,
+ ),
+ 'financial_type_id' => array(
+ 'title' => ts('Financial Type'),
+ 'default' => TRUE,
+ ),
+ 'contribution_status_id' => array(
+ 'title' => ts('Donation Status'),
+ ),
+ 'frequency_interval' => array(
+ 'title' => ts('Frequency interval'),
+ 'default' => TRUE,
+ ),
+ 'frequency_unit' => array(
+ 'title' => ts('Frequency unit'),
+ 'default' => TRUE,
+ ),
+ 'installments' => array(
+ 'title' => ts('Installments'),
+ 'default' => TRUE,
+ ),
+ 'start_date' => array(
+ 'title' => ts('Start Date'),
+ 'default' => TRUE,
+ ),
+ 'create_date' => array(
+ 'title' => ts('Create Date'),
+ ),
+ 'modified_date' => array(
+ 'title' => ts('Modified Date'),
+ ),
+ 'cancel_date' => array(
+ 'title' => ts('Cancel Date'),
+ ),
+ self::$nscd_fid => array(
+ 'title' => ts('Next Scheduled Contribution Date'),
+ 'default' => TRUE,
+ ),
+ 'cycle_day' => array(
+ 'title' => ts('Cycle Day'),
+ ),
+ 'failure_count' => array(
+ 'title' => ts('Failure Count'),
+ ),
+ 'failure_retry_date' => array(
+ 'title' => ts('Failure Retry Date'),
+ ),
+ 'payment_processor_id' => array(
+ 'title' => ts('Payment Processor'),
+ 'default' => TRUE,
+ ),
+ ),
+ 'filters' => array(
+ 'contribution_status_id' => array(
+ 'title' => ts('Donation Status'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => self::$contributionStatus,
+ 'default' => array(5),
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ 'payment_processor_id' => array(
+ 'title' => ts('Payment Processor'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => self::$processors,
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ 'currency' => array(
+ 'title' => 'Currency',
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => CRM_Core_OptionGroup::values('currencies_enabled'),
+ 'default' => NULL,
+ 'type' => CRM_Utils_Type::T_STRING,
+ ),
+ 'financial_type_id' => array(
+ 'title' => ts('Financial Type'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => self::$financial_types,
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ 'frequency_unit' => array(
+ 'title' => ts('Frequency Unit'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => CRM_Core_OptionGroup::values('recur_frequency_units'),
+ ),
+ self::$nscd_fid => array(
+ 'title' => ts('Next Scheduled Contribution Date'),
+ 'operatorType' => CRM_Report_Form::OP_DATE,
+ 'type' => CRM_Utils_Type::T_DATE,
+ ),
+ 'next_scheduled_day' => array(
+ 'title' => ts('Next Scheduled Day'),
+ 'operatorType' => CRM_Report_Form::OP_INT,
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ 'cycle_day' => array(
+ 'title' => ts('Cycle Day'),
+ 'operatorType' => CRM_Report_Form::OP_INT,
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ 'failure_count' => array(
+ 'title' => ts('Failure Count'),
+ 'operatorType' => CRM_Report_Form::OP_INT,
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ 'start_date' => array(
+ 'title' => ts('Start Date'),
+ 'operatorType' => CRM_Report_Form::OP_DATE,
+ 'type' => CRM_Utils_Type::T_DATE,
+ ),
+ 'modified_date' => array(
+ 'title' => ts('Modified Date'),
+ 'operatorType' => CRM_Report_Form::OP_DATE,
+ 'type' => CRM_Utils_Type::T_DATE,
+ ),
+ 'cancel_date' => array(
+ 'title' => ts('Cancel Date'),
+ 'operatorType' => CRM_Report_Form::OP_DATE,
+ 'type' => CRM_Utils_Type::T_DATE,
+ ),
+ ),
+ ),
+ 'civicrm_address' => array(
+ 'dao' => 'CRM_Core_DAO_Address',
+ 'fields' => array(
+ 'street_address' => array(
+ 'title' => ts('Address'),
+ 'default' => FALSE,
+ ),
+ 'supplemental_address_1' => array(
+ 'title' => ts('Supplementary Address Field 1'),
+ 'default' => FALSE,
+ ),
+ 'supplemental_address_2' => array(
+ 'title' => ts('Supplementary Address Field 2'),
+ 'default' => FALSE,
+ ),
+ 'city' => array(
+ 'title' => 'City',
+ 'default' => FALSE,
+ ),
+ 'state_province_id' => array(
+ 'title' => 'Province',
+ 'default' => FALSE,
+ 'alter_display' => 'alterStateProvinceID',
+ ),
+ 'postal_code' => array(
+ 'title' => 'Postal Code',
+ 'default' => FALSE,
+ ),
+ 'country_id' => array(
+ 'title' => 'Country',
+ 'default' => FALSE,
+ 'alter_display' => 'alterCountryID',
+ ),
+ ),
+ 'grouping' => 'contact-fields',
+ ),
+ );
+ if (empty(self::$financial_types)) {
+ unset($this->_columns['civicrm_contribution_recur']['filters']['financial_type_id']);
+ }
+ parent::__construct();
+ }
+
+ /**
+ *
+ */
+ public function getTemplateName() {
+ return 'CRM/Report/Form.tpl';
+ }
+
+ /**
+ *
+ */
+ public function from() {
+ $this->_from = "
+ FROM civicrm_contact {$this->_aliases['civicrm_contact']}
+ INNER JOIN civicrm_contribution_recur {$this->_aliases['civicrm_contribution_recur']}
+ ON {$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_contribution_recur']}.contact_id";
+ $this->_from .= "
+ LEFT JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']}
+ ON ({$this->_aliases['civicrm_contribution_recur']}.id = {$this->_aliases['civicrm_contribution']}.contribution_recur_id AND 1 = {$this->_aliases['civicrm_contribution']}.contribution_status_id)";
+ $this->_from .= "
+ LEFT JOIN civicrm_email {$this->_aliases['civicrm_email']}
+ ON ({$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_email']}.contact_id AND
+ {$this->_aliases['civicrm_email']}.is_primary = 1 )";
+ $this->_from .= "
+ LEFT JOIN civicrm_address {$this->_aliases['civicrm_address']}
+ ON ({$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_address']}.contact_id AND
+ {$this->_aliases['civicrm_address']}.is_primary = 1 )";
+ $this->_from .= "
+ LEFT JOIN civicrm_phone {$this->_aliases['civicrm_phone']}
+ ON ({$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_phone']}.contact_id AND
+ {$this->_aliases['civicrm_phone']}.is_primary = 1)";
+ $this->_from .= "
+ LEFT JOIN civicrm_iats_customer_codes {$this->_aliases['civicrm_iats_customer_codes']}
+ ON ({$this->_aliases['civicrm_iats_customer_codes']}.recur_id = {$this->_aliases['civicrm_contribution_recur']}.id)";
+ }
+
+ /**
+ *
+ */
+ public function groupBy() {
+ $this->_groupBy = "GROUP BY " . $this->_aliases['civicrm_contribution_recur'] . ".id";
+ }
+
+ /**
+ *
+ */
+ public function alterDisplay(&$rows) {
+ foreach ($rows as $rowNum => $row) {
+ // Convert display name to links.
+ if (array_key_exists('civicrm_contact_sort_name', $row) &&
+ CRM_Utils_Array::value('civicrm_contact_sort_name', $rows[$rowNum]) &&
+ array_key_exists('civicrm_contact_id', $row)
+ ) {
+ $url = CRM_Utils_System::url('civicrm/contact/view',
+ 'reset=1&cid=' . $row['civicrm_contact_id'],
+ $this->_absoluteUrl
+ );
+ $rows[$rowNum]['civicrm_contact_sort_name_link'] = $url;
+ $rows[$rowNum]['civicrm_contact_sort_name_hover'] = ts('View Contact Summary for this Contact.');
+ }
+
+ // Link to recurring series.
+ if (($value = CRM_Utils_Array::value('civicrm_contribution_recur_id', $row)) &&
+ CRM_Core_Permission::check('access CiviContribute')
+ ) {
+ $url = CRM_Utils_System::url("civicrm/contact/view/contributionrecur",
+ "reset=1&id=" . $row['civicrm_contribution_recur_id'] .
+ "&cid=" . $row['civicrm_contact_id'] .
+ "&action=view&context=contribution&selectedChild=contribute",
+ $this->_absoluteUrl
+ );
+ $rows[$rowNum]['civicrm_contribution_recur_id_link'] = $url;
+ $rows[$rowNum]['civicrm_contribution_recur_id_hover'] = ts("View Details of this Recurring Series.");
+ $entryFound = TRUE;
+ }
+
+ // Handle expiry date.
+ if ($value = CRM_Utils_Array::value('civicrm_iats_customer_codes_expiry', $row)) {
+ if ($rows[$rowNum]['civicrm_iats_customer_codes_expiry'] == '0000') {
+ $rows[$rowNum]['civicrm_iats_customer_codes_expiry'] = ' ';
+ }
+ elseif ($rows[$rowNum]['civicrm_iats_customer_codes_expiry'] != '0000') {
+ $rows[$rowNum]['civicrm_iats_customer_codes_expiry'] = '20' . substr($rows[$rowNum]['civicrm_iats_customer_codes_expiry'], 0, 2) . '/' . substr($rows[$rowNum]['civicrm_iats_customer_codes_expiry'], 2, 2);
+ }
+ }
+
+ // Handle contribution status id.
+ if ($value = CRM_Utils_Array::value('civicrm_contribution_recur_contribution_status_id', $row)) {
+ $rows[$rowNum]['civicrm_contribution_recur_contribution_status_id'] = self::$contributionStatus[$value];
+ }
+ // handle financial type id
+ if ($value = CRM_Utils_Array::value('civicrm_contribution_recur_financial_type_id', $row)) {
+ $rows[$rowNum]['civicrm_contribution_recur_financial_type_id'] = self::$financial_types[$value];
+ }
+ // Handle processor id.
+ if ($value = CRM_Utils_Array::value('civicrm_contribution_recur_payment_processor_id', $row)) {
+ $rows[$rowNum]['civicrm_contribution_recur_payment_processor_id'] = self::$processors[$value];
+ }
+ // Handle address country and province id => value conversion.
+ if ($value = CRM_Utils_Array::value('civicrm_address_country_id', $row)) {
+ $rows[$rowNum]['civicrm_address_country_id'] = CRM_Core_PseudoConstant::country($value, FALSE);
+ }
+ if ($value = CRM_Utils_Array::value('civicrm_address_state_province_id', $row)) {
+ $rows[$rowNum]['civicrm_address_state_province_id'] = CRM_Core_PseudoConstant::stateProvince($value, FALSE);
+ }
+ if ($value = CRM_Utils_Array::value('civicrm_contact_prefix_id', $row)) {
+ $rows[$rowNum]['civicrm_contact_prefix_id'] = self::$prefixes[$value];
+ }
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.mgd.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.mgd.php
new file mode 100644
index 00000000..bdada3da
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.mgd.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * This file declares a managed database record of type "ReportTemplate".
+ */
+
+// The record will be automatically inserted, updated, or deleted from the
+// database as appropriate. For more details, see "hook_civicrm_managed" at:
+// http://wiki.civicrm.org/confluence/display/CRMDOC42/Hook+Reference
+return array(
+ 0 =>
+ array(
+ 'name' => 'CRM_iATS_Form_Report_Verify',
+ 'entity' => 'ReportTemplate',
+ 'params' =>
+ array(
+ 'version' => 3,
+ 'label' => 'iATS Payments - Verify',
+ 'description' => 'iATS Payments - Verify Report',
+ 'class_name' => 'CRM_iATS_Form_Report_Verify',
+ 'report_url' => 'com.iatspayments.com/verify',
+ 'component' => 'CiviContribute',
+ ),
+ ),
+);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.php
new file mode 100644
index 00000000..f88e2486
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.php
@@ -0,0 +1,137 @@
+<?php
+
+require_once('CRM/Report/Form.php');
+
+/**
+ * @file
+ */
+
+/**
+ *
+ * $Id$
+ */
+class CRM_iATS_Form_Report_Verify extends CRM_Report_Form {
+
+ static private $contributionStatus = array();
+ static private $transaction_types = array(
+ 'VISA' => 'Visa',
+ 'ACHEFT' => 'ACH/EFT',
+ 'UNKNOW' => 'Uknown',
+ 'MC' => 'MasterCard',
+ 'AMX' => 'AMEX',
+ 'DSC' => 'Discover',
+ );
+
+ /**
+ *
+ */
+ public function __construct() {
+ self::$contributionStatus = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id');
+ $this->_columns = array(
+ 'civicrm_iats_verify' =>
+ array(
+ 'fields' =>
+ array(
+ 'id' => array('title' => 'CiviCRM Verify Id', 'default' => TRUE),
+ 'customer_code' => array('title' => 'Customer code', 'default' => TRUE),
+ 'cid' => array('title' => 'Contact', 'default' => TRUE),
+ 'contribution_id' => array('title' => 'Contribution', 'default' => TRUE),
+ 'recur_id' => array('title' => 'Recurring Contribution Id', 'default' => TRUE),
+ 'contribution_status_id' => array('title' => 'Payment Status', 'default' => TRUE),
+ 'verify_datetime' => array('title' => 'Verification date time', 'default' => TRUE),
+ ),
+ 'order_bys' =>
+ array(
+ 'id' => array('title' => ts('CiviCRM Verify Id'), 'default' => TRUE, 'default_order' => 'DESC'),
+ 'verify_datetime' => array('title' => ts('Verification Date Time')),
+ ),
+ 'filters' =>
+ array(
+ 'verify_datetime' => array(
+ 'title' => 'Verification date time',
+ 'operatorType' => CRM_Report_Form::OP_DATE,
+ 'type' => CRM_Utils_Type::T_DATE,
+ ),
+ 'contribution_status_id' => array(
+ 'title' => ts('Payment Status'),
+ 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+ 'options' => self::$contributionStatus,
+ 'type' => CRM_Utils_Type::T_INT,
+ ),
+ ),
+ ),
+ );
+ parent::__construct();
+ }
+
+ /**
+ *
+ */
+ public function getTemplateName() {
+ return 'CRM/Report/Form.tpl';
+ }
+
+ /**
+ *
+ */
+ public function from() {
+ $this->_from = "FROM civicrm_iats_verify {$this->_aliases['civicrm_iats_verify']}";
+ }
+
+ /**
+ *
+ */
+ public function alterDisplay(&$rows) {
+ foreach ($rows as $rowNum => $row) {
+ // Link to contact.
+ if (
+ ($value = CRM_Utils_Array::value('civicrm_iats_verify_cid', $row))
+ ) {
+ $url = CRM_Utils_System::url('civicrm/contact/view',
+ 'reset=1&cid=' . $row['civicrm_iats_verify_cid'],
+ $this->_absoluteUrl
+ );
+ $rows[$rowNum]['civicrm_iats_verify_cid_link'] = $url;
+ $rows[$rowNum]['civicrm_iats_verify_cid_hover'] = ts('View this contact.');
+ }
+ // Link to contribution.
+ if (
+ ($value = CRM_Utils_Array::value('civicrm_iats_verify_cid', $row))
+ && ($value = CRM_Utils_Array::value('civicrm_iats_verify_contribution_id', $row))
+ ) {
+ $url = CRM_Utils_System::url('civicrm/contact/view/contribution',
+ 'reset=1&action=view&context=contribution&selectedChild=contribute&cid=' . $row['civicrm_iats_verify_cid'] . '&id=' . $row['civicrm_iats_verify_contribution_id'],
+ $this->_absoluteUrl
+ );
+ $rows[$rowNum]['civicrm_iats_verify_contribution_id_link'] = $url;
+ $rows[$rowNum]['civicrm_iats_verify_contribution_id_hover'] = ts('View details of the verified contribution.');
+ }
+
+ // Link to recurring series.
+ if (
+ ($value = CRM_Utils_Array::value('civicrm_iats_verify_recur_id', $row))
+ && ($value = CRM_Utils_Array::value('civicrm_iats_verify_cid', $row))
+ && CRM_Core_Permission::check('access CiviContribute')
+ ) {
+ $url = CRM_Utils_System::url("civicrm/contact/view/contributionrecur",
+ "reset=1&id=" . $row['civicrm_iats_verify_recur_id'] .
+ "&cid=" . $row['civicrm_iats_verify_cid'] .
+ "&action=view&context=contribution&selectedChild=contribute",
+ $this->_absoluteUrl
+ );
+ $rows[$rowNum]['civicrm_iats_verify_recur_id_link'] = $url;
+ $rows[$rowNum]['civicrm_iats_verify_recur_id_hover'] = ts("View Details of this Recurring Series.");
+ $entryFound = TRUE;
+ }
+
+ // Handle contribution status id.
+ if ($value = CRM_Utils_Array::value('civicrm_iats_verify_contribution_status_id', $row)) {
+ $rows[$rowNum]['civicrm_iats_verify_contribution_status_id'] = self::$contributionStatus[$value];
+ }
+ // Handle processor id.
+ if ($value = CRM_Utils_Array::value('civicrm_iats_verify_recur_payment_processor_id', $row)) {
+ $rows[$rowNum]['civicrm_iats_verify_recur_payment_processor_id'] = self::$processors[$value];
+ }
+ }
+ }
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/IATSCustomerLink.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/IATSCustomerLink.php
new file mode 100644
index 00000000..c96cd8f6
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/IATSCustomerLink.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ */
+
+require_once 'CRM/Core/Page.php';
+/**
+ *
+ */
+class CRM_iATS_Page_IATSCustomerLink extends CRM_Core_Page {
+
+ /**
+ *
+ */
+ public function run() {
+ // TODO: use the cid value to put the customer name in the title?
+ // CRM_Utils_System::setTitle(ts('iATS CustomerLink'));.
+ $customerCode = CRM_Utils_Request::retrieve('customerCode', 'String');
+ $paymentProcessorId = CRM_Utils_Request::retrieve('paymentProcessorId', 'Positive');
+ $is_test = CRM_Utils_Request::retrieve('is_test', 'Integer');
+ $this->assign('customerCode', $customerCode);
+ require_once "CRM/iATS/iATSService.php";
+ $credentials = iATS_Service_Request::credentials($paymentProcessorId, $is_test);
+ $iats_service_params = array('type' => 'customer', 'iats_domain' => $credentials['domain'], 'method' => 'get_customer_code_detail');
+ $iats = new iATS_Service_Request($iats_service_params);
+ // print_r($iats); die();
+ $request = array('customerCode' => $customerCode);
+ // Make the soap request.
+ $response = $iats->request($credentials, $request);
+ // note: don't log this to the iats_response table.
+ $customer = $iats->result($response, FALSE);
+ if (empty($customer['ac1'])) {
+ $alert = ts('Unable to retrieve card details from iATS.<br />%1', array(1 => $customer['AUTHORIZATIONRESULT']));
+ CRM_Core_Session::setStatus($alert, ts('Warning'), 'alert');
+ }
+ else {
+ // This is a SimpleXMLElement Object.
+ $ac1 = $customer['ac1'];
+ $attributes = $ac1->attributes();
+ $type = $attributes['type'];
+ $card = get_object_vars($ac1->$type);
+ $card['type'] = $type;
+ foreach (array('ac1', 'status', 'remote_id', 'auth_result') as $key) {
+ if (isset($customer[$key])) {
+ unset($customer[$key]);
+ }
+ }
+ $this->assign('customer', $customer);
+ $this->assign('card', $card);
+ }
+ parent::run();
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/iATSAdmin.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/iATSAdmin.php
new file mode 100644
index 00000000..6c34df42
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/iATSAdmin.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file This administrative page provides simple access to recent transactions
+ * and an opportunity for the system to warn administrators about failing
+ * crons .*/
+
+require_once 'CRM/Core/Page.php';
+/**
+ *
+ */
+class CRM_iATS_Page_iATSAdmin extends CRM_Core_Page {
+
+ /**
+ *
+ */
+ public function run() {
+ // Reset the saved version of the extension.
+ require_once 'CRM/iATS/iATSService.php';
+ $iats_extension_version = iATS_Service_Request::iats_extension_version(1);
+ // The current time.
+ $this->assign('currentVersion', $iats_extension_version);
+ $this->assign('currentTime', date('Y-m-d H:i:s'));
+ $this->assign('jobLastRunWarning', '0');
+ // Check if I've got any recurring contributions setup. In theory I should only worry about iATS, but it's a problem regardless ..
+ $params = array('version' => 3, 'sequential' => 1);
+ $result = civicrm_api('ContributionRecur', 'getcount', $params);
+ if (!empty($result)) {
+ $this->assign('jobLastRunWarning', '1');
+ $params['api_action'] = 'Iatsrecurringcontributions';
+ $job = civicrm_api('Job', 'getSingle', $params);
+ $last_run = isset($job['last_run']) ? strtotime($job['last_run']) : '';
+ $this->assign('jobLastRun', $job['last_run']);
+ $this->assign('jobOverdue', '');
+ $overdueHours = (time() - $last_run) / (60 * 60);
+ if (24 < $overdueHours) {
+ $this->assign('jobOverdue', $overdueHours);
+ }
+ }
+ // Load the most recent requests and responses from the log files.
+ foreach (array('cc', 'auth_result') as $key) {
+ $search[$key] = empty($_GET['search_' . $key]) ? '' : filter_var($_GET['search_' . $key], FILTER_SANITIZE_STRING);
+ }
+ $log = $this->getLog($search);
+ // $log[] = array('cc' => 'test', 'ip' => 'whatever', 'auth_result' => 'blah');.
+ $this->assign('iATSLog', $log);
+ $this->assign('search', $search);
+ parent::run();
+ }
+
+ /**
+ *
+ */
+ public function getLog($search = array(), $n = 40) {
+ // Avoid sql injection attacks.
+ $n = (int) $n;
+ $filter = array();
+ foreach ($search as $key => $value) {
+ if (!empty($value)) {
+ $filter[] = "$key RLIKE '$value'";
+ }
+ }
+ $where = empty($filter) ? '' : " WHERE " . implode(' AND ', $filter);
+ $sql = "SELECT request.*,response.*,contrib.contact_id,contact.sort_name,pp.url_site,pp.user_name,pp.password
+ FROM civicrm_iats_request_log request
+ LEFT JOIN civicrm_iats_response_log response ON request.invoice_num = response.invoice_num
+ LEFT JOIN civicrm_contribution contrib ON request.invoice_num = contrib.invoice_id
+ LEFT JOIN civicrm_contact contact ON contrib.contact_id = contact.id
+ LEFT JOIN civicrm_contribution_recur recur ON contrib.contribution_recur_id = recur.id
+ LEFT JOIN civicrm_payment_processor pp ON recur.payment_processor_id = pp.id
+ $where ORDER BY request.id DESC LIMIT $n";
+
+ $dao = CRM_Core_DAO::executeQuery($sql);
+ $log = array();
+ $params = array('version' => 3, 'sequential' => 1, 'return' => 'contribution_id');
+ $className = get_class($dao);
+ $internal = array_keys(get_class_vars($className));
+ // Get some customer data while i'm at it
+ // require_once("CRM/iATS/iATSService.php");
+ // todo: fix iats_domain below
+ // $iats_service_params = array('type' => 'customer', 'method' => 'get_customer_code_detail', 'iats_domain' => 'www.iatspayments.com');.
+ while ($dao->fetch()) {
+ $entry = get_object_vars($dao);
+ // Ghost entry!
+ unset($entry['']);
+ // Remove internal fields.
+ foreach ($internal as $key) {
+ unset($entry[$key]);
+ }
+ $params['invoice_id'] = $entry['invoice_num'];
+ $result = civicrm_api('Contribution', 'getsingle', $params);
+ if (!empty($result['contribution_id'])) {
+ $entry += $result;
+ $entry['contributionURL'] = CRM_Utils_System::url('civicrm/contact/view/contribution', 'reset=1&id=' . $entry['contribution_id'] . '&cid=' . $entry['contact_id'] . '&action=view&selectedChild=Contribute');
+ }
+ if (!empty($result['contact_id'])) {
+ $entry['contactURL'] = CRM_Utils_System::url('civicrm/contact/view', 'reset=1&cid=' . $entry['contact_id']);
+ }
+ $log[] = $entry;
+ }
+ return $log;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/json.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/json.php
new file mode 100644
index 00000000..15faf8ed
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Page/json.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ */
+
+/**
+ *
+ */
+class CRM_iATS_Page_json {
+
+ /**
+ *
+ */
+ public function run() {
+ // Generate json output from iats service calls.
+ $request = $_POST;
+ $pp_id = (int) $request['payment_processor_id'];
+ if (empty($pp_id)) {
+ return;
+ }
+ $params = array('version' => 3, 'sequential' => 1, 'id' => $pp_id, 'return' => 'user_name');
+ $result = civicrm_api('PaymentProcessor', 'getvalue', $params);
+ $request['agentCode'] = $result;
+ $params = array('version' => 3, 'sequential' => 1, 'id' => $pp_id, 'return' => 'url_site');
+ $result = civicrm_api('PaymentProcessor', 'getvalue', $params);
+ $request['iats_domain'] = parse_url($result, PHP_URL_HOST);
+ foreach (array('reset', 'q', 'IDS_request_uri', 'IDS_user_agent', 'payment_processor_id') as $key) {
+ if (isset($request[$key])) {
+ unset($request[$key]);
+ }
+ }
+ $options = array();
+ foreach (array('type', 'method', 'iats_domain') as $key) {
+ if (isset($request[$key])) {
+ $options[$key] = $request[$key];
+ unset($request[$key]);
+ }
+ }
+ $credentials = array();
+ foreach (array('agentCode', 'password') as $key) {
+ if (isset($request[$key])) {
+ $credentials[$key] = $request[$key];
+ unset($request[$key]);
+ }
+ }
+ // TODO: bail here if I don't have enough for my service request
+ // use the iATSService object for interacting with iATS.
+ require_once "CRM/iATS/iATSService.php";
+ $iats = new iATS_Service_Request($options);
+ $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
+ // Make the soap request.
+ $response = $iats->request($credentials, $request);
+ // Process the soap response into a readable result.
+ if (!empty($response)) {
+ $result = $iats->result($response);
+ }
+ else {
+ $result = array('Invalid request');
+ }
+ // TODO: fix header
+ // header('Content-Type: text/javascript');.
+ echo json_encode(array_merge($result));
+ exit;
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Upgrader.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Upgrader.php
new file mode 100644
index 00000000..74d6ff61
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Upgrader.php
@@ -0,0 +1,213 @@
+<?php
+
+/**
+ * Collection of upgrade steps
+ */
+class CRM_iATS_Upgrader extends CRM_iATS_Upgrader_Base {
+
+ // By convention, functions that look like "function upgrade_NNNN()" are
+ // upgrade tasks. They are executed in order (like Drupal's hook_update_N).
+
+ public function getCurrentRevision() {
+ // reset the saved extension version as well
+ try {
+ $xmlfile = CRM_Core_Resources::singleton()->getPath('com.iatspayments.civicrm','info.xml');
+ $myxml = simplexml_load_file($xmlfile);
+ $version = (string)$myxml->version;
+ CRM_Core_BAO_Setting::setItem($version, 'iATS Payments Extension', 'iats_extension_version');
+ }
+ catch (Exception $e) {
+ // ignore
+ }
+ return parent::getCurrentRevision();
+ }
+ /**
+ * Standard: run an install sql script
+ */
+ public function install() {
+ $this->executeSqlFile('sql/install.sql');
+ }
+
+ /**
+ * Standard: run an uninstall script
+ */
+ public function uninstall() {
+ $this->executeSqlFile('sql/uninstall.sql');
+ }
+
+ public function upgrade_1_2_010() {
+ CRM_Core_ManagedEntities::singleton(TRUE)->reconcile();
+ return TRUE;
+ }
+
+ /**
+ * Example: Run a simple query when a module is enabled
+ *
+ public function enable() {
+ CRM_Core_DAO::executeQuery('UPDATE foo SET is_active = 1 WHERE bar = "whiz"');
+ }
+ */
+
+ /**
+ * Example: Run a simple query when a module is disabled
+ *
+ public function disable() {
+ CRM_Core_DAO::executeQuery('UPDATE foo SET is_active = 0 WHERE bar = "whiz"');
+ }
+ */
+
+ /**
+ * Add the uk_dd table
+ *
+ * @return TRUE on success
+ * @throws Exception
+ */
+ public function upgrade_1_3_001() {
+ $this->ctx->log->info('Applying update 1_3_001');
+ $this->executeSqlFile('sql/upgrade_1_3_001.sql');
+ return TRUE;
+ }
+
+ public function upgrade_1_3_002() {
+ CRM_Core_ManagedEntities::singleton(TRUE)->reconcile();
+ return TRUE;
+ }
+
+ public function upgrade_1_4_001() {
+ // reset iATS Extension Version in the civicrm_setting table
+ CRM_Core_BAO_Setting::setItem(NULL, 'iATS Payments Extension', 'iats_extension_version');
+ return TRUE;
+ }
+
+ public function upgrade_1_5_000() {
+ // reset iATS Extension Version in the civicrm_setting table
+ CRM_Core_BAO_Setting::setItem(NULL, 'iATS Payments Extension', 'iats_extension_version');
+ return TRUE;
+ }
+
+ public function upgrade_1_5_003() {
+ // populate the new payment instrument id fields in the payment_processor and payment_processor_type fields
+ $version = CRM_Utils_System::version();
+ if (version_compare($version, '4.7') >= 0) {
+ $this->executeSqlFile('sql/upgrade_1_5_003.sql');
+ }
+ return TRUE;
+ }
+
+ public function upgrade_1_6_001() {
+ $this->ctx->log->info('Applying update 1_6_001');
+ try {
+ $this->executeSqlFile('sql/upgrade_1_6_001.sql');
+ }
+ catch (Exception $e) {
+ $this->ctx->log->info($e->getMessage());
+ }
+ return TRUE;
+ }
+
+ public function upgrade_1_6_002() {
+ $this->ctx->log->info('Applying update 1_6_002');
+ try {
+ $this->executeSqlFile('sql/upgrade_1_6_002.sql');
+ }
+ catch (Exception $e) {
+ $this->ctx->log->info($e->getMessage());
+ }
+ return TRUE;
+ }
+
+ public function upgrade_1_6_003() {
+ $this->ctx->log->info('(Re)applying update 1_5_003');
+ try {
+ $this->ctx->log->info('(Re)applying update 1_5_003');
+ $this->executeSqlFile('sql/upgrade_1_5_003.sql');
+ }
+ catch (Exception $e) {
+ $this->ctx->log->info($e->getMessage());
+ }
+ try {
+ $this->ctx->log->info('Setting payment instrument label');
+ $acheft_option_value_id = civicrm_api3('OptionValue', 'getvalue', array('return' => 'id', 'value' => 2, 'option_group_id' => 'payment_instrument'));
+ civicrm_api3('OptionValue', 'create', array('label' => 'ACHEFT', 'id' => $acheft_option_value_id));
+ }
+ catch (Exception $e) {
+ $this->ctx->log->info($e->getMessage());
+ }
+ return TRUE;
+ }
+
+ public function upgrade_1_6_004() {
+ $this->ctx->log->info('Applying update 1_6_004');
+ try {
+ $this->executeSqlFile('sql/upgrade_1_6_004.sql');
+ }
+ catch (Exception $e) {
+ $this->ctx->log->info($e->getMessage());
+ }
+ return TRUE;
+ }
+
+
+ /**
+ * Example: Run an external SQL script
+ *
+ * @return TRUE on success
+ * @throws Exception
+ public function upgrade_4201() {
+ $this->ctx->log->info('Applying update 4201');
+ // this path is relative to the extension base dir
+ $this->executeSqlFile('sql/upgrade_4201.sql');
+ return TRUE;
+ } // */
+
+
+ /**
+ * Example: Run a slow upgrade process by breaking it up into smaller chunk
+ *
+ * @return TRUE on success
+ * @throws Exception
+ public function upgrade_4202() {
+ $this->ctx->log->info('Planning update 4202'); // PEAR Log interface
+
+ $this->addTask(ts('Process first step'), 'processPart1', $arg1, $arg2);
+ $this->addTask(ts('Process second step'), 'processPart2', $arg3, $arg4);
+ $this->addTask(ts('Process second step'), 'processPart3', $arg5);
+ return TRUE;
+ }
+ public function processPart1($arg1, $arg2) { sleep(10); return TRUE; }
+ public function processPart2($arg3, $arg4) { sleep(10); return TRUE; }
+ public function processPart3($arg5) { sleep(10); return TRUE; }
+ // */
+
+
+ /**
+ * Example: Run an upgrade with a query that touches many (potentially
+ * millions) of records by breaking it up into smaller chunks.
+ *
+ * @return TRUE on success
+ * @throws Exception
+ public function upgrade_4203() {
+ $this->ctx->log->info('Planning update 4203'); // PEAR Log interface
+
+ $minId = CRM_Core_DAO::singleValueQuery('SELECT coalesce(min(id),0) FROM civicrm_contribution');
+ $maxId = CRM_Core_DAO::singleValueQuery('SELECT coalesce(max(id),0) FROM civicrm_contribution');
+ for ($startId = $minId; $startId <= $maxId; $startId += self::BATCH_SIZE) {
+ $endId = $startId + self::BATCH_SIZE - 1;
+ $title = ts('Upgrade Batch (%1 => %2)', array(
+ 1 => $startId,
+ 2 => $endId,
+ ));
+ $sql = '
+ UPDATE civicrm_contribution SET foobar = whiz(wonky()+wanker)
+ WHERE id BETWEEN %1 and %2
+ ';
+ $params = array(
+ 1 => array($startId, 'Integer'),
+ 2 => array($endId, 'Integer'),
+ );
+ $this->addTask($title, 'executeSql', $sql, $params);
+ }
+ return TRUE;
+ } // */
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Upgrader/Base.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Upgrader/Base.php
new file mode 100644
index 00000000..6cf61525
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/Upgrader/Base.php
@@ -0,0 +1,320 @@
+<?php
+
+/**
+ * @file
+ * AUTO-GENERATED FILE -- Civix may overwrite any changes made to this file.
+ */
+
+/**
+ * Base class which provides helpers to execute upgrade logic.
+ */
+class CRM_iATS_Upgrader_Base {
+
+ /**
+ * @var variessubclassofhtis
+ */
+ static $instance;
+
+ /**
+ * @var CRM_Queue_TaskContext
+ */
+ protected $ctx;
+
+ /**
+ * @var stringegcomexamplemyextension
+ */
+ protected $extensionName;
+
+ /**
+ * @var stringfullpathtotheextensionssourcetree
+ */
+ protected $extensionDir;
+
+ /**
+ * @var arrayrevisionNumbersortednumerically
+ */
+ private $revisions;
+
+ /**
+ * Obtain a refernece to the active upgrade handler.
+ */
+ static public function instance() {
+ if (!self::$instance) {
+ // FIXME auto-generate.
+ self::$instance = new CRM_iATS_Upgrader(
+ 'com.iatspayments.civicrm',
+ realpath(__DIR__ . '/../../../')
+ );
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Adapter that lets you add normal (non-static) member functions to the queue.
+ *
+ * Note: Each upgrader instance should only be associated with one
+ * task-context; otherwise, this will be non-reentrant.
+ *
+ * @code
+ * CRM_iATS_Upgrader_Base::_queueAdapter($ctx, 'methodName', 'arg1', 'arg2');
+ * @endcode
+ */
+ static public function _queueAdapter() {
+ $instance = self::instance();
+ $args = func_get_args();
+ $instance->ctx = array_shift($args);
+ $instance->queue = $instance->ctx->queue;
+ $method = array_shift($args);
+ return call_user_func_array(array($instance, $method), $args);
+ }
+
+ /**
+ *
+ */
+ public function __construct($extensionName, $extensionDir) {
+ $this->extensionName = $extensionName;
+ $this->extensionDir = $extensionDir;
+ }
+
+ // ******** Task helpers ********.
+ /**
+ * Run a CustomData file.
+ *
+ * @param string $relativePath
+ * the CustomData XML file path (relative to this extension's dir)
+ *
+ * @return bool
+ */
+ public function executeCustomDataFile($relativePath) {
+ $xml_file = $this->extensionDir . '/' . $relativePath;
+ return $this->executeCustomDataFileByAbsPath($xml_file);
+ }
+
+ /**
+ * Run a CustomData file.
+ *
+ * @param string $xml_file
+ * the CustomData XML file path (absolute path)
+ *
+ * @return bool
+ */
+ protected static function executeCustomDataFileByAbsPath($xml_file) {
+ require_once 'CRM/Utils/Migrate/Import.php';
+ $import = new CRM_Utils_Migrate_Import();
+ $import->run($xml_file);
+ return TRUE;
+ }
+
+ /**
+ * Run a SQL file.
+ *
+ * @param string $relativePath
+ * the SQL file path (relative to this extension's dir)
+ *
+ * @return bool
+ */
+ public function executeSqlFile($relativePath) {
+ CRM_Utils_File::sourceSQLFile(
+ CIVICRM_DSN,
+ $this->extensionDir . '/' . $relativePath
+ );
+ return TRUE;
+ }
+
+ /**
+ * Run one SQL query.
+ *
+ * This is just a wrapper for CRM_Core_DAO::executeSql, but it
+ * provides syntatic sugar for queueing several tasks that
+ * run different queries.
+ */
+ public function executeSql($query, $params = array()) {
+ // FIXME verify that we raise an exception on error.
+ CRM_Core_DAO::executeSql($query, $params);
+ return TRUE;
+ }
+
+ /**
+ * Syntatic sugar for enqueuing a task which calls a function
+ * in this class. The task is weighted so that it is processed
+ * as part of the currently-pending revision.
+ *
+ * After passing the $funcName, you can also pass parameters that will go to
+ * the function. Note that all params must be serializable.
+ */
+ public function addTask($title) {
+ $args = func_get_args();
+ $title = array_shift($args);
+ $task = new CRM_Queue_Task(
+ array(get_class($this), '_queueAdapter'),
+ $args,
+ $title
+ );
+ return $this->queue->createItem($task, array('weight' => -1));
+ }
+
+ // ******** Revision-tracking helpers ********.
+
+/**
+ * Determine if there are any pending revisions.
+ *
+ * @return bool
+ */
+ public function hasPendingRevisions() {
+ $revisions = $this->getRevisions();
+ $currentRevision = $this->getCurrentRevision();
+
+ if (empty($revisions)) {
+ return FALSE;
+ }
+ if (empty($currentRevision)) {
+ return TRUE;
+ }
+
+ return ($currentRevision < max($revisions));
+ }
+
+ /**
+ * Add any pending revisions to the queue.
+ */
+ public function enqueuePendingRevisions(CRM_Queue_Queue $queue) {
+ $this->queue = $queue;
+
+ $currentRevision = $this->getCurrentRevision();
+ foreach ($this->getRevisions() as $revision) {
+ if ($revision > $currentRevision) {
+ $title = ts('Upgrade %1 to revision %2', array(
+ 1 => $this->extensionName,
+ 2 => $revision,
+ ));
+
+ // note: don't use addTask() because it sets weight=-1.
+ $task = new CRM_Queue_Task(
+ array(get_class($this), '_queueAdapter'),
+ array('upgrade_' . $revision),
+ $title
+ );
+ $this->queue->createItem($task);
+
+ $task = new CRM_Queue_Task(
+ array(get_class($this), '_queueAdapter'),
+ array('setCurrentRevision', $revision),
+ $title
+ );
+ $this->queue->createItem($task);
+ }
+ }
+ }
+
+ /**
+ * Get a list of revisions.
+ *
+ * @return array(revisionNumbers) sorted numerically
+ */
+ public function getRevisions() {
+ if (!is_array($this->revisions)) {
+ $this->revisions = array();
+
+ $clazz = new ReflectionClass(get_class($this));
+ $methods = $clazz->getMethods();
+ foreach ($methods as $method) {
+ if (preg_match('/^upgrade_(.*)/', $method->name, $matches)) {
+ $this->revisions[] = $matches[1];
+ }
+ }
+ sort($this->revisions, SORT_NUMERIC);
+ }
+
+ return $this->revisions;
+ }
+
+ /**
+ *
+ */
+ public function getCurrentRevision() {
+ // Return CRM_Core_BAO_Extension::getSchemaVersion($this->extensionName);.
+ $key = $this->extensionName . ':version';
+ return CRM_Core_BAO_Setting::getItem('Extension', $key);
+ }
+
+ /**
+ *
+ */
+ public function setCurrentRevision($revision) {
+ // We call this during hook_civicrm_install, but the underlying SQL
+ // UPDATE fails because the extension record hasn't been INSERTed yet.
+ // Instead, track revisions in our own namespace.
+ // CRM_Core_BAO_Extension::setSchemaVersion($this->extensionName, $revision);.
+ $key = $this->extensionName . ':version';
+ CRM_Core_BAO_Setting::setItem($revision, 'Extension', $key);
+ return TRUE;
+ }
+
+ /**
+ * Hook delegates ********.
+ */
+ public function onInstall() {
+ foreach (glob($this->extensionDir . '/sql/*_install.sql') as $file) {
+ CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
+ }
+ foreach (glob($this->extensionDir . '/xml/*_install.xml') as $file) {
+ $this->executeCustomDataFileByAbsPath($file);
+ }
+ if (is_callable(array($this, 'install'))) {
+ $this->install();
+ }
+ $revisions = $this->getRevisions();
+ if (!empty($revisions)) {
+ $this->setCurrentRevision(max($revisions));
+ }
+ }
+
+ /**
+ *
+ */
+ public function onUninstall() {
+ if (is_callable(array($this, 'uninstall'))) {
+ $this->uninstall();
+ }
+ foreach (glob($this->extensionDir . '/sql/*_uninstall.sql') as $file) {
+ CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
+ }
+ $this->setCurrentRevision(NULL);
+ }
+
+ /**
+ *
+ */
+ public function onEnable() {
+ // Stub for possible future use.
+ if (is_callable(array($this, 'enable'))) {
+ $this->enable();
+ }
+ }
+
+ /**
+ *
+ */
+ public function onDisable() {
+ // Stub for possible future use.
+ if (is_callable(array($this, 'disable'))) {
+ $this->disable();
+ }
+ }
+
+ /**
+ *
+ */
+ public function onUpgrade($op, CRM_Queue_Queue $queue = NULL) {
+ switch ($op) {
+ case 'check':
+ return array($this->hasPendingRevisions());
+
+ case 'enqueue':
+ return $this->enqueuePendingRevisions($queue);
+
+ default:
+ }
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/iATSService.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/iATSService.php
new file mode 100644
index 00000000..6c6d02ca
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/CRM/iATS/iATSService.php
@@ -0,0 +1,887 @@
+<?php
+
+/**
+ * @file IATS Service Request Object used for accessing iATS Service Interface.
+ *
+ * A lightweight object that encapsulates the details of the iATS Payments interface.
+ *
+ * Provides SOAP interface details for the various methods,
+ * error messages, and cc details
+ *
+ * Require the method id string on construction and any options like trace, logging.
+ * Require the specific payment details, and the client credentials, on request
+ *
+ * TODO: provide logging options for the request, exception and response
+ *
+ * Expected usage:
+ * $iats = new iATS_Service_Request($options)
+ * where options usually include
+ * type: 'report', 'customer', 'process'
+ * method: 'cc', etc. as appropriate for that type
+ * iats_domain: the domain for the api (us or uk currently)
+ * $response = $iats->request($credentials, $request_params)
+ * the request method encapsulates the soap inteface and requires iATS client details + payment info (cc + amount + billing info)
+ * $result = $iats->response($response)
+ * the 'response' method converts the soap response into a nicer format
+ **/
+
+/**
+ *
+ */
+class iATS_Service_Request {
+
+ // iATS transaction mode definitions:
+ const iATS_TXN_NS = 'xmlns';
+ const iATS_TXN_TRACE = TRUE;
+ const iATS_TXN_SUCCESS = 'Success';
+ const iATS_TXN_OK = 'OK';
+ const iATS_URL_PROCESSLINK = '/NetGate/ProcessLinkv2.asmx?WSDL';
+ const iATS_URL_REPORTLINK = '/NetGate/ReportLinkv2.asmx?WSDL';
+ const iATS_URL_CUSTOMERLINK = '/NetGate/CustomerLinkv2.asmx?WSDL';
+ // TODO: confirm with Stephen if this needs a v2 as well:
+ const iATS_URL_DPMPROCESS = '/NetGate/IATSDPMProcess.aspx';
+ const iATS_USE_DPMPROCESS = FALSE;
+
+ /**
+ *
+ */
+ public function __construct($options) {
+ $this->type = isset($options['type']) ? $options['type'] : 'process';
+ $method = $options['method'];
+ $iats_domain = $options['iats_domain'];
+ switch ($this->type) {
+ case 'report':
+ $this->_wsdl_url = 'https://' . $iats_domain . self::iATS_URL_REPORTLINK;
+ break;
+
+ case 'customer':
+ $this->_wsdl_url = 'https://' . $iats_domain . self::iATS_URL_CUSTOMERLINK;
+ break;
+
+ case 'process':
+ default:
+ $this->_wsdl_url = 'https://' . $iats_domain . self::iATS_URL_PROCESSLINK;
+ if ($method == 'cc') {/* as suggested by iATS, though not necessary I believe */
+ $this->_tag_order = array('agentCode', 'password', 'customerIPAddress', 'invoiceNum', 'creditCardNum', 'ccNum', 'creditCardExpiry', 'ccExp', 'firstName', 'lastName', 'address', 'city', 'state', 'zipCode', 'cvv2', 'total', 'comment');
+ }
+ break;
+ }
+ // TODO: check that the method is allowed!
+ $this->method = $this->methodInfo($this->type, $method);
+ // Initialize the request array.
+ $this->request = array();
+ // Name space url.
+ $this->_wsdl_url_ns = 'https://www.iatspayments.com/NetGate/';
+ $this->options = $options;
+ $this->options['debug'] = _iats_civicrm_domain_info('debug_enabled');
+ // Check for valid currencies with domain/method combinations.
+ if (isset($options['currencyID'])) {
+ $valid = FALSE;
+ switch ($iats_domain) {
+ case 'www2.iatspayments.com':
+ case 'www.iatspayments.com':
+ if (in_array($options['currencyID'], array('USD', 'CAD'))) {
+ $valid = TRUE;
+ }
+ break;
+
+ case 'www.uk.iatspayments.com':
+ if ('cc' == substr($method, 0, 2) || 'create_credit_card_customer' == $method) {
+ if (in_array($options['currencyID'], array('AUD', 'USD', 'EUR', 'GBP', 'IEE', 'CHF', 'HKD', 'JPY', 'SGD', 'MXN'))) {
+ $valid = TRUE;
+ }
+ }
+ elseif ('direct_debit' == substr($method, 0, 12)) {
+ if (in_array($options['currencyID'], array('GBP'))) {
+ $valid = TRUE;
+ }
+ }
+ break;
+ }
+ if (!$valid) {
+ CRM_Core_Error::fatal('Invalid currency selection: ' . $options['currencyID'] . ' for domain ' . $iats_domain);
+ }
+ }
+ }
+
+ /* check iATS website for additional supported currencies */
+
+ /**
+ * Submits an API request through the iATS SOAP API Toolkit.
+ *
+ * @param $credentials
+ * The request object or array containing the merchant credentials
+ *
+ * @param $request_params
+ * The request array containing the parameters of the requested services.
+ *
+ * @return
+ * The response object from the API with properties pertinent to the requested
+ * services.
+ */
+ public function request($credentials, $request_params) {
+ // Attempt the SOAP request and log the exception on failure.
+ $method = $this->method['method'];
+ if (empty($method)) {
+ CRM_Core_Error::fatal('No method for request.');
+ return FALSE;
+ }
+ // Do some massaging of parameters for badly behaving iATS methods ($method is now the iATS method, not our internal key)
+ switch ($method) {
+ case 'CreateACHEFTCustomerCode':
+ case 'CreateCreditCardCustomerCode':
+ case 'UpdateCreditCardCustomerCode':
+ if (empty($request_params['beginDate'])) {
+ $request_params['beginDate'] = date('c', time());
+ }
+ if (empty($request_params['endDate'])) {
+ $request_params['endDate'] = date('c', strtotime('+5 years'));
+ }
+ if (empty($request_params['recurring'])) {
+ $request_params['recurring'] = '0';
+ }
+ break;
+ case 'GetACHEFTApprovedDateRangeCSV':
+ case 'GetACHEFTRejectDateRangeCSV':
+ if (!isset($request_params['startIndex'])) {
+ $request_params['startIndex'] = '0';
+ }
+ if (!isset($request_params['endIndex'])) {
+ $request_params['endIndex'] = '199';
+ }
+ }
+ $message = $this->method['message'];
+ $response = $this->method['response'];
+ // Always log requests to my own table, start by making a copy of the original request
+ // note: this is different from the debug logging that only happens if debug is enabled.
+ if (!empty($request_params['invoiceNum'])) {
+ $logged_request = $request_params;
+ // Mask the cc numbers.
+ $this->mask($logged_request);
+ // log: ip, invoiceNum, , cc, total, date
+ // dpm($logged_request);
+ $cc = isset($logged_request['creditCardNum']) ? $logged_request['creditCardNum'] : (isset($logged_request['ccNum']) ? $logged_request['ccNum'] : '');
+ $ip = $logged_request['customerIPAddress'];
+ $query_params = array(
+ 1 => array($logged_request['invoiceNum'], 'String'),
+ 2 => array($ip, 'String'),
+ 3 => array(substr($cc, -4), 'String'),
+ 4 => array('', 'String'),
+ 5 => array($logged_request['total'], 'String'),
+ );
+ CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_request_log
+ (invoice_num, ip, cc, customer_code, total, request_datetime) VALUES (%1, %2, %3, %4, %5, NOW())", $query_params);
+ if (!$this->is_ipv4($ip)) {
+ $request_params['customerIPAddress'] = substr($ip, 0, 30);
+ }
+ // Save the invoiceNum so I can log it for the response.
+ $this->invoiceNum = $logged_request['invoiceNum'];
+ }
+ // The agent user and password only get put in here so they don't end up in a log above.
+ try {
+ $credentials = (array) $credentials;
+ $testAgentCode = ('TEST88' == $credentials['agentCode']) ? TRUE : FALSE;
+ /* until iATS fixes it's box verify, we need to have trace on to make the hack below work */
+ $soapClient = new SoapClient($this->_wsdl_url, array('trace' => 1, 'soap_version' => SOAP_1_2));
+ /* build the request manually as per the iATS docs */
+ $xml = '<' . $message . ' xmlns="' . $this->_wsdl_url_ns . '">';
+ $request = array_merge($this->request, $credentials, $request_params);
+ // Pass CiviCRM tag + version to iATS.
+ $request['comment'] = 'CiviCRM: ' . CRM_Utils_System::version() . ' + ' . 'iATS Extension: ' . $this->iats_extension_version();
+ $tags = (!empty($this->_tag_order)) ? $this->_tag_order : array_keys($request);
+ foreach ($tags as $k) {
+ if (isset($request[$k])) {
+ $xml .= '<' . $k . '>' . $this->xmlsafe($request[$k]) . '</' . $k . '>';
+ }
+ }
+ $xml .= '</' . $message . '>';
+ if ($testAgentCode && !empty($this->options['debug'])) {
+ CRM_Core_Error::debug_var('Method info', $method);
+ CRM_Core_Error::debug_var('XML', $xml);
+ }
+ $soapRequest = new SoapVar($xml, XSD_ANYXML);
+ if ($testAgentCode && !empty($this->options['debug'])) {
+ CRM_Core_Error::debug_var('SoapRequest', $soapRequest);
+ }
+ $soapResponse = $soapClient->$method($soapRequest);
+ if (!empty($this->options['debug']) && $testAgentCode) {
+ $request_log = "\n HEADER:\n";
+ $request_log .= $soapClient->__getLastRequestHeaders();
+ $request_log .= "\n BODY:\n";
+ $request_log .= $soapClient->__getLastRequest();
+ $request_log .= "\n BODYEND:\n";
+ CRM_Core_Error::debug_var('Request Log', $request_log);
+ $response_log = "\n HEADER:\n";
+ $response_log .= $soapClient->__getLastResponseHeaders();
+ $response_log .= "\n BODY:\n";
+ $response_log .= $soapClient->__getLastResponse();
+ $response_log .= "\n BODYEND:\n";
+ CRM_Core_Error::debug_var('Response Log', $response_log);
+ }
+ }
+ catch (SoapFault $exception) {
+ if (!empty($this->options['debug'])) {
+ CRM_Core_Error::debug_var('SoapFault Exception', $exception);
+ $response_log = "\n HEADER:\n";
+ $response_log .= $soapClient->__getLastResponseHeaders();
+ $response_log .= "\n BODY:\n";
+ $response_log .= $soapClient->__getLastResponse();
+ $response_log .= "\n BODYEND:\n";
+ CRM_Core_Error::debug_var('Raw Response', $response_log);
+ }
+ return FALSE;
+ }
+
+ // Log the response if specified.
+ if (!empty($this->options['debug'])) {
+ CRM_Core_Error::debug_var('iATS SOAP Response', $soapResponse);
+ }
+ if (isset($soapResponse->$response->any)) {
+ $xml_response = $soapResponse->$response->any;
+ return new SimpleXMLElement($xml_response);
+ }
+ // Deal with bad iats soap, this will only work if trace (debug) is on for now.
+ else {
+ $hack = new stdClass();
+ $hack->FILE = strip_tags($soapClient->__getLastResponse());
+ return $hack;
+ }
+ }
+
+ /**
+ *
+ */
+ public function file($response) {
+ return base64_decode($response->FILE);
+ }
+
+ /**
+ * Process the response to the request into a more friendly format in an array $result;
+ * Log the result to an internal table while I'm at it, unless explicitly not requested.
+ */
+ public function result($response, $log = TRUE) {
+ $result = array('auth_result' => '', 'remote_id' => '', 'status' => '');
+ switch ($this->type) {
+ case 'report':
+ case 'process':
+ if (!empty($response->PROCESSRESULT)) {
+ $processresult = $response->PROCESSRESULT;
+ $result['auth_result'] = trim(current($processresult->AUTHORIZATIONRESULT));
+ $result['remote_id'] = current($processresult->TRANSACTIONID);
+ // If we didn't get an approval response code...
+ // Note: do not use SUCCESS property, which just means iATS said "hello".
+ $result['status'] = (substr($result['auth_result'], 0, 2) == self::iATS_TXN_OK) ? 1 : 0;
+ }
+ // If the payment failed, display an error and rebuild the form.
+ if (empty($result['status'])) {
+ $result['reasonMessage'] = $result['auth_result'] ? $this->reasonMessage($result['auth_result']) : 'Unexpected Server Error, please see your logs';
+ }
+ break;
+
+ case 'customer':
+ if ($response->STATUS == 'Success') {
+ if (!empty($response->AUTHRESULT)) {
+ $result = get_object_vars($response->AUTHRESULT);
+ $result['status'] = (substr($result['AUTHSTATUS'], 0, 2) == self::iATS_TXN_OK) ? 1 : 0;
+ }
+ elseif (!empty($response->PROCESSRESULT)) {
+ $result = get_object_vars($response->PROCESSRESULT);
+ $result['status'] = (substr($result['AUTHORIZATIONRESULT'], 0, 2) == self::iATS_TXN_OK) ? 1 : 0;
+ }
+ elseif (!empty($response->CUSTOMERS->CST)) {
+ $customer = get_object_vars($response->CUSTOMERS->CST);
+ foreach ($customer as $key => $value) {
+ if (is_string($value)) {
+ $result[$key] = $value;
+ }
+ }
+ $result['ac1'] = $customer['AC1'];
+ $result['status'] = 1;
+ }
+ }
+ // If the payment failed, display an error and rebuild the form.
+ if (empty($result['status'])) {
+ $result['reasonMessage'] = isset($result['BANKERROR']) ? $result['BANKERROR'] :
+ (isset($result['AUTHORIZATIONRESULT']) ? $result['AUTHORIZATIONRESULT'] :
+ (isset($result['ERRORS']) ? $result['ERRORS'] :
+ 'Unexpected error'
+ )
+ );
+ }
+ break;
+ }
+ if ($log && !empty($this->invoiceNum) && ($this->type == 'process')) {
+ $query_params = array(
+ 1 => array($this->invoiceNum, 'String'),
+ 2 => array($result['auth_result'], 'String'),
+ 3 => array($result['remote_id'], 'String'),
+ );
+ CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_response_log
+ (invoice_num, auth_result, remote_id, response_datetime) VALUES (%1, %2, %3, NOW())", $query_params);
+ // #hack - this is necessary for 4.4 and possibly earlier versions of 4.6.x
+ // this ensures that trxn_id gets written to the contribution record - even if core did not do so.
+ if ($this->options['method'] == 'cc_with_customer_code') {
+ $api_params = array(
+ 'version' => 3,
+ 'sequential' => 1,
+ 'invoice_id' => $this->invoiceNum,
+ );
+ $contribution = civicrm_api('contribution', 'getsingle', $api_params);
+ if (!empty($contribution['id']) && empty($contribution['trxn_id'])) {
+ $api_params = array(
+ 'version' => 3,
+ 'sequential' => 1,
+ 'id' => $contribution['id'],
+ 'trxn_id' => trim($result['remote_id']) . ':' . time(),
+ );
+ civicrm_api('contribution', 'create', $api_params);
+ // watchdog('civicrm_iatspayments_com', 'rewrite: !request', array('!request' => '<pre>' . print_r($tmp, TRUE) . '</pre>', WATCHDOG_DEBUG));.
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Helper function to process csv files
+ * convert to an array of objects, each one corresponding to a transaction row.
+ */
+ public function getCSV($response, $method) {
+ $transactions = array();
+ $iats_domain = parse_url($this->_wsdl_url, PHP_URL_HOST);
+
+ switch ($iats_domain) {
+ case 'www.iatspayments.com':
+ $date_format = 'm/d/Y H:i:s';
+ $tz_string = 'America/Vancouver';
+ break;
+
+ case 'www.uk.iatspayments.com':
+ $date_format = 'd/m/Y H:i:s';
+ $tz_string = 'Europe/London';
+ break;
+
+ // Todo throw an exception instead? This should never happen!
+ default:
+ die('Invalid domain for date format');
+ }
+ $tz_object = new DateTimeZone($tz_string);
+ $gmt_datetime = new DateTime;
+ $gmt_offset = $tz_object->getOffset($gmt_datetime);
+ if (is_object($response)) {
+ $box = preg_split("/\r\n|\n|\r/", $this->file($response));
+ // watchdog('civicrm_iatspayments_com', 'csv: <pre>!data</pre>', array('!data' => print_r($box,TRUE)), WATCHDOG_NOTICE);.
+ if (1 < count($box)) {
+ // Data is an array of rows, the first of which is the column headers.
+ $headers = array_flip(array_map('trim',str_getcsv($box[0])));
+ for ($i = 1; $i < count($box); $i++) {
+ if (empty($box[$i])) {
+ continue;
+ }
+ $transaction = new stdClass();
+ // save the raw data in 'data'
+ $data = str_getcsv($box[$i]);
+ // and then store it as an associate array based on the headers
+ $record = array();
+ foreach($headers as $label => $column_i) {
+ $record[$label] = $data[$column_i];
+ }
+ // First get the data common to all methods.
+ $transaction->id = $record['Transaction ID'];
+ $transaction->customer_code = $record['Customer Code'];
+ // Save the entire record in case I need it
+ $transaction->data = $record;
+ // Now the method specific headers.
+ switch($method) {
+ // These are the same-day journals
+ case 'cc_journal_csv':
+ case 'acheft_journal_csv':
+ $datetime = $record['Date'];
+ $transaction->invoice = $record['Invoice'];
+ $transaction->amount = $record['Total'];
+ break;
+ // The box journals are the default.
+ default:
+ $transaction->amount = $record['Amount'];
+ $datetime = $record['Date Time'];
+ $transaction->invoice = $record['Invoice Number'];
+ // And now the uk dd specific hack, only for the box journals.
+ if ('www.uk.iatspayments.com' == $iats_domain) {
+ $transaction->achref = $record['ACH Ref.'];
+ }
+ break;
+ }
+ // Note that $gmt_offset and date_format depend on the server (iats_domain)
+ $rdp = date_parse_from_format($date_format, $datetime);
+ $transaction->receive_date = gmmktime($rdp['hour'], $rdp['minute'], $rdp['second'], $rdp['month'], $rdp['day'], $rdp['year']) - $gmt_offset;
+ // And now save it.
+ $transactions[$transaction->id] = $transaction;
+ }
+ }
+ }
+ return $transactions;
+ }
+
+ /**
+ * Provides the soap parameters for each of the ways to process payments at iATS Services
+ * Parameters are: method, message and response, these are all soap object properties
+ * Title and description provide a public information interface, not used internally.
+ */
+ public function methodInfo($type = '', $method = '') {
+ $desc = 'Integrates the iATS SOAP webservice: ';
+ switch ($type) {
+ default:
+ case 'process':
+ $methods = array(
+ 'cc' => array(
+ 'title' => 'Credit card',
+ 'description' => $desc . 'ProcessCreditCard',
+ 'method' => 'ProcessCreditCard',
+ 'message' => 'ProcessCreditCard',
+ 'response' => 'ProcessCreditCardResult',
+ ),
+ 'cc_create_customer_code' => array(
+ 'title' => 'Credit card, saved',
+ 'description' => $desc . 'CreateCustomerCodeAndProcessCreditCard',
+ 'method' => 'CreateCustomerCodeAndProcessCreditCard',
+ 'message' => 'CreateCustomerCodeAndProcessCreditCard',
+ 'response' => 'CreateCustomerCodeAndProcessCreditCardResult',
+ ),
+ 'cc_with_customer_code' => array(
+ 'title' => 'Credit card using saved info',
+ 'description' => $desc . 'ProcessCreditCardWithCustomerCode',
+ 'method' => 'ProcessCreditCardWithCustomerCode',
+ 'message' => 'ProcessCreditCardWithCustomerCode',
+ 'response' => 'ProcessCreditCardWithCustomerCodeResult',
+ ),
+ 'acheft' => array(
+ 'title' => 'ACH/EFT',
+ 'description' => $desc . 'ProcessACHEFT',
+ 'method' => 'ProcessACHEFT',
+ 'message' => 'ProcessACHEFT',
+ 'response' => 'ProcessACHEFTResult',
+ ),
+ 'acheft_create_customer_code' => array(
+ 'title' => 'ACH/EFT, saved',
+ 'description' => $desc . 'CreateCustomerCodeAndProcessACHEFT',
+ 'method' => 'CreateCustomerCodeAndProcessACHEFT',
+ 'message' => 'CreateCustomerCodeAndProcessACHEFT',
+ 'response' => 'CreateCustomerCodeAndProcessACHEFTResult',
+ ),
+ 'acheft_with_customer_code' => array(
+ 'title' => 'ACH/EFT with customer code',
+ 'description' => $desc . 'ProcessACHEFTWithCustomerCode',
+ 'method' => 'ProcessACHEFTWithCustomerCode',
+ 'message' => 'ProcessACHEFTWithCustomerCode',
+ 'response' => 'ProcessACHEFTWithCustomerCodeResult',
+ ),
+ );
+ break;
+ case 'report':
+ $methods = array(
+ // 'acheft_journal' => array(
+ // 'title' => 'ACH-EFT Journal',
+ // 'description'=> $desc. 'GetACHEFTJournal',
+ // 'method' => 'GetACHEFTJournal',
+ // 'message' => 'GetACHEFTJournal',
+ // 'response' => 'GetACHEFTJournalResult',
+ // ),.
+ 'cc_journal_csv' => array(
+ 'title' => 'Credit Card Journal CSV',
+ 'description' => $desc . 'GetCreditCardApprovedSpecificDateCSV',
+ 'method' => 'GetCreditCardApprovedSpecificDateCSV',
+ 'message' => 'GetCreditCardApprovedSpecificDateCSV',
+ 'response' => 'GetCreditCardApprovedSpecificDateCSVResult',
+ ),
+ 'cc_payment_box_journal_csv' => array(
+ 'title' => 'Credit Card Payment Box Journal CSV',
+ 'description'=> $desc. 'GetCreditCardApprovedDateRangeCSV',
+ 'method' => 'GetCreditCardApprovedDateRangeCSV',
+ 'message' => 'GetCreditCardApprovedDateRangeCSV',
+ 'response' => 'GetCreditCardApprovedDateRangeCSVResult',
+ ),
+ 'cc_payment_box_reject_csv' => array(
+ 'title' => 'Credit Card Payment Box Reject CSV',
+ 'description'=> $desc. 'GetCreditCardRejectDateRangeCSV',
+ 'method' => 'GetCreditCardRejectDateRangeCSV',
+ 'message' => 'GetCreditCardRejectDateRangeCSV',
+ 'response' => 'GetCreditCardRejectDateRangeCSVResult',
+ ),
+ 'acheft_journal_csv' => array(
+ 'title' => 'ACH-EFT Journal CSV',
+ 'description' => $desc . 'GetACHEFTApprovedSpecificDateCSV',
+ 'method' => 'GetACHEFTApprovedSpecificDateCSV',
+ 'message' => 'GetACHEFTApprovedSpecificDateCSV',
+ 'response' => 'GetACHEFTApprovedSpecificDateCSVResult',
+ ),
+ 'acheft_payment_box_journal_csv' => array(
+ 'title' => 'ACH-EFT Payment Box Journal CSV',
+ 'description' => $desc . 'GetACHEFTApprovedDateRangeCSV',
+ 'method' => 'GetACHEFTApprovedDateRangeCSV',
+ 'message' => 'GetACHEFTApprovedDateRangeCSV',
+ 'response' => 'GetACHEFTApprovedDateRangeCSVResult',
+ ),
+ 'acheft_payment_box_reject_csv' => array(
+ 'title' => 'ACH-EFT Payment Box Reject CSV',
+ 'description' => $desc . 'GetACHEFTRejectDateRangeCSV',
+ 'method' => 'GetACHEFTRejectDateRangeCSV',
+ 'message' => 'GetACHEFTRejectDateRangeCSV',
+ 'response' => 'GetACHEFTRejectDateRangeCSVResult',
+ ),
+ // 'acheft_reject' => array(
+ // 'title' => 'ACH-EFT Reject',
+ // 'description'=> $desc. 'GetACHEFTReject',
+ // 'method' => 'GetACHEFTReject',
+ // 'message' => 'GetACHEFTReject',
+ // 'response' => 'GetACHEFTRejectResult',
+ // ),.
+ 'acheft_reject_csv' => array(
+ 'title' => 'ACH-EFT Reject CSV',
+ 'description' => $desc . 'GetACHEFTRejectSpecificDateCSV',
+ 'method' => 'GetACHEFTRejectSpecificDateCSV',
+ 'message' => 'GetACHEFTRejectSpecificDateCSV',
+ 'response' => 'GetACHEFTRejectSpecificDateCSVResult',
+ ),
+ );
+ break;
+
+ case 'customer':
+ $methods = array(
+ 'get_customer_code_detail' => array(
+ 'title' => 'Get Customer Code Detail',
+ 'description' => $desc . 'GetCustomerCodeDetail',
+ 'method' => 'GetCustomerCodeDetail',
+ 'message' => 'GetCustomerCodeDetail',
+ 'response' => 'GetCustomerCodeDetailResult',
+ ),
+ 'create_credit_card_customer' => array(
+ 'title' => 'Create CustomerCode Credit Card',
+ 'description' => $desc . 'CreateCreditCardCustomerCode',
+ 'method' => 'CreateCreditCardCustomerCode',
+ 'message' => 'CreateCreditCardCustomerCode',
+ 'response' => 'CreateCreditCardCustomerCodeResult',
+ ),
+ 'update_credit_card_customer' => array(
+ 'title' => 'Update CustomerCode Credit Card',
+ 'description' => $desc . 'UpdateCreditCardCustomerCode',
+ 'method' => 'UpdateCreditCardCustomerCode',
+ 'message' => 'UpdateCreditCardCustomerCode',
+ 'response' => 'UpdateCreditCardCustomerCodeResult',
+ ),
+ 'direct_debit_acheft_payer_validate' => array(
+ 'title' => 'Direct Debit ACHEFT Payer Validate',
+ 'description' => $desc . 'DirectDebitACHEFTPayerValidate',
+ 'method' => 'DirectDebitACHEFTPayerValidate',
+ 'message' => 'DirectDebitACHEFTPayerValidate',
+ 'response' => 'DirectDebitACHEFTPayerValidateResult',
+ ),
+ 'create_acheft_customer_code' => array(
+ 'title' => 'Create ACHEFT Customer Code',
+ 'description' => $desc . 'CreateACHEFTCustomerCode',
+ 'method' => 'CreateACHEFTCustomerCode',
+ 'message' => 'CreateACHEFTCustomerCode',
+ 'response' => 'CreateACHEFTCustomerCodeResult',
+ ),
+ );
+ break;
+ }
+ if ($method) {
+ return $methods[$method];
+ }
+ return $methods;
+ }
+
+ /**
+ * Returns the message text for a credit card service reason code.
+ * As per iATS error codes - sent to us by Ryan Creamore
+ * TODO: multilingual options?
+ */
+ public function reasonMessage($code) {
+ switch ($code) {
+
+ case 'REJECT: 1':
+ return 'Agent code has not been set up on the authorization system. Please call iATS at 1-888-955-5455.';
+
+ case 'REJECT: 2':
+ return 'Unable to process transaction. Verify and reenter credit card information.';
+
+ case 'REJECT: 3':
+ return 'Invalid customer code.';
+
+ case 'REJECT: 4':
+ return 'Incorrect expiry date.';
+
+ case 'REJECT: 5':
+ return 'Invalid transaction. Verify and re-enter credit card information.';
+
+ case 'REJECT: 6':
+ return 'Please have cardholder call the number on the back of the card.';
+
+ case 'REJECT: 7':
+ return 'Lost or stolen card.';
+
+ case 'REJECT: 8':
+ return 'Invalid card status.';
+
+ case 'REJECT: 9':
+ return 'Restricted card status, usually on corporate cards restricted to specific sales.';
+
+ case 'REJECT: 10':
+ return 'Error. Please verify and re-enter credit card information.';
+
+ case 'REJECT: 11':
+ return 'General decline code. Please have cardholder call the number on the back of the card.';
+
+ case 'REJECT: 12':
+ return 'Incorrect CVV2 or expiry date.';
+
+ case 'REJECT: 14':
+ return 'The card is over the limit.';
+
+ case 'REJECT: 15':
+ // return 'General decline code. Please have cardholder call the number on the back of the card.';
+ return 'General decline code.';
+
+ case 'REJECT: 16':
+ return 'Invalid charge card number. Verify and re-enter credit card information.';
+
+ case 'REJECT: 17':
+ return 'Unable to authorize transaction. Authorizer needs more information for approval.';
+
+ case 'REJECT: 18':
+ return 'Card not supported by institution.';
+
+ case 'REJECT: 19':
+ return 'Incorrect CVV2 security code.';
+
+ case 'REJECT: 22':
+ return 'Bank timeout. Bank lines may be down or busy. Retry later.';
+
+ case 'REJECT: 23':
+ return 'System error. Retry transaction later.';
+
+ case 'REJECT: 24':
+ return 'Charge card expired.';
+
+ case 'REJECT: 25':
+ // return 'Capture card. Reported lost or stolen.';
+ return 'Possibly reported lost or stolen.';
+
+ case 'REJECT: 26':
+ return 'Invalid transaction, invalid expiry date. Please confirm and retry transaction.';
+
+ case 'REJECT: 27':
+ return 'Please have cardholder call the number on the back of the card.';
+
+ case 'REJECT: 32':
+ return 'Invalid charge card number.';
+
+ case 'REJECT: 39':
+ return 'Contact iATS at 1-888-955-5455.';
+
+ case 'REJECT: 40':
+ return 'Invalid card number. Card not supported by iATS.';
+
+ case 'REJECT: 41':
+ return 'Invalid expiry date.';
+
+ case 'REJECT: 42':
+ return 'CVV2 required.';
+
+ case 'REJECT: 43':
+ return 'Incorrect AVS.';
+
+ case 'REJECT: 45':
+ return 'Credit card name blocked. Call iATS at 1-888-955-5455.';
+
+ case 'REJECT: 46':
+ return 'Card tumbling. Call iATS at 1-888-955-5455.';
+
+ case 'REJECT: 47':
+ return 'Name tumbling. Call iATS at 1-888-955-5455.';
+
+ case 'REJECT: 48':
+ return 'IP blocked. Call iATS at 1-888-955-5455.';
+
+ case 'REJECT: 49':
+ return 'Velocity 1 – IP block. Call iATS at 1-888-955-5455.';
+
+ case 'REJECT: 50':
+ return 'Velocity 2 – IP block. Call iATS at 1-888-955-5455.';
+
+ case 'REJECT: 51':
+ return 'Velocity 3 – IP block. Call iATS at 1-888-955-5455.';
+
+ case 'REJECT: 52':
+ return 'Credit card BIN country blocked. Call iATS at 1-888-955-5455.';
+
+ case 'REJECT: 100':
+ return 'DO NOT REPROCESS. Call iATS at 1-888-955-5455.';
+
+ case 'Timeout':
+ return 'The system has not responded in the time allotted. Call iATS at 1-888-955-5455.';
+ }
+
+ return $code;
+ }
+
+ /**
+ * Returns the message text for a CVV match.
+ * This function not currently in use.
+ */
+ public function cvnResponse($code) {
+ switch ($code) {
+ case 'D':
+ return t('The transaction was determined to be suspicious by the issuing bank.');
+
+ case 'I':
+ return t("The CVN failed the processor's data validation check.");
+
+ case 'M':
+ return t('The CVN matched.');
+
+ case 'N':
+ return t('The CVN did not match.');
+
+ case 'P':
+ return t('The CVN was not processed by the processor for an unspecified reason.');
+
+ case 'S':
+ return t('The CVN is on the card but was not included in the request.');
+
+ case 'U':
+ return t('Card verification is not supported by the issuing bank.');
+
+ case 'X':
+ return t('Card verification is not supported by the card association.');
+
+ case '1':
+ return t('Card verification is not supported for this processor or card type.');
+
+ case '2':
+ return t('An unrecognized result code was returned by the processor for the card verification response.');
+
+ case '3':
+ return t('No result code was returned by the processor.');
+ }
+
+ return '-';
+ }
+
+ /**
+ *
+ */
+ public function creditCardTypes() {
+ return array(
+ 'VI' => t('Visa'),
+ 'MC' => t('MasterCard'),
+ 'AMX' => t('American Express'),
+ 'DSC' => t('Discover Card'),
+ );
+ }
+
+ /**
+ *
+ */
+ public function mask(&$log_request) {
+ // Mask the credit card number and CVV.
+ foreach (array('creditCardNum', 'cvv2', 'ccNum') as $mask) {
+ if (!empty($log_request[$mask])) {
+ // Show the last four digits of cc numbers.
+ if (4 < strlen($log_request[$mask])) {
+ $log_request[$mask] = str_repeat('X', strlen($log_request[$mask]) - 4) . substr($log_request[$mask], -4);
+ }
+ else {
+ $log_request[$mask] = str_repeat('X', strlen($log_request[$mask]));
+ }
+ }
+ }
+ }
+
+ /**
+ * When I'm using this object outside of the doDirect payment interface (e.g. a payment from the recurring job), I need to look up the credentials
+ * I need to pay attention to whether I should use the test credentials, which is the 'mode' in doDirect payment
+ * I also return the url_site value in case I need that.
+ */
+ public static function credentials($payment_processor_id, $is_test = 0) {
+ static $credentials = array();
+ if (empty($credentials[$payment_processor_id])) {
+ $select = 'SELECT user_name, password, url_site FROM civicrm_payment_processor WHERE id = %1 AND is_test = %2';
+ $args = array(
+ 1 => array($payment_processor_id, 'Int'),
+ 2 => array($is_test, 'Int'),
+ );
+ $dao = CRM_Core_DAO::executeQuery($select, $args);
+ if ($dao->fetch()) {
+ $cred = array(
+ 'agentCode' => $dao->user_name,
+ 'password' => $dao->password,
+ 'domain' => parse_url($dao->url_site, PHP_URL_HOST),
+ );
+ $credentials[$payment_processor_id] = $cred;
+ return $cred;
+ }
+ return;
+ }
+ return $credentials[$payment_processor_id];
+ }
+
+ /**
+ *
+ */
+ public static function is_ipv4($ip) {
+ // The regular expression checks for any number between 0 and 255 beginning with a dot (repeated 3 times)
+ // followed by another number between 0 and 255 at the end. The equivalent to an IPv4 address.
+ // It does not allow leading zeros [from http://runnable.com/UmrneujI6Q4_AAIW/how-to-validate-an-ipv4-address-using-regular-expressions-for-php-and-pcre]
+ return (bool) preg_match('/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])' .
+ '\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|[0-9])$/', $ip);
+ }
+
+ /**
+ *
+ */
+ public static function isDPM($pp) {
+ return self::iATS_USE_DPMPROCESS;
+ }
+
+ /**
+ *
+ */
+ public static function dpm_url($iats_domain) {
+ return 'https://' . $iats_domain . self::iATS_URL_DPMPROCESS;
+ }
+
+ /**
+ *
+ */
+ public static function iats_extension_version($reset = 0) {
+ $version = $reset ? '' : CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_extension_version');
+ if (empty($version)) {
+ $xmlfile = CRM_Core_Resources::singleton()->getPath('com.iatspayments.civicrm', 'info.xml');
+ $myxml = simplexml_load_file($xmlfile);
+ $version = (string) $myxml->version;
+ CRM_Core_BAO_Setting::setItem($version, 'iATS Payments Extension', 'iats_extension_version');
+ }
+ return $version;
+ }
+ /**
+ * function xmlsafe
+ *
+ * Replacement for using ENT_XML1 with htmlspecialchars for php5.3 compatibility.
+ */
+ private function xmlsafe($string) {
+ if (version_compare(PHP_VERSION, '5.4.0') < 0) {
+ $replace = array(
+ '"'=> "&quot;",
+ "&" => "&amp;",
+ "'"=> "&apos;",
+ "<" => "&lt;",
+ ">"=> "&gt;"
+ );
+ return strtr($string, $replace);
+ }
+ // else, better way for php5.4 and above
+ return htmlspecialchars($string, ENT_XML1, 'UTF-8');
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/README.md b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/README.md
new file mode 100644
index 00000000..865e2a6c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/README.md
@@ -0,0 +1,125 @@
+com.iatspayments.civicrm
+===============
+
+CiviCRM Extension for iATS Web Services Payment Processor - Date: Oct 17, 2017.
+Version 1.5.3 for 4.6.x and below.
+Version 1.6.1 for 4.7.x.
+
+This README.md contains information specific to system administrators/developers. Information for users/implementors can be found in the Documentation Wiki: https://github.com/iATSPayments/com.iatspayments.civicrm/wiki/Documentation
+
+Requirements
+------------
+
+1. CiviCRM 4.6.x or 4.7.x. We strongly recommend that you keep up with the most recent version of each branch.
+
+2. Your PHP needs to include the SOAP extension (php.net/manual/en/soap.setup.php), recommended that you use at least PHP 5.6 but 5.3 and above should work if it supports TLS1.1/1.2 and SHA-256.
+
+3. To use this extension in production, You must have an iATS Payments Account - and have configured it to accept payment though WebServices. You can use the shared iATS test account credentials for initial setup and testing. For details please see the Documentation Wiki: https://github.com/iATSPayments/com.iatspayments.civicrm/wiki/Documentation
+
+4. To handle ACH/EFT Contributions (verification of them) and to handle Recurring Contributions (of any type) you must configure cron for your CiviCRM install. Information about how to do this can be found in: http://wiki.civicrm.org/confluence/display/CRMDOC/Managing+Scheduled+Jobs
+
+
+Installation
+------------
+
+This extension follows the standard installation method - if you've got a supported CiviCRM version and you've set up your extensions directory, it'll appear in the Manage Extensions list as 'iATS Payments (com.iatspayments.civicrm)'. Hit Install.
+
+As of CiviCRM 5.x, the iATS extension is distributed with the CiviCRM download. This is generally the right version to install. See https://github.com/iATSPayments/com.iatspayments.civicrm/issues/242 for notes on converting from a previous manual install.
+
+If you need help with installing extensions, try: https://wiki.civicrm.org/confluence/display/CRMDOC/Extensions
+
+If you want to try out a particular version directly from github, you probably already know how to do that.
+
+Once the extension is installed, you need to add the payment processor(s) and input your iATS credentials:
+
+1. Administer -> System Settings -> Payment Processors -> + Add Payment Processor
+
+2. Select iATS Payments Credit Card, iATS Payments ACH/EFT, or iATS Payments SWIPE, they are all provided by this extension (the instructions differ only slightly for each one). You can create multiple payment processor entries using the same credentials for the different types.
+
+3. The "Name" of the payment processor is what your site visitors will see when they select a payment method, so typically use "Credit Card" here, or "Credit Card C$" (or US$) if there's any doubt about the currency. Your iATS Payments Account is configured for a single currency, so when you set up the payment page, you'll have to manually ensure you set the right currency (not an issue if you're only handling one currency).
+
+4. The test account uses Agent Code = TEST88 and Password = TEST88. This is a shared test account, so don't put in any private information.
+
+5. If you'd like to test using live workflows, you can just temporarily use the test account credentials in your live processor fields.
+
+6. Create a Contribution Page (or go to an existing one) -> Under Configure -> Contribution Amounts -> select your newly installed/configured Payment Processor(s), and Save.
+
+Extension Testing Notes
+-----------------------
+
+1. Our test matrix includes 21 type of transactions at the moment. View a summary of the results here: https://cloud.githubusercontent.com/assets/5340555/5616064/2459a9b8-94be-11e4-84c7-2ef0c83cc744.png
+
+2. Manage Contribution Pages -> Links -> Live Page.
+
+ * iATS Payments Credit Card: use test VISA: 4222222222222220 security code = 123 and any future Expiration date - to process any $amount.
+
+ * iATS Payments ACH/EFT: use 000000 for the Transit Number; 123 for the Bank Number; 123456 for the Bank Account Number $1
+
+ * iATS Payments SWIPE: not easy to test - even if you have an Encrypted USB Card Reader (sourced by iATS Payments) you will need a physical fake credit card with: 4222222222222220 security code = 123 and any future Expiration date in the magnetic strip - to process any $amount.
+
+ * iATS Payments UK Direct Debit: use 12345678 for Account Number; 000000 for Sort Code
+
+7. iATS has another test VISA: 41111111111111111 security code = 123 and any future Expiration date
+
+8. Reponses for a transaction with VISA: 41111111111111111 depend on the $amount processed - as follows
+ * 1.00 OK: 678594;
+ * 2.00 REJ: 15;
+ * 3.00 OK: 678594;
+ * 4.00 REJ: 15;
+ * 5.00 REJ: 15;
+ * 6.00 OK: 678594:X;
+ * 7.00 OK: 678594:y;
+ * 8.00 OK: 678594:A;
+ * 9.00 OK: 678594:Z;
+ * 10.00 OK: 678594:N;
+ * 15.00, if CVV2=1234 OK: 678594:Y; if there is no CVV2: REJ: 19
+ * 16.00 REJ: 2;
+ * Other Amount REJ: 15
+
+9. After completing a TEST payment -> check the Contributions -> Dashboard. Credit Card Transactions are authorized (=Completed) right away. ACH/EFT + UK direct Debit will be (=Pending).
+
+10. Visit https://home.iatspayments.com -> and click the Client Login button (top right)
+ * Login with TEST88 and TEST88
+ * hit Reports -> Journal - Credit Card Transactions -> Get Journal -> if it has been a busy day there will be lots of transactions here - so hit display all and scroll down to see the transaction you just processed via CiviCRM.
+ * hit Reports -> Journal - ACHEFT Transactions -> List Batches (the test transaction will be here until it is sent to the bank for processing - after that - and depending on the Result - it will appear in either the ACHEFT Approval or the ACHEFT Reject journal.
+
+11. For iATS Payments UK Direct Debit -> visit: https://www.uk.iatspayments.com
+ * Login with UDDD88 and DDTESTUK
+ * hit Virtual Terminal -> Customer Database -> Search by Name -> hit Edit icon (on the left) -> to see all details, including the Reference Number (which should match up with what you saw on your Thank you screen in CiviCRM).
+ * NOTE: Each charity needs to have a BACS accredited supplier vet their CiviCRM Direct Debit - Contribution Pages
+
+12. If things don't look right, you can turn on Drupal and CiviCRM logging - try another TEST transaction - and then see some detailed logging of the SOAP exchanges for more hints about where it might have gone wrong.
+
+13. To test recurring contributions - try creating a recurring contribution for every day and then go back the next day and manually trigger Scheduled Job: iATS Payments Recurring Contributions
+
+14. To test ACH/EFT contributions - manually run Scheduled Job: iATS Payments Verification - it will check with iATS to see if there is any word from the bank yet. How long it takes before a yeah or neah is available depends on the day of the week and the time the transaction is submitted. It can take overnight (over weekend) to get a verification.
+
+Once you're happy all is well - then all you need to do is update the Payment Processor data - with your own iATS' Agent Code and Password.
+
+Remember that iATS master accounts (ending in 01) can typically NOT be used to push monies into via web services. So when setting up your Account with iATS - ask them to create another (set of) Agent Codes for you: e.g. 80 or 90, etc.
+
+Also remember to turn off debugging/logging on any production environment!
+
+Issues
+------
+
+The best source for understanding current issues with the most recent release is the github issue queue:
+https://github.com/iATSPayments/com.iatspayments.civicrm/issues
+
+Some issues may be related to core CiviCRM issues, and may not have an immediate solution, but we'll endeavour to help you understand, work-around, and/or fix whatever concerns you raise on the issue queue.
+
+Below is a list of some of the most common issues:
+
+'Backend' ACH/EFT is not supported by CiviCRM core. Having an enabled ACH/EFT payment processor broke the backend live credit card payment page in core (until it was fixed here https://issues.civicrm.org/jira/browse/CRM-14442), so this module fixes that if it's an issue, and also provides links to easily allow administrators to input ACH/EFT on behalf of constituents. A similar problem existings for backend membership and event payments, and this has only been fixed in core for 4.6.
+
+9002 Error - if you get this when trying to make a contribution, then you're getting that error back from the iATS server due to an account misconfiguration. One source is due to some special characters in your passwd.
+
+CiviCRM core assigns Membership status (=new) and extends Membership End date as well as Event status (=registered) as soon as ACH/EFT is submitted (so while payment is still pending - this could be several days for ACH/EFT). If the contribution receives a Ok:BankAccept -> the extension will mark the contribution in CiviCRM as completed. If the contribution does NOT receive a Ok:BankAccept -> the extension will mark the contribution in CiviCRM as rejected - however - associated existing Membership and Event records may need to be updated manually.
+
+For 4.6, recurring ACH/EFT memberships contributions are automatically approved by CiviCRM due to a bug in CiviCRM Core, but which should be fixed in the near future.
+
+Please note that ACH Returns require manually processing. iATS Payments will notify an organization by Email in case such ACH Returns occur - the reason (e.g. NSF) is included. It is up to CiviCRM administrators to handle this in CiviCRM according to your organization's procedures (e.g. if these were monies re: Event registration -> should that registration be canceled as well or will you ask participant to bring cash; if NSF fees should be charged to the participant etc).
+
+Caution on the use of Pricesets in recurring contributions. This extension will try to use the original transactions' line items. But there are two separate issues here. First, CiviCRM API does an incomplete job with the bookkeeping of line items, so if you need detailed bookkeeping of line items in recurring contributions, you may be disappointed. Separately, if the total amount of the recurring contribution has changed, then there's no machine way of reliably re-allocating it into the original line items, so in that case, they are not used at all. Though not always ideal, a workaround might be to do different transactions for different types of CiviCRM payments instead.
+
+Please post an issue to the github repository if you have any questions.
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/GetJournal.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/GetJournal.php
new file mode 100644
index 00000000..e228c1f5
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/GetJournal.php
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * @file
+ */
+
+/**
+ * Action get journal.
+ *
+ * @param array $params
+ *
+ * Get entries from iATSPayments in the journal table
+ */
+function _civicrm_api3_iats_payments_get_journal_spec(&$params) {
+ $params['tnid'] = array(
+ 'name' => 'tnid',
+ 'title' => 'Transaction string',
+ 'api.required' => 0,
+ );
+ $params['iats_id'] = array(
+ 'name' => 'iats_id',
+ 'title' => 'IatsPayments Journal Id',
+ 'api.required' => 0,
+ );
+ $params['tntyp'] = array(
+ 'name' => 'tntyp',
+ 'title' => 'Transaction type',
+ 'api.required' => 0,
+ );
+ $params['inv'] = array(
+ 'name' => 'inv',
+ 'title' => 'Invoice Reference',
+ 'api.required' => 0,
+ );
+}
+
+/**
+ * Action IatsPayments GetJournal
+ *
+ * @param array $params
+ *
+ * @return array
+ * API result array.
+ *
+ * @throws CiviCRM_API3_Exception
+ */
+
+/**
+ *
+ */
+function civicrm_api3_iats_payments_get_journal($params) {
+
+ // print_r($params); die();
+ $select = "SELECT * FROM civicrm_iats_journal WHERE TRUE ";
+ $args = array();
+
+ $select_params = array(
+ 'tnid' => 'String',
+ 'tn_type' => 'Integer',
+ 'iats_id' => 'Integer',
+ 'inv' => 'String',
+ );
+ $i = 0;
+ foreach ($params as $key => $value) {
+ if (isset($select_params[$key])) {
+ $i++;
+ if (is_string($value)) {
+ $select .= " AND $key = %$i";
+ $args[$i] = array($value, $select_params[$key]);
+ }
+ elseif (is_array($value)) {
+ foreach (array_keys($value) as $sql) {
+ $select .= " AND ($key %$i)";
+ $args[$i] = array($sql, 'String');
+ }
+ }
+ }
+ }
+ $limit = 25;
+ if (isset($params['options']['limit'])) {
+ $limit = (integer) $params['options']['limit'];
+ }
+ if ($limit > 0) {
+ $i++;
+ $select .= " LIMIT %$i";
+ $args[$i] = array($limit, 'Integer');
+ }
+ if (isset($params['options']['sort'])) {
+ $sort = $params['options']['sort'];
+ $i++;
+ $select .= " ORDER BY %$i";
+ $args[$i] = array($sort, 'String');
+ }
+
+ $values = array();
+ try {
+ $dao = CRM_Core_DAO::executeQuery($select, $args);
+ while ($dao->fetch()) {
+ /* We index in the transaction_id */
+ $record = array();
+ foreach (get_object_vars($dao) as $key => $value) {
+ if ('N' != $key && (0 !== strpos($key, '_'))) {
+ $record[$key] = $value;
+ }
+ }
+ $key = $dao->tnid;
+ $values[$key] = $record;
+ }
+ }
+ catch (Exception $e) {
+ CRM_Core_Error::debug_var('params', $params);
+ // throw API_Exception('iATS Payments journalling failed: '. $e->getMessage());
+ }
+ return civicrm_api3_create_success($values);
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/Journal.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/Journal.php
new file mode 100644
index 00000000..a872753c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/Journal.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ */
+
+/**
+ * Action journal.
+ *
+ * @param array $params
+ *
+ * Record an entry from iATSPayments into the journal table
+ */
+function _civicrm_api3_iats_payments_journal_spec(&$params) {
+ $params['transaction_id']['api.required'] = 1;
+}
+
+/**
+ * Action IatsPayments VerifyLog.
+ *
+ * @param array $params
+ *
+ * @return array
+ * API result array.
+ *
+ * @throws CiviCRM_API3_Exception
+ */
+
+/**
+ *
+ */
+function civicrm_api3_iats_payments_journal($params) {
+ //CRM_Core_Error::debug_var('params', $params);
+ //return civicrm_api3_create_success(TRUE, array('test' => TRUE));
+ try {
+ $data = $params['data'];
+ $dtm = date('YmdHis', $params['receive_date']);
+ $defaults = array(
+ 'Client Code' => '',
+ 'Method of Payment' => '',
+ 'Comment' => '',
+ );
+ foreach ($defaults as $key => $default) {
+ $data[$key] = empty($data[$key]) ? $default : $data[$key];
+ }
+ // There are unique keys on tnid (transaction) and iats_id (journal)
+ // If I don't have a journal id, don't overwrite.
+ if (empty($data['Journal Id'])) {
+ $iats_journal_id = 'NULL';
+ $sql_action = 'INSERT IGNORE ';
+ }
+ else {
+ $iats_journal_id = (int) $data['Journal Id'];
+ $sql_action = 'REPLACE INTO ';
+ }
+ $query_params = array(
+ 1 => array($data['Transaction ID'], 'String'),
+ 3 => array($dtm, 'String'),
+ 4 => array($data['Client Code'], 'String'),
+ 5 => array($params['customer_code'], 'String'),
+ 6 => array($params['invoice'], 'String'),
+ 7 => array($params['amount'], 'String'),
+ 8 => array($data['Result'], 'String'),
+ 9 => array($data['Method of Payment'], 'String'),
+ 10 => array($data['Comment'], 'String'),
+ 11 => array($params['status_id'], 'Integer'),
+ );
+ $result = CRM_Core_DAO::executeQuery($sql_action . " civicrm_iats_journal
+ (tnid, iats_id, dtm, agt, cstc, inv, amt, rst, tntyp, cm, status_id) VALUES (%1, $iats_journal_id, %3, %4, %5, %6, %7, %8, %9, %10, %11)", $query_params);
+ }
+ catch (Exception $e) {
+ CRM_Core_Error::debug_var('params', $params);
+ // throw CiviCRM_API3_Exception('iATS Payments journalling failed: ' . $e->getMessage());
+ }
+ return civicrm_api3_create_success();
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/Verifylog.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/Verifylog.php
new file mode 100644
index 00000000..0b83d7a9
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/IatsPayments/Verifylog.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ */
+
+/**
+ * Action IatsPayments VerifyLog.
+ *
+ * @param array $params
+ *
+ * @return array
+ * API result array.
+ *
+ * @throws CiviCRM_API3_Exception
+ */
+
+/**
+ *
+ */
+function civicrm_api3_iats_payments_verifylog($params) {
+ try {
+ $customer_code = empty($params['customer_code']) ? '' : $params['customer_code'];
+ if (!empty($params['contribution'])) {
+ $contribution = $params['contribution'];
+ $query_params = array(
+ 1 => array($customer_code, 'String'),
+ 2 => array($contribution['contact_id'], 'Integer'),
+ 3 => array($contribution['id'], 'Integer'),
+ 4 => array($params['contribution_status_id'], 'Integer'),
+ 5 => array($params['transaction_id'], 'String'),
+ 6 => array($contribution['contribution_recur_id'], 'Integer'),
+ );
+ if (empty($contribution['contribution_recur_id'])) {
+ unset($query_params[6]);
+ $result = CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_verify
+ (customer_code, cid, contribution_id, contribution_status_id, auth_result, verify_datetime) VALUES (%1, %2, %3, %4, %5, NOW())", $query_params);
+ }
+ else {
+ $result = CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_verify
+ (customer_code, cid, contribution_id, contribution_status_id, auth_result, verify_datetime, recur_id) VALUES (%1, %2, %3, %4, %5, NOW(), %5)", $query_params);
+ }
+ }
+ else {
+ $query_params = array(
+ 1 => array($customer_code, 'String'),
+ 2 => array($params['transaction_id'], 'String'),
+ );
+ $result = CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_verify
+ (customer_code, auth_result, verify_datetime) VALUES (%1, %2, NOW())", $query_params);
+ }
+ }
+ catch (Exception $e) {
+ throw API_Exception('iATS Payments verification logging failed.');
+ }
+ return civicrm_api3_create_success(TRUE, $params);
+}
+
+/**
+ * Action payment.
+ *
+ * @param array $params
+ *
+ * @return array
+ */
+function _civicrm_api3_iats_payments_verifylog_spec(&$params) {
+ $params['transaction_id']['api.required'] = 1;
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.mgd.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.mgd.php
new file mode 100644
index 00000000..489e1d9d
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.mgd.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * This file declares a managed database record of type "Job".
+ */
+
+// The record will be automatically inserted, updated, or deleted from the
+// database as appropriate. For more details, see "hook_civicrm_managed" at:
+// http://wiki.civicrm.org/confluence/display/CRMDOC42/Hook+Reference
+return array(
+ 0 =>
+ array(
+ 'name' => 'Cron:Job.Iatsrecurringcontributions',
+ 'entity' => 'Job',
+ 'params' =>
+ array(
+ 'version' => 3,
+ 'name' => 'iATS Payments Recurring Contributions',
+ 'description' => 'Trigger + Generate recurring contributions for iATS Payments',
+ 'run_frequency' => 'Daily',
+ 'api_entity' => 'Job',
+ 'api_action' => 'iatsrecurringcontributions',
+ 'parameters' => '',
+ ),
+ 'update' => 'never',
+ ),
+);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php
new file mode 100644
index 00000000..21510bdb
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php
@@ -0,0 +1,467 @@
+<?php
+
+/**
+ * @file
+ */
+
+/**
+ * Job.iATSRecurringContributions API specification.
+ *
+ * @param array $spec description of fields supported by this API call
+ *
+ * @return void
+ *
+ * @see http://wiki.civicrm.org/confluence/display/CRM/API+Architecture+Standards
+ */
+
+/**
+ *
+ */
+function _civicrm_api3_job_iatsrecurringcontributions_spec(&$spec) {
+ $spec['recur_id'] = array(
+ 'name' => 'recur_id',
+ 'title' => 'Recurring payment id',
+ 'api.required' => 0,
+ 'type' => 1,
+ );
+ $spec['cycle_day'] = array(
+ 'name' => 'cycle_day',
+ 'title' => 'Only contributions that match a specific cycle day.',
+ 'api.required' => 0,
+ 'type' => 1,
+ );
+ $spec['failure_count'] = array(
+ 'name' => 'failure_count',
+ 'title' => 'Filter by number of failure counts',
+ 'api.required' => 0,
+ 'type' => 1,
+ );
+ $spec['catchup'] = array(
+ 'title' => 'Process as if in the past to catch up.',
+ 'api.required' => 0,
+ );
+ $spec['ignoremembership'] = array(
+ 'title' => 'Ignore memberships',
+ 'api.required' => 0,
+ );
+}
+
+/**
+ * Job.iATSRecurringContributions API.
+ *
+ * @param array $params
+ *
+ * @return array API result descriptor
+ *
+ * @see civicrm_api3_create_success
+ * @see civicrm_api3_create_error
+ *
+ * @throws API_Exception
+ */
+function civicrm_api3_job_iatsrecurringcontributions($params) {
+ // Running this job in parallell could generate bad duplicate contributions.
+ $lock = new CRM_Core_Lock('civicrm.job.IatsRecurringContributions');
+
+ if (!$lock->acquire()) {
+ return civicrm_api3_create_success(ts('Failed to acquire lock. No contribution records were processed.'));
+ }
+ $catchup = !empty($params['catchup']);
+ unset($params['catchup']);
+ $domemberships = empty($params['ignoremembership']);
+ unset($params['ignoremembership']);
+
+ // TODO: what kind of extra security do we want or need here to prevent it from being triggered inappropriately? Or does it matter?
+ // The next scheduled contribution date field name is civicrm version dependent.
+ define('IATS_CIVICRM_NSCD_FID', _iats_civicrm_nscd_fid());
+ // $config = &CRM_Core_Config::singleton();
+ // $debug = false;
+ // do my calculations based on yyyymmddhhmmss representation of the time
+ // not sure about time-zone issues.
+ $dtCurrentDay = date("Ymd", mktime(0, 0, 0, date("m"), date("d"), date("Y")));
+ $dtCurrentDayStart = $dtCurrentDay . "000000";
+ $dtCurrentDayEnd = $dtCurrentDay . "235959";
+ $expiry_limit = date('ym');
+ // Restrict this method of recurring contribution processing to only these three payment processors.
+ $args = array(
+ 1 => array('Payment_iATSService', 'String'),
+ 2 => array('Payment_iATSServiceACHEFT', 'String'),
+ 3 => array('Payment_iATSServiceSWIPE', 'String'),
+ );
+ // Before triggering payments, we need to do some housekeeping of the civicrm_contribution_recur records.
+ // First update the end_date and then the complete/in-progress values.
+ // We do this both to fix any failed settings previously, and also
+ // to deal with the possibility that the settings for the number of payments (installments) for an existing record has changed.
+ // First check for recur end date values on non-open-ended recurring contribution records that are either complete or in-progress.
+ $select = 'SELECT cr.id, count(c.id) AS installments_done, cr.installments, cr.end_date, NOW() as test_now
+ FROM civicrm_contribution_recur cr
+ INNER JOIN civicrm_contribution c ON cr.id = c.contribution_recur_id
+ INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
+ WHERE
+ (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
+ AND (cr.installments > 0)
+ AND (cr.contribution_status_id IN (1,5))
+ AND (c.contribution_status_id IN (1,2))
+ GROUP BY c.contribution_recur_id';
+ $dao = CRM_Core_DAO::executeQuery($select, $args);
+ while ($dao->fetch()) {
+ // Check for end dates that should be unset because I haven't finished
+ // at least one more installment todo.
+ if ($dao->installments_done < $dao->installments) {
+ // Unset the end_date.
+ if (($dao->end_date > 0) && ($dao->end_date <= $dao->test_now)) {
+ $update = 'UPDATE civicrm_contribution_recur SET end_date = NULL, contribution_status_id = 5 WHERE id = %1';
+ CRM_Core_DAO::executeQuery($update, array(1 => array($dao->id, 'Int')));
+ }
+ }
+ // otherwise, check if my end date should be set to the past because I have finished
+ // I'm done with installments.
+ elseif ($dao->installments_done >= $dao->installments) {
+ if (empty($dao->end_date) || ($dao->end_date >= $dao->test_now)) {
+ // This interval complete, set the end_date to an hour ago.
+ $update = 'UPDATE civicrm_contribution_recur SET end_date = DATE_SUB(NOW(),INTERVAL 1 HOUR) WHERE id = %1';
+ CRM_Core_DAO::executeQuery($update, array(1 => array($dao->id, 'Int')));
+ }
+ }
+ }
+ // Second, make sure any open-ended recurring contributions have no end date set.
+ $update = 'UPDATE civicrm_contribution_recur cr
+ INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
+ SET
+ cr.end_date = NULL
+ WHERE
+ cr.contribution_status_id IN (1,5)
+ AND NOT(cr.installments > 0)
+ AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
+ AND NOT(ISNULL(cr.end_date))';
+ $dao = CRM_Core_DAO::executeQuery($update, $args);
+
+ // Third, we update the status_id of the all in-progress or completed recurring contribution records
+ // Unexpire uncompleted cycles.
+ $update = 'UPDATE civicrm_contribution_recur cr
+ INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
+ SET
+ cr.contribution_status_id = 5
+ WHERE
+ cr.contribution_status_id = 1
+ AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
+ AND (cr.end_date IS NULL OR cr.end_date > NOW())';
+ $dao = CRM_Core_DAO::executeQuery($update, $args);
+ // Expire or badly-defined completed cycles.
+ $update = 'UPDATE civicrm_contribution_recur cr
+ INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
+ SET
+ cr.contribution_status_id = 1
+ WHERE
+ cr.contribution_status_id = 5
+ AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
+ AND (
+ (NOT(cr.end_date IS NULL) AND cr.end_date <= NOW())
+ OR
+ ISNULL(cr.frequency_unit)
+ OR
+ (frequency_interval = 0)
+ )';
+ $dao = CRM_Core_DAO::executeQuery($update, $args);
+
+ // Now we're ready to trigger payments
+ // Select the ongoing recurring payments for iATSServices where the next scheduled contribution date (NSCD) is before the end of of the current day.
+ $select = 'SELECT cr.*, icc.customer_code, icc.expiry as icc_expiry, icc.cid as icc_contact_id, pp.class_name as pp_class_name, pp.url_site as url_site, pp.is_test
+ FROM civicrm_contribution_recur cr
+ INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
+ INNER JOIN civicrm_iats_customer_codes icc ON cr.id = icc.recur_id
+ WHERE
+ cr.contribution_status_id = 5
+ AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)';
+ // AND pp.is_test = 0
+ // in case the job was called to execute a specific recurring contribution id -- not yet implemented!
+ if (!empty($params['recur_id'])) {
+ $select .= ' AND icc.recur_id = %4';
+ $args[4] = array($params['recur_id'], 'Int');
+ }
+ // If (!empty($params['scheduled'])) {.
+ else {
+ // normally, process all recurring contributions due today or earlier.
+ $select .= ' AND cr.' . IATS_CIVICRM_NSCD_FID . ' <= %4';
+ $args[4] = array($dtCurrentDayEnd, 'String');
+ // ' AND cr.next_sched_contribution >= %2
+ // $args[2] = array($dtCurrentDayStart, 'String');
+ // also filter by cycle day.
+ if (!empty($params['cycle_day'])) {
+ $select .= ' AND cr.cycle_day = %5';
+ $args[5] = array($params['cycle_day'], 'Int');
+ }
+ // Also filter by cycle day.
+ if (isset($params['failure_count'])) {
+ $select .= ' AND cr.failure_count = %6';
+ $args[6] = array($params['failure_count'], 'Int');
+ }
+ }
+ $dao = CRM_Core_DAO::executeQuery($select, $args);
+ $counter = 0;
+ $error_count = 0;
+ $output = array();
+ $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
+ $receipt_recurring = $settings['receipt_recurring'];
+ $email_failure_report = empty($settings['email_recurring_failure_report']) ? '' : $settings['email_recurring_failure_report'];
+ // By default, after 3 failures move the next scheduled contribution date forward.
+ $failure_threshhold = empty($settings['recurring_failure_threshhold']) ? 3 : (int) $settings['recurring_failure_threshhold'];
+
+ /* while ($dao->fetch()) {
+ foreach($dao as $key => $value) {
+ echo "$value,";
+ }
+ echo "\n";
+ }
+ die(); */
+ $failure_report_text = '';
+ while ($dao->fetch()) {
+
+ // Strategy: create the contribution record with status = 2 (= pending), try the payment, and update the status to 1 if successful
+ // Try to get a contribution template for this contribution series - if none matches (e.g. if a donation amount has been changed), we'll just be naive about it.
+ $contribution_template = _iats_civicrm_getContributionTemplate(array('contribution_recur_id' => $dao->id, 'total_amount' => $dao->amount));
+ $contact_id = $dao->contact_id;
+ $total_amount = $dao->amount;
+ $hash = md5(uniqid(rand(), TRUE));
+ $contribution_recur_id = $dao->id;
+ $original_contribution_id = $contribution_template['original_contribution_id'];
+ $failure_count = $dao->failure_count;
+ $subtype = substr($dao->pp_class_name, 19);
+ $source = "iATS Payments $subtype Recurring Contribution (id=$contribution_recur_id)";
+ $receive_ts = $catchup ? strtotime($dao->next_sched_contribution_date) : time();
+ // i.e. now or whenever it was supposed to run if in catchup mode.
+ $receive_date = date("YmdHis", $receive_ts);
+ // Check if we already have an error.
+ $errors = array();
+ if (empty($dao->customer_code)) {
+ $errors[] = ts('Recur id %1 is missing a customer code.', array(1 => $contribution_recur_id));
+ }
+ else {
+ if ($dao->contact_id != $dao->icc_contact_id) {
+ $errors[] = ts('Recur id %1 is has a mismatched contact id for the customer code.', array(1 => $contribution_recur_id));
+ }
+ if (($dao->icc_expiry != '0000') && ($dao->icc_expiry < $expiry_limit)) {
+ // $errors[] = ts('Recur id %1 is has an expired cc for the customer code.', array(1 => $contribution_recur_id));.
+ }
+ }
+ if (count($errors)) {
+ $source .= ' Errors: ' . implode(' ', $errors);
+ }
+ $contribution = array(
+ 'version' => 3,
+ 'contact_id' => $contact_id,
+ 'receive_date' => $receive_date,
+ 'total_amount' => $total_amount,
+ 'payment_instrument_id' => $dao->payment_instrument_id,
+ 'contribution_recur_id' => $contribution_recur_id,
+ 'invoice_id' => $hash,
+ 'source' => $source,
+ 'contribution_status_id' => 2, /* initialize as pending, so we can run completetransaction after taking the money */
+ 'currency' => $dao->currency,
+ 'payment_processor' => $dao->payment_processor_id,
+ 'is_test' => $dao->is_test, /* propagate the is_test value from the parent contribution */
+ );
+ $get_from_template = array('contribution_campaign_id', 'amount_level');
+ foreach ($get_from_template as $field) {
+ if (isset($contribution_template[$field])) {
+ $contribution[$field] = is_array($contribution_template[$field]) ? implode(', ', $contribution_template[$field]) : $contribution_template[$field];
+ }
+ }
+ // 4.2.
+ if (isset($dao->contribution_type_id)) {
+ $contribution['contribution_type_id'] = $dao->contribution_type_id;
+ }
+ // 4.3+.
+ else {
+ $contribution['financial_type_id'] = $dao->financial_type_id;
+ }
+ // if we have a created a pending contribution record due to a future start time, then recycle that CiviCRM contribution record now.
+ // Note that the date and amount both could have changed.
+ // The key is to only match if we find a single pending contribution, with a NULL transaction id, for this recurring schedule.
+ // We'll need to pay attention later that we may or may not already have a contribution id.
+ try {
+ $pending_contribution = civicrm_api3('Contribution', 'get', array(
+ 'return' => array('id'),
+ 'trxn_id' => array('IS NULL' => 1),
+ 'contribution_recur_id' => $contribution_recur_id,
+ 'contribution_status_id' => "Pending",
+ ));
+ if (!empty($pending_contribution['id'])) {
+ $contribution['id'] = $pending_contribution['id'];
+ }
+ }
+ catch (Exception $e) {
+ // ignore, we'll proceed normally without a contribution id
+ }
+ // If I'm not recycling a contribution record and my original has line_items, then I'll add them to the contribution creation array.
+ // TODO: if the amount of a matched pending contribution has changed, then we should be removing line items from the contribution and replacing them.
+ if (empty($contribution['id']) && !empty($contribution_template['line_items'])) {
+ $contribution['skipLineItem'] = 1;
+ $contribution['api.line_item.create'] = $contribution_template['line_items'];
+ }
+ if (count($errors)) {
+ ++$error_count;
+ ++$counter;
+ /* create a failed contribution record, don't bother talking to iats */
+ $contribution['contribution_status_id'] = 4;
+ $contributionResult = civicrm_api('contribution', 'create', $contribution);
+ if ($contributionResult['is_error']) {
+ $errors[] = $contributionResult['error_message'];
+ }
+ if ($email_failure_report) {
+ $failure_report_text .= "\n Unexpected Errors: " . implode(' ', $errors);
+ }
+ continue;
+ }
+ else {
+ // Assign basic options.
+ $options = array(
+ 'is_email_receipt' => (($receipt_recurring < 2) ? $receipt_recurring : $dao->is_email_receipt),
+ 'customer_code' => $dao->customer_code,
+ 'subtype' => $subtype,
+ );
+ // If our template contribution is a membership payment, make this one also.
+ if ($domemberships && !empty($contribution_template['contribution_id'])) {
+ try {
+ $membership_payment = civicrm_api('MembershipPayment', 'getsingle', array('version' => 3, 'contribution_id' => $contribution_template['contribution_id']));
+ if (!empty($membership_payment['membership_id'])) {
+ $options['membership_id'] = $membership_payment['membership_id'];
+ }
+ }
+ catch (Exception $e) {
+ // ignore, if will fail correctly if there is no membership payment.
+ }
+ }
+ // So far so, good ... now create the pending contribution, and save its id
+ // and then try to get the money, and do one of:
+ // update the contribution to failed, leave as pending for server failure, complete the transaction,
+ // or update a pending ach/eft with it's transaction id.
+ $result = _iats_process_contribution_payment($contribution, $options, $original_contribution_id);
+ if ($email_failure_report && !empty($contribution['iats_reject_code'])) {
+ $failure_report_text .= "\n $result ";
+ }
+ $output[] = $result;
+ }
+
+ /* in case of critical failure set the series to pending */
+ if (!empty($contribution['iats_reject_code'])) {
+ switch ($contribution['iats_reject_code']) {
+ // Reported lost or stolen.
+ case 'REJECT: 25':
+ // Do not reprocess!
+ case 'REJECT: 100':
+ /* convert the contribution series to pending to avoid reprocessing until dealt with */
+ civicrm_api('ContributionRecur', 'create',
+ array(
+ 'version' => 3,
+ 'id' => $contribution['contribution_recur_id'],
+ 'contribution_status_id' => 2,
+ )
+ );
+ break;
+ }
+ }
+
+ /* calculate the next collection date, based on the recieve date (note effect of catchup mode, above) */
+ $next_collection_date = date('Y-m-d H:i:s', strtotime("+$dao->frequency_interval $dao->frequency_unit", $receive_ts));
+ /* by default, advance to the next schduled date and set the failure count back to 0 */
+ $contribution_recur_set = array('version' => 3, 'id' => $contribution['contribution_recur_id'], 'failure_count' => '0', 'next_sched_contribution_date' => $next_collection_date);
+ /* special handling for failures */
+ if (4 == $contribution['contribution_status_id']) {
+ $contribution_recur_set['failure_count'] = $failure_count + 1;
+ /* if it has failed but the failure threshold will not be reached with this failure, leave the next sched contribution date as it was */
+ if ($contribution_recur_set['failure_count'] < $failure_threshhold) {
+ // Should the failure count be reset otherwise? It is not.
+ unset($contribution_recur_set['next_sched_contribution_date']);
+ }
+ }
+ civicrm_api('ContributionRecur', 'create', $contribution_recur_set);
+ $result = civicrm_api('activity', 'create',
+ array(
+ 'version' => 3,
+ 'activity_type_id' => 6,
+ 'source_contact_id' => $contact_id,
+ 'source_record_id' => $contribution['id'],
+ 'assignee_contact_id' => $contact_id,
+ 'subject' => "Attempted iATS Payments $subtype Recurring Contribution for " . $total_amount,
+ 'status_id' => 2,
+ 'activity_date_time' => date("YmdHis"),
+ )
+ );
+ if ($result['is_error']) {
+ $output[] = ts(
+ 'An error occurred while creating activity record for contact id %1: %2',
+ array(
+ 1 => $contact_id,
+ 2 => $result['error_message'],
+ )
+ );
+ ++$error_count;
+ }
+ else {
+ $output[] = ts('Created activity record for contact id %1', array(1 => $contact_id));
+ }
+ ++$counter;
+ }
+
+ // Now update the end_dates and status for non-open-ended contribution series if they are complete (so that the recurring contribution status will show correctly)
+ // This is a simplified version of what we did before the processing.
+ $select = 'SELECT cr.id, count(c.id) AS installments_done, cr.installments
+ FROM civicrm_contribution_recur cr
+ INNER JOIN civicrm_contribution c ON cr.id = c.contribution_recur_id
+ INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
+ WHERE
+ (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
+ AND (cr.installments > 0)
+ AND (cr.contribution_status_id = 5)
+ AND (c.contribution_status_id IN (1,2))
+ GROUP BY c.contribution_recur_id';
+ $dao = CRM_Core_DAO::executeQuery($select, $args);
+ while ($dao->fetch()) {
+ // Check if my end date should be set to now because I have finished
+ // I'm done with installments.
+ if ($dao->installments_done >= $dao->installments) {
+ // Set this series complete and the end_date to now.
+ $update = 'UPDATE civicrm_contribution_recur SET contribution_status_id = 1, end_date = NOW() WHERE id = %1';
+ CRM_Core_DAO::executeQuery($update, array(1 => array($dao->id, 'Int')));
+ }
+ }
+
+ $lock->release();
+ // If errors ..
+ if ((strlen($failure_report_text) > 0) && $email_failure_report) {
+ list($fromName, $fromEmail) = CRM_Core_BAO_Domain::getNameAndEmail();
+ $mailparams = array(
+ 'from' => $fromName . ' <' . $fromEmail . '> ',
+ 'to' => 'System Administrator <' . $email_failure_report . '>',
+ 'subject' => ts('iATS Recurring Payment job failure report: ' . date('c')),
+ 'text' => $failure_report_text,
+ 'returnPath' => $fromEmail,
+ );
+ // print_r($mailparams);
+ CRM_Utils_Mail::send($mailparams);
+ }
+ // If errors ..
+ if ($error_count > 0) {
+ return civicrm_api3_create_error(
+ ts("Completed, but with %1 errors. %2 records processed.",
+ array(
+ 1 => $error_count,
+ 2 => $counter,
+ )
+ ) . "<br />" . implode("<br />", $output)
+ );
+ }
+ // If no errors and records processed ..
+ if ($counter) {
+ return civicrm_api3_create_success(
+ ts(
+ '%1 contribution record(s) were processed.',
+ array(
+ 1 => $counter,
+ )
+ ) . "<br />" . implode("<br />", $output)
+ );
+ }
+ // No records processed.
+ return civicrm_api3_create_success(ts('No contribution records were processed.'));
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.mgd.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.mgd.php
new file mode 100644
index 00000000..21df020a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.mgd.php
@@ -0,0 +1,23 @@
+<?php
+// This file declares a managed database record of type "Job".
+// The record will be automatically inserted, updated, or deleted from the
+// database as appropriate. For more details, see "hook_civicrm_managed" at:
+// http://wiki.civicrm.org/confluence/display/CRMDOC42/Hook+Reference
+return array(
+ 0 =>
+ array(
+ 'name' => 'Cron:Job.Iatsreport',
+ 'entity' => 'Job',
+ 'params' =>
+ array(
+ 'version' => 3,
+ 'name' => 'iATS Payments Get Transaction Journal',
+ 'description' => 'Call into iATS to get transaction journals (for auditing and verifying).',
+ 'run_frequency' => 'Hourly',
+ 'api_entity' => 'Job',
+ 'api_action' => 'iatsreport',
+ 'parameters' => '',
+ ),
+ 'update' => 'never',
+ ),
+);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php
new file mode 100644
index 00000000..90bc3c22
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php
@@ -0,0 +1,211 @@
+<?php
+
+/**
+ * Job.IatsReport API specification (optional)
+ *
+ * Pull in the iATS transaction journal and save it in the corresponding table
+ * for local access for easier verification, auditing and reporting.
+ *
+ * @param array $spec description of fields supported by this API call
+ * @return void
+ * @see http://wiki.civicrm.org/confluence/display/CRM/API+Architecture+Standards
+ */
+function _civicrm_api3_job_iatsreport_spec(&$spec) {
+ // no arguments
+ // TODO: configure for a date range, report, etc.
+}
+
+/**
+ * Job.IatsReport API
+ *
+ * Fetch all recent transactions from iATS for the purposes of auditing (in separate jobs).
+ * This addresses multiple needs:
+ * 1. Verify incomplete ACH/EFT contributions.
+ * 2. Verify recent contributions that went through but weren't reported to CiviCRM due to unexpected connection/code breakage.
+ * 3. Input recurring contributions managed by iATS
+ * 4. Input one-time contributions that did not go through CiviCRM
+ * 5. Audit for remote changes in iATS.
+ *
+ * @param array $params
+ * @return array API result descriptor
+ * @see civicrm_api3_create_success
+ * @see civicrm_api3_create_error
+ * @throws API_Exception
+ */
+function civicrm_api3_job_iatsreport($params) {
+
+ /* get a list of all active/non-test iATS payment processors of any type, quit if there are none */
+ /* We'll make sure they are unique from iATS point of view (i.e. distinct agent codes = username) */
+ try {
+ $result = civicrm_api3('PaymentProcessor', 'get', array(
+ 'sequential' => 1,
+ 'class_name' => array('LIKE' => 'Payment_iATSService%'),
+ 'is_active' => 1,
+ 'is_test' => 0,
+ ));
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ throw new API_Exception('Unexpected error getting payment processors: ' . $e->getMessage()); // . "\n" . $e->getTraceAsString());
+ }
+ if (empty($result['values'])) {
+ return;
+ }
+ $payment_processors = array();
+ foreach ($result['values'] as $payment_processor) {
+ $user_name = $payment_processor['user_name'];
+ $type = $payment_processor['payment_type']; // 1 for cc, 2 for ach/eft
+ $id = $payment_processor['id'];
+ if (empty($payment_processors[$user_name])) {
+ $payment_processors[$user_name] = array();
+ }
+ if (empty($payment_processors[$user_name][$type])) {
+ $payment_processors[$user_name][$type] = array();
+ }
+ $payment_processors[$user_name][$type][$id] = $payment_processor;
+ }
+ // CRM_Core_Error::debug_var('Payment Processors', $payment_processors);
+ // get the settings: TODO allow more detailed configuration of which transactions to import?
+ $iats_settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
+ // I also use the setttings to keep track of the last time I imported journal data from iATS.
+ $iats_journal = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_journal');
+ foreach (array('quick', 'recur', 'series') as $setting) {
+ $import[$setting] = empty($iats_settings['import_' . $setting]) ? 0 : 1;
+ }
+ require_once "CRM/iATS/iATSService.php";
+ // an array of types => methods => payment status of the records retrieved
+ $process_methods = array(
+ 1 => array('cc_journal_csv' => 1, 'cc_payment_box_journal_csv' => 1, 'cc_payment_box_reject_csv' => 4),
+ 2 => array('acheft_journal_csv' => 1, 'acheft_payment_box_journal_csv' => 1, 'acheft_payment_box_reject_csv' => 4),
+ );
+ /* initialize some values so I can report at the end */
+ // count the number of records from each iats account analysed, and the number of each kind found ('action')
+ $processed = array();
+ // save all my api result error messages as well
+ $error_log = array();
+ foreach ($payment_processors as $user_name => $payment_processors_per_user) {
+ $processed[$user_name] = array();
+ foreach ($payment_processors_per_user as $type => $payment_processors_per_user_type) {
+ $processed[$user_name][$type] = array();
+ // we might have multiple payment processors by type e.g. SWIPE or separate codes for
+ // one-time and recurring contributions, I only want to process once per user_name + type
+ $payment_processor = reset($payment_processors_per_user_type);
+ $process_methods_per_type = $process_methods[$type];
+ $iats_service_params = array('type' => 'report', 'iats_domain' => parse_url($payment_processor['url_site'], PHP_URL_HOST)); // + $iats_service_params;
+ /* the is_test below should always be 0, but I'm leaving it in, in case eventually we want to be verifying tests */
+ $credentials = iATS_Service_Request::credentials($payment_processor['id'], $payment_processor['is_test']);
+
+ foreach ($process_methods_per_type as $method => $payment_status_id) {
+ // initialize my counts
+ $processed[$user_name][$type][$method] = 0;
+ // watchdog('civicrm_iatspayments_com', 'pp: <pre>!pp</pre>', array('!pp' => print_r($payment_processor,TRUE)), WATCHDOG_NOTICE);
+ /* get approvals from yesterday, approvals from previous days, and then rejections for this payment processor */
+ /* we're going to assume that all the payment_processors_per_type are using the same server */
+ $iats_service_params['method'] = $method;
+ $iats = new iATS_Service_Request($iats_service_params);
+ // For some methods, I only want to check once per day.
+ $skip_method = FALSE;
+ $journal_setting_key = 'last_update_' . $method;
+ switch ($method) {
+ case 'acheft_journal_csv': // special case to get today's transactions, so we're as real-time as we can be
+ case 'cc_journal_csv':
+ $request = array(
+ 'date' => date('Y-m-d') . 'T23:59:59+00:00',
+ 'customerIPAddress' => (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']),
+ );
+ break;
+
+ default:
+ // box journals (approvals and rejections) only go up to the end of yesterday
+ $request = array(
+ 'startIndex' => 0,
+ 'endIndex' => 1000,
+ 'toDate' => date('Y-m-d', strtotime('-1 day')) . 'T23:59:59+00:00',
+ 'customerIPAddress' => (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']),
+ );
+ // Calculate how far back I want to go, default 2 days ago.
+ $fromDate = strtotime('-2 days');
+ // Check when I last downloaded this box journal
+ if (!empty($iats_journal[$journal_setting_key])) {
+ // If I've already done this today, don't do it again.
+ if (0 === strpos($iats_journal[$journal_setting_key], date('Y-m-d'))) {
+ $skip_method = TRUE;
+ }
+ else {
+ // Make sure I fill in any gaps if this cron hasn't run for a while, but no more than a month
+ $fromDate = min(strtotime($iats_journal[$journal_setting_key]), strtotime('-2 days'));
+ $fromDate = max($fromDate, strtotime('-30 days'));
+ }
+ }
+ else {
+ // If I've cleared the settings, then go back a month of data.
+ $fromDate = strtotime('-30 days');
+ }
+ // reset the request fromDate, from the beginning of fromDate's day.
+ $request['fromDate'] = date('Y-m-d', $fromDate) . 'T00:00:00+00:00';
+ break;
+ }
+ if (!$skip_method) {
+ $iats_journal[$journal_setting_key] = date('Y-m-d H:i:s');
+ // make the soap request, should return a csv file
+ $response = $iats->request($credentials, $request);
+ // use my iats object to parse the result into an array of transaction ojects
+ $transactions = $iats->getCSV($response, $method);
+ // for the acheft journal, I also pull the previous 4 days and append, a bit of a hack.
+ if ('acheft_journal_csv' == $method) {
+ for ($days_before = -1; $days_before > -5; $days_before--) {
+ $request['date'] = date('Y-m-d', strtotime($days_before . ' day')) . 'T23:59:59+00:00';
+ $response = $iats->request($credentials, $request);
+ $transactions = array_merge($transactions, $iats->getCSV($response, $method));
+ }
+ }
+ // CRM_Core_Error::debug_var($method, $transactions);
+ foreach ($transactions as $transaction) {
+ try {
+ $t = get_object_vars($transaction);
+ $t['status_id'] = $payment_status_id;
+ // A few more hacks for the one day journals
+ switch ($method) {
+ case 'acheft_journal_csv':
+ $t['data']['Method of Payment'] = 'ACHEFT';
+ $t['data']['Client Code'] = $credentials['agentCode'];
+ break;
+
+ case 'cc_journal_csv':
+ $t['data']['Method of Payment'] = $t['data']['CCType'];
+ $t['data']['Client Code'] = $credentials['agentCode'];
+ break;
+
+ }
+ civicrm_api3('IatsPayments', 'journal', $t);
+ $processed[$user_name][$type][$method]++;
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ $error_log[] = $e->getMessage();
+ }
+ }
+ }
+ }
+ }
+ }
+ CRM_Core_BAO_Setting::setItem($iats_journal, 'iATS Payments Extension', 'iats_journal');
+ // watchdog('civicrm_iatspayments_com', 'found: <pre>!found</pre>', array('!found' => print_r($processed,TRUE)), WATCHDOG_NOTICE);
+ $message = '';
+ foreach ($processed as $user_name => $p) {
+ foreach ($p as $type => $ps) {
+ $prefix = ($type == 1) ? 'cc' : 'acheft';
+ $results
+ = array(
+ 1 => $user_name,
+ 2 => $prefix,
+ 3 => $ps[$prefix . '_journal_csv'],
+ 4 => $ps[$prefix . '_payment_box_journal_csv'],
+ 5 => $ps[$prefix . '_payment_box_reject_csv'],
+ );
+ $message .= '<br />' . ts('For account %1, type %2, processed %3 approvals from the one-day journals, and %4 approval and %5 rejection records from previous days using the box journals.', $results);
+ }
+ }
+ if (count($error_log) > 0) {
+ return civicrm_api3_create_error($message . '</br />' . implode('<br />', $error_log));
+ }
+ return civicrm_api3_create_success($message);
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.mgd.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.mgd.php
new file mode 100644
index 00000000..22e8eec1
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.mgd.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * This file declares a managed database record of type "Job".
+ */
+
+// The record will be automatically inserted, updated, or deleted from the
+// database as appropriate. For more details, see "hook_civicrm_managed" at:
+// http://wiki.civicrm.org/confluence/display/CRMDOC42/Hook+Reference
+return array(
+ 0 =>
+ array(
+ 'name' => 'Cron:Job.Iatsverify',
+ 'entity' => 'Job',
+ 'params' =>
+ array(
+ 'version' => 3,
+ 'name' => 'iATS Payments Verification',
+ 'description' => 'Verify payments from the iATS journal.',
+ 'run_frequency' => 'Hourly',
+ 'api_entity' => 'Job',
+ 'api_action' => 'iatsverify',
+ 'parameters' => '',
+ ),
+ 'update' => 'never',
+ ),
+);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php
new file mode 100644
index 00000000..2145781d
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php
@@ -0,0 +1,212 @@
+<?php
+
+/**
+ * @file
+ * Contains the IATS Payments Verification API Job.
+ */
+
+/**
+ * Job.IatsVerify API specification
+ * This is used for documentation and validation.
+ *
+ * @param array $spec description of fields supported by this API call
+ *
+ * @return void
+ *
+ * @see http://wiki.civicrm.org/confluence/display/CRM/API+Architecture+Standards
+ */
+function _civicrm_api3_job_iatsverify_spec(&$spec) {
+ $spec['recur_id'] = array(
+ 'name' => 'recur_id',
+ 'title' => 'Recurring payment id',
+ 'api.required' => 0,
+ 'type' => 1,
+ );
+ $spec['contribution_id'] = array(
+ 'name' => 'contribution_id',
+ 'title' => 'Test a single contribution by CiviCRM contribution table id.',
+ 'api.required' => 0,
+ 'type' => 1,
+ );
+ $spec['invoice_id'] = array(
+ 'name' => 'invoice_id',
+ 'title' => 'Test a single contribution by invoice id.',
+ 'api.required' => 0,
+ 'type' => 1,
+ );
+ $spec['payment_instrument_id'] = array(
+ 'name' => 'payment_instrument_id',
+ 'title' => 'Test contributions by payment method.',
+ 'api.required' => 0,
+ 'type' => 1,
+ );
+ $spec['reverify'] = array(
+ 'name' => 'reverify',
+ 'title' => 'Reverify contributions',
+ 'api.required' => 0,
+ 'type' => 1,
+ );
+}
+
+/**
+ * Job.IatsVerify API.
+ *
+ * Look up all incomplete or pending (status = 2) contributions and see if they've been received approved or rejected payments
+ * at iATS, looked up via the Journal
+ * Update the corresponding recurring contribution record to status = 1 (or 4)
+ * This works for both the initial contribution and subsequent contributions of recurring contributions, as well as one offs.
+ * TODO: what kind of alerts should be provided if it fails?
+ *
+ * @param array $params
+ *
+ * @return array API result descriptor
+ *
+ * @see civicrm_api3_create_success
+ * @see civicrm_api3_create_error
+ *
+ * @throws API_Exception
+ */
+function civicrm_api3_job_iatsverify($params) {
+
+ $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
+ $receipt_recurring = $settings['receipt_recurring'];
+ define('IATS_VERIFY_DAYS', 30);
+ // I've added an extra 2 days when getting candidates from CiviCRM to be sure i've got them all.
+ $verify_days = IATS_VERIFY_DAYS + 2;
+ // Get all the contributions that may need approval within the last verify_days.
+ // And see if they are approved in my iATS Journal.
+ // This could include ACH/EFT approvals, as well as CC contributions that were completed but didn't get back from iATS.
+ // Count the number of each kind found.
+ $processed = array(1 => 0, 4 => 0);
+ // Save all my api error result messages.
+ $error_log = array();
+ $select_params = array(
+ 'sequential' => 1,
+ 'receive_date' => array('>' => "now - $verify_days day"),
+ 'options' => array('limit' => 0),
+ 'contribution_status_id' => array('IN' => array('Pending')),
+ 'invoice_id' => array('IS NOT NULL' => 1),
+ 'contribution_test' => 0,
+ 'return' => array('trxn_id', 'invoice_id', 'contribution_recur_id', 'contact_id', 'source'),
+ );
+ // get my parameters
+ $recur_id = empty($params['recur_id']) ? 0 : ((int) $params['recur_id']);
+ unset($params['recur_id']);
+ if ($recur_id) {
+ $select_params['contribution_recur_id'] = $recur_id;
+ }
+ $contribution_id = empty($params['contribution_id']) ? 0 : ((int) $params['contribution_id']);
+ unset($params['contribution_id']);
+ if ($contribution_id) {
+ $select_params['contribution_id'] = $contribution_id;
+ }
+ $invoice_id = empty($params['invoice_id']) ? '' : trim($params['invoice_id']);
+ unset($params['invoice_id']);
+ if ($invoice_id) {
+ $select_params['invoice_id'] = $invoice_id;
+ }
+
+ $message = '';
+ try {
+ $contributions_verify = civicrm_api3('Contribution', 'get', $select_params);
+ $message .= '<br />' . ts('Found %1 contributions to verify.', array(1 => count($contributions_verify['values'])));
+ // CRM_Core_Error::debug_var('Verifying contributions', $contributions_verify);
+ foreach ($contributions_verify['values'] as $contribution) {
+ $journal_matches = civicrm_api3('IatsPayments', 'get_journal', array(
+ 'sequential' => 1,
+ 'inv' => $contribution['invoice_id'],
+ ));
+ if ($journal_matches['count'] > 0) {
+ /* found a matching journal entry, we can approve or fail it */
+ $is_recur = empty($pending_contribution['contribution_recur_id']) ? FALSE : TRUE;
+ // I only use the first one to determine the new status of the contribution.
+ // TODO, deal with multiple partial payments
+ $journal_entry = reset($journal_matches['values']);
+ $transaction_id = $journal_entry['tnid'];
+ $contribution_status_id = (int) $journal_entry['status_id'];
+ // Keep track of how many of each time I've processed.
+ $processed[$contribution_status_id]++;
+ switch ($contribution_status_id) {
+ case 1: // i.e. complete
+ // Updating a contribution status to complete needs some extra bookkeeping.
+ // Note that I'm updating the timestamp portion of the transaction id here, since this might be useful at some point
+ // Should I update the receive date to when it was actually received? Would that confuse membership dates?
+ $trxn_id = $transaction_id . ':' . time();
+ $complete = array('version' => 3, 'id' => $contribution['id'], 'trxn_id' => $trxn_id, 'receive_date' => $contribution['receive_date']);
+ if ($is_recur) {
+ // For email receipting, use either my iats extension global, or the specific setting for this schedule.
+ $is_email_receipt = $receipt_recurring;
+ if ($is_email_receipt >= 2) {
+ try {
+ $is_email_receipt = civicrm_api3('ContributionRecur', 'getvalue', array(
+ 'return' => 'is_email_receipt',
+ 'id' => $contribution['contribution_recur_id'],
+ ));
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ $is_email_receipt = 0;
+ $error_log[] = $e->getMessage() . "\n";
+ }
+ }
+ $complete['is_email_receipt'] = $is_email_receipt;
+ }
+ try {
+ $contributionResult = civicrm_api3('contribution', 'completetransaction', $complete);
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ $error_log[] = 'Failed to complete transaction: ' . $e->getMessage() . "\n";
+ }
+
+ // Restore source field and trxn_id that completetransaction overwrites
+ civicrm_api3('contribution', 'create', array(
+ 'id' => $contribution['id'],
+ 'source' => $contribution['source'],
+ 'trxn_id' => $trxn_id,
+ ));
+ case 4: // failed, just update the contribution status.
+ civicrm_api3('Contribution', 'create', array(
+ 'id' => $contribution['id'],
+ 'contribution_status_id' => $contribution_status_id,
+ ));
+ }
+ // Always log these requests in my cutom civicrm table for auditing type purposes
+ $query_params = array(
+ 1 => array($journal_entry['cstc'], 'String'),
+ 2 => array($contribution['contact_id'], 'Integer'),
+ 3 => array($contribution['id'], 'Integer'),
+ 4 => array($contribution_status_id, 'Integer'),
+ 5 => array($journal_entry['rst'], 'String'),
+ 6 => array($contribution['contribution_recur_id'], 'Integer'),
+ );
+ if (empty($contribution['contribution_recur_id'])) {
+ unset($query_params[6]);
+ CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_verify
+ (customer_code, cid, contribution_id, contribution_status_id, verify_datetime, auth_result) VALUES (%1, %2, %3, %4, NOW(), %5)", $query_params);
+ }
+ else {
+ CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_verify
+ (customer_code, cid, contribution_id, contribution_status_id, verify_datetime, auth_result, recur_id) VALUES (%1, %2, %3, %4, NOW(), %5, %6)", $query_params);
+ }
+ }
+ }
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ $error_log[] = $e->getMessage() . "\n";
+ }
+ $message .= '<br />' . ts('Completed with %1 errors.',
+ array(
+ 1 => count($error_log),
+ )
+ );
+ $message .= '<br />' . ts('Processed %1 approvals and %2 rejection records from the previous ' . IATS_VERIFY_DAYS . ' days.',
+ array(
+ 1 => $processed[1],
+ 2 => $processed[4],
+ )
+ );
+ // If errors ..
+ if (count($error_log) > 0) {
+ return civicrm_api3_create_error($message . '</br />' . implode('<br />', $error_log));
+ }
+ return civicrm_api3_create_success($message);
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/composer.json b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/composer.json
new file mode 100644
index 00000000..74c9b211
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/composer.json
@@ -0,0 +1,5 @@
+{
+ "name": "iats-payments/civicrm",
+ "description": "CiviCRM extension for iATS Payments",
+ "license": "AGPL-3.0"
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/css/iatspayments_civicrm.css b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/css/iatspayments_civicrm.css
new file mode 100644
index 00000000..089639db
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/css/iatspayments_civicrm.css
@@ -0,0 +1,15 @@
+/* Disable the display of the direct debit fields that are injected by Core templates/CRM/Core/BillingBlock */
+
+.account_holder-section {
+ display: none;
+}
+.bank_account_number-section {
+ display: none;
+}
+.bank_identification_number-section {
+ display: none;
+}
+.bank_name-section {
+ display: none;
+}
+
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iATS_4.4.14.diff b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iATS_4.4.14.diff
new file mode 100644
index 00000000..02327db7
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iATS_4.4.14.diff
@@ -0,0 +1,49 @@
+--- ./CRM/Core/Payment/Form.php 2014-07-01 20:52:02.000000000 -0400
++++ ./CRM/Core/Payment/Form.php 2014-09-12 08:27:20.564179607 -0400
+@@ -363,9 +363,9 @@
+ $errors['cvv2'] = ts('Please enter a valid Card Verification Number');
+ }
+ }
+- elseif (!empty($values['credit_card_number'])) {
+- $errors['credit_card_number'] = ts('Please enter a valid Card Number');
+- }
++ /* elseif (!empty($values['credit_card_number'])) {
++ $errors['credit_card_number'] = ts('Please enter a Card Number');
++ } */
+ }
+
+ /**
+--- ./CRM/Member/Form/Membership.php 2014-07-01 20:52:02.000000000 -0400
++++ ./CRM/Member/Form/Membership.php 2014-09-11 13:42:33.470862876 -0400
+@@ -150,7 +150,7 @@
+ if ($this->_mode) {
+ $this->_paymentProcessor = array('billing_mode' => 1);
+ $validProcessors = array();
+- $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, 'billing_mode IN ( 1, 3 )');
++ $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, 'billing_mode IN ( 1, 3 ) AND payment_type = 1');
+
+ foreach ($processors as $ppID => $label) {
+ $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($ppID, $this->_mode);
+--- ./CRM/Event/Form/Participant.php 2014-07-01 20:52:02.000000000 -0400
++++ ./CRM/Event/Form/Participant.php 2014-09-11 12:36:41.549807505 -0400
+@@ -264,7 +264,7 @@
+ $this->_paymentProcessor = array('billing_mode' => 1);
+
+ $validProcessors = array();
+- $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, "billing_mode IN ( 1, 3 )");
++ $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, "billing_mode IN ( 1, 3 ) AND payment_type = 1");
+
+ foreach ($processors as $ppID => $label) {
+ $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($ppID, $this->_mode);
+--- ./CRM/Event/Form/Participant.php 2015-03-26 19:51:15.208118122 -0400
++++ ./CRM/Event/Form/Participant.php 2015-03-26 19:52:20.455620537 -0400
+@@ -1340,7 +1340,8 @@
+ $payment = CRM_Core_Payment::singleton($this->_mode, $this->_paymentProcessor, $this);
+
+ // CRM-15622: fix for incorrect contribution.fee_amount
+- $paymentParams['fee_amount'] = NULL;
++ // KG 4.4 issue only
++ // $paymentParams['fee_amount'] = NULL;
+ $result = $payment->doDirectPayment($paymentParams);
+
+ if (is_a($result, 'CRM_Core_Error')) {
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iATS_4.5.8.diff b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iATS_4.5.8.diff
new file mode 100644
index 00000000..48e9b1c5
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iATS_4.5.8.diff
@@ -0,0 +1,37 @@
+--- CRM/Core/Payment/Form.php
++++ CRM/Core/Payment/Form.php
+@@ -364,9 +364,9 @@ class CRM_Core_Payment_Form {
+ $errors['cvv2'] = ts('Please enter a valid Card Verification Number');
+ }
+ }
+- elseif (!empty($values['credit_card_number'])) {
+- $errors['credit_card_number'] = ts('Please enter a valid Card Number');
+- }
++ /* elseif (!empty($values['credit_card_number'])) {
++ $errors['credit_card_number'] = ts('Please enter a Card Number');
++ } */
+ }
+
+ /**
+--- CRM/Event/Form/Participant.php
++++ CRM/Event/Form/Participant.php
+@@ -274,7 +274,7 @@ class CRM_Event_Form_Participant extends CRM_Contact_Form_Task {
+ $this->_paymentProcessor = array('billing_mode' => 1);
+
+ $validProcessors = array();
+- $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, "billing_mode IN ( 1, 3 )");
++ $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, "billing_mode IN ( 1, 3 ) AND payment_type = 1");
+
+ foreach ($processors as $ppID => $label) {
+ $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($ppID, $this->_mode);
+--- CRM/Member/Form/Membership.php
++++ CRM/Member/Form/Membership.php
+@@ -150,7 +150,7 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
+ if ($this->_mode) {
+ $this->_paymentProcessor = array('billing_mode' => 1);
+ $validProcessors = array();
+- $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, 'billing_mode IN ( 1, 3 )');
++ $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, 'billing_mode IN ( 1, 3 ) AND payment_type = 1');
+
+ foreach ($processors as $ppID => $label) {
+ $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($ppID, $this->_mode);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iats.civix.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iats.civix.php
new file mode 100644
index 00000000..fc2423c5
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iats.civix.php
@@ -0,0 +1,278 @@
+<?php
+
+// AUTO-GENERATED FILE -- Civix may overwrite any changes made to this file
+
+/**
+ * (Delegated) Implementation of hook_civicrm_config
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_config
+ */
+function _iats_civix_civicrm_config(&$config = NULL) {
+ static $configured = FALSE;
+ if ($configured) return;
+ $configured = TRUE;
+
+ $template =& CRM_Core_Smarty::singleton();
+
+ $extRoot = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
+ $extDir = $extRoot . 'templates';
+
+ if ( is_array( $template->template_dir ) ) {
+ array_unshift( $template->template_dir, $extDir );
+ } else {
+ $template->template_dir = array( $extDir, $template->template_dir );
+ }
+
+ $include_path = $extRoot . PATH_SEPARATOR . get_include_path( );
+ set_include_path( $include_path );
+}
+
+/**
+ * (Delegated) Implementation of hook_civicrm_xmlMenu
+ *
+ * @param $files array(string)
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_xmlMenu
+ */
+function _iats_civix_civicrm_xmlMenu(&$files) {
+ foreach (_iats_civix_glob(__DIR__ . '/xml/Menu/*.xml') as $file) {
+ $files[] = $file;
+ }
+}
+
+/**
+ * Implementation of hook_civicrm_install
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_install
+ */
+function _iats_civix_civicrm_install() {
+ _iats_civix_civicrm_config();
+ if ($upgrader = _iats_civix_upgrader()) {
+ return $upgrader->onInstall();
+ }
+}
+
+/**
+ * Implementation of hook_civicrm_uninstall
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_uninstall
+ */
+function _iats_civix_civicrm_uninstall() {
+ _iats_civix_civicrm_config();
+ if ($upgrader = _iats_civix_upgrader()) {
+ return $upgrader->onUninstall();
+ }
+}
+
+/**
+ * (Delegated) Implementation of hook_civicrm_enable
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_enable
+ */
+function _iats_civix_civicrm_enable() {
+ _iats_civix_civicrm_config();
+ if ($upgrader = _iats_civix_upgrader()) {
+ if (is_callable(array($upgrader, 'onEnable'))) {
+ return $upgrader->onEnable();
+ }
+ }
+}
+
+/**
+ * (Delegated) Implementation of hook_civicrm_disable
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_disable
+ */
+function _iats_civix_civicrm_disable() {
+ _iats_civix_civicrm_config();
+ if ($upgrader = _iats_civix_upgrader()) {
+ if (is_callable(array($upgrader, 'onDisable'))) {
+ return $upgrader->onDisable();
+ }
+ }
+}
+
+/**
+ * (Delegated) Implementation of hook_civicrm_upgrade
+ *
+ * @param $op string, the type of operation being performed; 'check' or 'enqueue'
+ * @param $queue CRM_Queue_Queue, (for 'enqueue') the modifiable list of pending up upgrade tasks
+ *
+ * @return mixed based on op. for 'check', returns array(boolean) (TRUE if upgrades are pending)
+ * for 'enqueue', returns void
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_upgrade
+ */
+function _iats_civix_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) {
+ if ($upgrader = _iats_civix_upgrader()) {
+ return $upgrader->onUpgrade($op, $queue);
+ }
+}
+
+/**
+ * @return CRM_iATS_Upgrader
+ */
+function _iats_civix_upgrader() {
+ if (!file_exists(__DIR__.'/CRM/iATS/Upgrader.php')) {
+ return NULL;
+ } else {
+ return CRM_iATS_Upgrader_Base::instance();
+ }
+}
+
+/**
+ * Search directory tree for files which match a glob pattern
+ *
+ * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
+ * Note: In Civi 4.3+, delegate to CRM_Utils_File::findFiles()
+ *
+ * @param $dir string, base dir
+ * @param $pattern string, glob pattern, eg "*.txt"
+ * @return array(string)
+ */
+function _iats_civix_find_files($dir, $pattern) {
+ if (is_callable(array('CRM_Utils_File', 'findFiles'))) {
+ return CRM_Utils_File::findFiles($dir, $pattern);
+ }
+
+ $todos = array($dir);
+ $result = array();
+ while (!empty($todos)) {
+ $subdir = array_shift($todos);
+ foreach (_iats_civix_glob("$subdir/$pattern") as $match) {
+ if (!is_dir($match)) {
+ $result[] = $match;
+ }
+ }
+ if ($dh = opendir($subdir)) {
+ while (FALSE !== ($entry = readdir($dh))) {
+ $path = $subdir . DIRECTORY_SEPARATOR . $entry;
+ if ($entry{0} == '.') {
+ } elseif (is_dir($path)) {
+ $todos[] = $path;
+ }
+ }
+ closedir($dh);
+ }
+ }
+ return $result;
+}
+/**
+ * (Delegated) Implementation of hook_civicrm_managed
+ *
+ * Find any *.mgd.php files, merge their content, and return.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_managed
+ */
+function _iats_civix_civicrm_managed(&$entities) {
+ $mgdFiles = _iats_civix_find_files(__DIR__, '*.mgd.php');
+ foreach ($mgdFiles as $file) {
+ $es = include $file;
+ foreach ($es as $e) {
+ if (empty($e['module'])) {
+ $e['module'] = 'com.iatspayments.civicrm';
+ }
+ $entities[] = $e;
+ }
+ }
+}
+
+/**
+ * (Delegated) Implementation of hook_civicrm_caseTypes
+ *
+ * Find any and return any files matching "xml/case/*.xml"
+ *
+ * Note: This hook only runs in CiviCRM 4.4+.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_caseTypes
+ */
+function _iats_civix_civicrm_caseTypes(&$caseTypes) {
+ if (!is_dir(__DIR__ . '/xml/case')) {
+ return;
+ }
+
+ foreach (_iats_civix_glob(__DIR__ . '/xml/case/*.xml') as $file) {
+ $name = preg_replace('/\.xml$/', '', basename($file));
+ if ($name != CRM_Case_XMLProcessor::mungeCaseType($name)) {
+ $errorMessage = sprintf("Case-type file name is malformed (%s vs %s)", $name, CRM_Case_XMLProcessor::mungeCaseType($name));
+ CRM_Core_Error::fatal($errorMessage);
+ // throw new CRM_Core_Exception($errorMessage);
+ }
+ $caseTypes[$name] = array(
+ 'module' => 'com.iatspayments.civicrm',
+ 'name' => $name,
+ 'file' => $file,
+ );
+ }
+}
+
+/**
+ * Glob wrapper which is guaranteed to return an array.
+ *
+ * The documentation for glob() says, "On some systems it is impossible to
+ * distinguish between empty match and an error." Anecdotally, the return
+ * result for an empty match is sometimes array() and sometimes FALSE.
+ * This wrapper provides consistency.
+ *
+ * @link http://php.net/glob
+ * @param string $pattern
+ * @return array, possibly empty
+ */
+function _iats_civix_glob($pattern) {
+ $result = glob($pattern);
+ return is_array($result) ? $result : array();
+}
+
+/**
+ * Inserts a navigation menu item at a given place in the hierarchy
+ *
+ * $menu - menu hierarchy
+ * $path - path where insertion should happen (ie. Administer/System Settings)
+ * $item - menu you need to insert (parent/child attributes will be filled for you)
+ * $parentId - used internally to recurse in the menu structure
+ */
+function _iats_civix_insert_navigation_menu(&$menu, $path, $item, $parentId = NULL) {
+ static $navId;
+
+ // If we are done going down the path, insert menu
+ if (empty($path)) {
+ if (!$navId) $navId = CRM_Core_DAO::singleValueQuery("SELECT max(id) FROM civicrm_navigation");
+ $navId ++;
+ $menu[$navId] = array (
+ 'attributes' => array_merge($item, array(
+ 'label' => CRM_Utils_Array::value('name', $item),
+ 'active' => 1,
+ 'parentID' => $parentId,
+ 'navID' => $navId,
+ ))
+ );
+ return true;
+ } else {
+ // Find an recurse into the next level down
+ $found = false;
+ $path = explode('/', $path);
+ $first = array_shift($path);
+ foreach ($menu as $key => &$entry) {
+ if ($entry['attributes']['name'] == $first) {
+ if (!$entry['child']) $entry['child'] = array();
+ $found = _iats_civix_insert_navigation_menu($entry['child'], implode('/', $path), $item, $key);
+ }
+ }
+ return $found;
+ }
+}
+
+/**
+ * (Delegated) Implementation of hook_civicrm_alterSettingsFolders
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_alterSettingsFolders
+ */
+function _iats_civix_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) {
+ static $configured = FALSE;
+ if ($configured) return;
+ $configured = TRUE;
+
+ $settingsDir = __DIR__ . DIRECTORY_SEPARATOR . 'settings';
+ if(is_dir($settingsDir) && !in_array($settingsDir, $metaDataFolders)) {
+ $metaDataFolders[] = $settingsDir;
+ }
+} \ No newline at end of file
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iats.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iats.php
new file mode 100644
index 00000000..65467d75
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/iats.php
@@ -0,0 +1,1530 @@
+<?php
+
+/**
+ * @file Copyright iATS Payments (c) 2014
+ * Author: Alan Dixon.
+ *
+ * This file is a part of CiviCRM published extension.
+ *
+ * This extension is free software; you can copy, modify, and distribute it
+ * under the terms of the GNU Affero General Public License
+ * Version 3, 19 November 2007.
+ *
+ * It is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License with this program; if not, see http://www.gnu.org/licenses/
+ */
+
+require_once 'iats.civix.php';
+
+/**
+ * Implementation of hook_civicrm_config().
+ */
+function iats_civicrm_config(&$config) {
+ _iats_civix_civicrm_config($config);
+}
+
+/**
+ * Implementation of hook_civicrm_xmlMenu.
+ *
+ * @param $files array(string)
+ */
+function iats_civicrm_xmlMenu(&$files) {
+ _iats_civix_civicrm_xmlMenu($files);
+}
+
+/**
+ * Implementation of hook_civicrm_install.
+ */
+function iats_civicrm_install() {
+ if (!class_exists('SoapClient')) {
+ $session = CRM_Core_Session::singleton();
+ $session->setStatus(ts('The PHP SOAP extension is not installed on this server, but is required for this extension'), ts('iATS Payments Installation'), 'error');
+ }
+ return _iats_civix_civicrm_install();
+}
+
+/**
+ * Implementation of hook_civicrm_uninstall.
+ */
+function iats_civicrm_uninstall() {
+ return _iats_civix_civicrm_uninstall();
+}
+
+/**
+ * Implementation of hook_civicrm_enable.
+ */
+function iats_civicrm_enable() {
+ if (!class_exists('SoapClient')) {
+ $session = CRM_Core_Session::singleton();
+ $session->setStatus(ts('The PHP SOAP extension is not installed on this server, but is required for this extension'), ts('iATS Payments Installation'), 'error');
+ }
+ return _iats_civix_civicrm_enable();
+}
+
+/**
+ * Implementation of hook_civicrm_disable.
+ */
+function iats_civicrm_disable() {
+ return _iats_civix_civicrm_disable();
+}
+
+/**
+ * Implementation of hook_civicrm_upgrade.
+ *
+ * @param $op string, the type of operation being performed; 'check' or 'enqueue'
+ * @param $queue CRM_Queue_Queue, (for 'enqueue') the modifiable list of pending up upgrade tasks
+ *
+ * @return mixed based on op. for 'check', returns array(boolean) (TRUE if upgrades are pending)
+ * for 'enqueue', returns void
+ */
+function iats_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) {
+ return _iats_civix_civicrm_upgrade($op, $queue);
+}
+
+/**
+ * Implementation of hook_civicrm_managed.
+ *
+ * Generate a list of entities to create/deactivate/delete when this module
+ * is installed, disabled, uninstalled.
+ */
+function iats_civicrm_managed(&$entities) {
+ $entities[] = array(
+ 'module' => 'com.iatspayments.civicrm',
+ 'name' => 'iATS Payments',
+ 'entity' => 'PaymentProcessorType',
+ 'params' => array(
+ 'version' => 3,
+ 'name' => 'iATS Payments Credit Card',
+ 'title' => 'iATS Payments Credit Card',
+ 'description' => 'iATS credit card payment processor using the web services interface.',
+ 'class_name' => 'Payment_iATSService',
+ 'billing_mode' => 'form',
+ 'user_name_label' => 'Agent Code',
+ 'password_label' => 'Password',
+ 'url_site_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'url_recur_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'url_site_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'url_recur_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'is_recur' => 1,
+ 'payment_type' => 1,
+ ),
+ );
+ $entities[] = array(
+ 'module' => 'com.iatspayments.civicrm',
+ 'name' => 'iATS Payments ACH/EFT',
+ 'entity' => 'PaymentProcessorType',
+ 'params' => array(
+ 'version' => 3,
+ 'name' => 'iATS Payments ACH/EFT',
+ 'title' => 'iATS Payments ACH/EFT',
+ 'description' => 'iATS ACH/EFT payment processor using the web services interface.',
+ 'class_name' => 'Payment_iATSServiceACHEFT',
+ 'billing_mode' => 'form',
+ 'user_name_label' => 'Agent Code',
+ 'password_label' => 'Password',
+ 'url_site_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'url_recur_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'url_site_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'url_recur_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'is_recur' => 1,
+ 'payment_type' => 2,
+ 'payment_instrument_id' => '2', /* "Debit Card" */
+ ),
+ );
+ $entities[] = array(
+ 'module' => 'com.iatspayments.civicrm',
+ 'name' => 'iATS Payments SWIPE',
+ 'entity' => 'PaymentProcessorType',
+ 'params' => array(
+ 'version' => 3,
+ 'name' => 'iATS Payments SWIPE',
+ 'title' => 'iATS Payments SWIPE',
+ 'description' => 'iATS credit card payment processor using the encrypted USB IDTECH card reader.',
+ 'class_name' => 'Payment_iATSServiceSWIPE',
+ 'billing_mode' => 'form',
+ 'user_name_label' => 'Agent Code',
+ 'password_label' => 'Password',
+ 'url_site_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'url_recur_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'url_site_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'url_recur_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'is_recur' => 1,
+ 'payment_type' => 1,
+ ),
+ );
+ return _iats_civix_civicrm_managed($entities);
+}
+
+/**
+ * Implements hook_civicrm_check().
+ */
+function iats_civicrm_check(&$messages) {
+ if (!class_exists('SoapClient')) {
+ $messages[] = new CRM_Utils_Check_Message(
+ 'iats_soap',
+ ts('The SOAP extension for PHP %1 is not installed on this server, but is required for this extension.', array(1 => phpversion())),
+ ts('iATS Payments Installation'),
+ \Psr\Log\LogLevel::CRITICAL,
+ 'fa-flag'
+ );
+ }
+}
+
+/**
+ * Utility function to get domain info.
+ *
+ * Get values from the civicrm_domain table, or a domain setting.
+ * May be called multiple times, so be efficient.
+ */
+function _iats_civicrm_domain_info($key) {
+ static $domain, $settings;
+ if (empty($domain)) {
+ $domain = civicrm_api3('Domain', 'getsingle', array('current_domain' => TRUE));
+ }
+ if (!isset($settings)) {
+ $settings = array();
+ }
+ switch ($key) {
+ case 'version':
+ return explode('.', $domain['version']);
+
+ default:
+ if (isset($domain[$key])) {
+ return $domain[$key];
+ }
+ elseif (isset($settings[$key])) {
+ return $settings[$key];
+ }
+ else {
+ try{
+ $setting = civicrm_api3('Setting', 'getvalue', array('name' => $key));
+ if (is_string($setting)) {
+ $settings[$key] = $setting;
+ return $setting;
+ }
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ // ignore errors
+ }
+ // This remaining code is now very legacy, from earlier Civi versions and should soon be retired.
+ if (!empty($domain['config_backend'])) {
+ $config_backend = unserialize($domain['config_backend']);
+ if (!empty($config_backend[$key])) {
+ $settings[$key] = $config_backend[$key];
+ return $config_backend[$key];
+ }
+ }
+ }
+ // Uncomment one or more of these lines to find out what it was we were looking for and didn't find.
+ // CRM_Core_Error::debug_var('domain', $domain);
+ // CRM_Core_Error::debug_var($key, $settings);
+ // CRM_Core_Error::debug_var($key, $setting);
+ }
+}
+
+/* START utility functions to allow this extension to work with different civicrm version */
+
+/**
+ * Does this version of Civi implement repeattransaction well?
+ */
+function _iats_civicrm_use_repeattransaction() {
+ $version = CRM_Utils_System::version();
+ return (version_compare($version, '4.7.12') < 0) ? FALSE : TRUE;
+}
+
+/**
+ * Get the name of the next scheduled contribution date field, (not necessary since 4.4)
+ */
+function _iats_civicrm_nscd_fid() {
+ $version = CRM_Utils_System::version();
+ return (version_compare($version, '4.4') < 0) ? 'next_sched_contribution' : 'next_sched_contribution_date';
+}
+
+/**
+ * Set js config values, version dependent. Access is also version dependent.
+ */
+function _iats_civicrm_varset($vars) {
+ $version = CRM_Utils_System::version();
+ // Support 4.4!
+ if (version_compare($version, '4.5') < 0) {
+ CRM_Core_Resources::singleton()->addSetting('iatspayments', $vars);
+ }
+ else {
+ CRM_Core_Resources::singleton()->addVars('iatspayments', $vars);
+ }
+}
+
+/* END functions to allow this extension to work with different civicrm version */
+
+/**
+ * Utility to get the next available menu key.
+ */
+function _iats_getMenuKeyMax($menuArray) {
+ $max = array(max(array_keys($menuArray)));
+ foreach ($menuArray as $v) {
+ if (!empty($v['child'])) {
+ $max[] = _iats_getMenuKeyMax($v['child']);
+ }
+ }
+ return max($max);
+}
+
+/**
+ *
+ */
+function iats_civicrm_navigationMenu(&$navMenu) {
+ $pages = array(
+ 'admin_page' => array(
+ 'label' => 'iATS Payments Admin',
+ 'name' => 'iATS Payments Admin',
+ 'url' => 'civicrm/iATSAdmin',
+ 'parent' => array('Contributions'),
+ 'permission' => 'access CiviContribute,administer CiviCRM',
+ 'operator' => 'AND',
+ 'separator' => NULL,
+ 'active' => 1,
+ ),
+ 'settings_page' => array(
+ 'label' => 'iATS Payments Settings',
+ 'name' => 'iATS Payments Settings',
+ 'url' => 'civicrm/admin/contribute/iatssettings',
+ 'parent' => array('Administer', 'CiviContribute'),
+ 'permission' => 'access CiviContribute,administer CiviCRM',
+ 'operator' => 'AND',
+ 'separator' => NULL,
+ 'active' => 1,
+ ),
+ );
+ foreach ($pages as $item) {
+ // Check that our item doesn't already exist.
+ $menu_item_search = array('url' => $item['url']);
+ $menu_items = array();
+ CRM_Core_BAO_Navigation::retrieve($menu_item_search, $menu_items);
+ if (empty($menu_items)) {
+ $path = implode('/', $item['parent']);
+ unset($item['parent']);
+ _iats_civix_insert_navigation_menu($navMenu, $path, $item);
+ }
+ }
+}
+
+/**
+ * Hook_civicrm_buildForm.
+ * Do a Drupal 7 style thing so we can write smaller functions.
+ */
+function iats_civicrm_buildForm($formName, &$form) {
+ // But start by grouping a few forms together for nicer code.
+ switch ($formName) {
+ case 'CRM_Event_Form_Participant':
+ case 'CRM_Member_Form_Membership':
+ case 'CRM_Contribute_Form_Contribution':
+ // Override normal convention, deal with all these backend credit card contribution forms the same way.
+ $fname = 'iats_civicrm_buildForm_CreditCard_Backend';
+ break;
+
+ case 'CRM_Contribute_Form_Contribution_Main':
+ case 'CRM_Event_Form_Registration_Register':
+ case 'CRM_Financial_Form_Payment':
+ // Override normal convention, deal with all these front-end contribution forms the same way.
+ $fname = 'iats_civicrm_buildForm_Contribution_Frontend';
+ break;
+
+ case 'CRM_Contribute_Form_Contribution_Confirm':
+ // On the confirmation form, we know the processor, so only do processor specific customizations.
+ $fname = 'iats_civicrm_buildForm_Contribution_Confirm_' . $form->_paymentProcessor['class_name'];
+ break;
+
+ case 'CRM_Contribute_Form_Contribution_ThankYou':
+ // On the confirmation form, we know the processor, so only do processor specific customizations.
+ $fname = 'iats_civicrm_buildForm_Contribution_ThankYou_' . $form->_paymentProcessor['class_name'];
+ break;
+
+ default:
+ $fname = 'iats_civicrm_buildForm_' . $formName;
+ break;
+ }
+ if (function_exists($fname)) {
+ $fname($form);
+ }
+ // Else echo $fname;.
+}
+
+/**
+ *
+ */
+function iats_civicrm_pageRun(&$page) {
+ $fname = 'iats_civicrm_pageRun_' . $page->getVar('_name');
+ if (function_exists($fname)) {
+ $fname($page);
+ }
+}
+
+/**
+ *
+ */
+function iats_civicrm_pageRun_CRM_Contact_Page_View_Summary(&$page) {
+ // Because of AJAX loading, I need to load my backend swipe js here.
+ $swipe = iats_civicrm_processors(NULL, 'SWIPE', array('is_default' => 1));
+ if (count($swipe) > 0) {
+ CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/swipe.js', 10);
+ }
+}
+
+/**
+ * Modify the recurring contribution (subscription) page.
+ * Display extra information about recurring contributions using iATS, and
+ * link to iATS CustomerLink display and editing pages.
+ */
+function iats_civicrm_pageRun_CRM_Contribute_Page_ContributionRecur($page) {
+ // Get the corresponding (most recently created) iATS customer code record referenced from the customer_codes table via the recur_id ('crid')
+ // we'll also get the expiry date and last four digits (at least, our best information about that).
+ $extra = array();
+ $crid = CRM_Utils_Request::retrieve('id', 'Integer', $page, FALSE);
+ try {
+ $recur = civicrm_api3('ContributionRecur', 'getsingle', array('id' => $crid));
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ return;
+ }
+ $type = _iats_civicrm_is_iats($recur['payment_processor_id']);
+ if (!$type) {
+ return;
+ }
+ try {
+ $params = array(1 => array($crid, 'Integer'));
+ $dao = CRM_Core_DAO::executeQuery("SELECT customer_code,expiry FROM civicrm_iats_customer_codes WHERE recur_id = %1 ORDER BY id DESC LIMIT 1", $params);
+ if ($dao->fetch()) {
+ $customer_code = $dao->customer_code;
+ $extra['iATS Customer Code'] = $customer_code;
+ $customerLinkView = CRM_Utils_System::url('civicrm/contact/view/iatscustomerlink',
+ 'reset=1&cid=' . $recur['contact_id'] . '&customerCode=' . $customer_code . '&paymentProcessorId=' . $recur['payment_processor_id'] . '&is_test=' . $recur['is_test']);
+ $extra['customerLink'] = "<a href='$customerLinkView'>View</a>";
+ if ($type == 'iATSService' || $type == 'iATSServiceSWIPE') {
+ $customerLinkEdit = CRM_Utils_System::url('civicrm/contact/edit/iatscustomerlink',
+ 'reset=1&cid=' . $recur['contact_id'] . '&customerCode=' . $customer_code . '&paymentProcessorId=' . $recur['payment_processor_id'] . '&is_test=' . $recur['is_test']);
+ $extra['customerLink'] .= " | <a href='$customerLinkEdit'>Edit</a>";
+ $processLink = CRM_Utils_System::url('civicrm/contact/iatsprocesslink',
+ 'reset=1&cid=' . $recur['contact_id'] . '&customerCode=' . $customer_code . '&paymentProcessorId=' . $recur['payment_processor_id'] . '&crid=' . $crid . '&is_test=' . $recur['is_test']);
+ $extra['customerLink'] .= " | <a href='$processLink'>Process</a>";
+ $expiry = str_split($dao->expiry, 2);
+ $extra['expiry'] = '20' . implode('-', $expiry);
+ }
+ }
+ if (!empty($recur['invoice_id'])) {
+ // We may have the last 4 digits via the original request log, though they may no longer be accurate, but let's get it anyway if we can.
+ $params = array(1 => array($recur['invoice_id'], 'String'));
+ $dao = CRM_Core_DAO::executeQuery("SELECT cc FROM civicrm_iats_request_log WHERE invoice_num = %1", $params);
+ if ($dao->fetch()) {
+ $extra['cc'] = $dao->cc;
+ }
+ }
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ return;
+ }
+ if (!count($extra)) {
+ return;
+ }
+ $template = CRM_Core_Smarty::singleton();
+ foreach ($extra as $key => $value) {
+ $template->assign($key, $value);
+ }
+ CRM_Core_Region::instance('page-body')->add(array(
+ 'template' => 'CRM/iATS/ContributionRecur.tpl',
+ ));
+ CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/subscription_view.js');
+}
+
+/**
+ * Hook_civicrm_merge
+ * Deal with contact merges - our custom iats customer code table contains contact id's as a check, it might need to be updated.
+ */
+function iats_civicrm_merge($type, &$data, $mainId = NULL, $otherId = NULL, $tables = NULL) {
+ if ('cidRefs' == $type) {
+ $data['civicrm_iats_customer_codes'] = array('cid');
+ $data['civicrm_iats_verify'] = array('cid');
+ }
+}
+
+/**
+ * Hook_civicrm_pre.
+ *
+ * Handle special cases of creating contribution (regular and recurring) records when using IATS Payments.
+ *
+ * 1. CiviCRM assumes all recurring contributions need to be confirmed using the IPN mechanism. This is not true for iATS recurring contributions.
+ * So when creating a contribution that is part of a recurring series, test for status = 2, and set to status = 1 instead, unless we're using the fixed day feature
+ * Do this only for the initial contribution record.
+ * The (subsequent) recurring contributions' status id is set explicitly in the job that creates it, this modification breaks that process.
+ *
+ * 2. For ACH/EFT, we also have the opposite problem - all contributions will need to verified by iATS and only later set to status success or
+ * failed via the acheft verify job. We also want to modify the payment instrument from CC to ACH/EFT
+ *
+ * TODO: update this code with constants for the various id values of 1 and 2.
+ * TODO: CiviCRM should have nicer ways to handle this.
+ */
+function iats_civicrm_pre($op, $objectName, $objectId, &$params) {
+ // Since this function gets called a lot, quickly determine if I care about the record being created.
+ if (('create' == $op) && ('Contribution' == $objectName) && !empty($params['contribution_status_id'])) {
+ // watchdog('iats_civicrm','hook_civicrm_pre for Contribution <pre>@params</pre>',array('@params' => print_r($params));
+ // figure out the payment processor id, not nice.
+ $version = CRM_Utils_System::version();
+ $payment_processor_id = !empty($params['payment_processor']) ? $params['payment_processor'] :
+ (!empty($params['contribution_recur_id']) ? _iats_civicrm_get_payment_processor_id($params['contribution_recur_id']) :
+ 0);
+ if ($type = _iats_civicrm_is_iats($payment_processor_id)) {
+ $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
+ $allow_days = empty($settings['days']) ? array('-1') : $settings['days'];
+ switch ($type . $objectName) {
+ // Cc contribution, test if it's been set to status 2 on a recurring contribution.
+ case 'iATSServiceContribution':
+ case 'iATSServiceSWIPEContribution':
+ // For civi version before 4.6.6, we had to force the status to 1.
+ if ((2 == $params['contribution_status_id'])
+ && !empty($params['contribution_recur_id'])
+ && (max($allow_days) <= 0)
+ && (version_compare($version, '4.6.6') < 0)
+ ) {
+ // But only for the first one.
+ $count = civicrm_api3('Contribution', 'getcount', array('contribution_recur_id' => $params['contribution_recur_id']));
+ if (
+ (is_array($count) && empty($count['result']))
+ || empty($count)
+ ) {
+ // watchdog('iats_civicrm','hook_civicrm_pre updating status_id for objectName @id, count <pre>!count</pre>, params <pre>!params</pre>, ',array('@id' => $objectName, '!count' => print_r($count,TRUE),'!params' => print_r($params,TRUE)));.
+ $params['contribution_status_id'] = 1;
+ }
+ }
+ break;
+
+ case 'iATSServiceACHEFTContribution':
+ // ach/eft contribution: update the payment instrument if it's still showing cc or empty
+ if ($params['payment_instrument_id'] <= 1) {
+ $params['payment_instrument_id'] = 2;
+ }
+ // And push the status to 2 if civicrm thinks it's 1, i.e. for one-time contributions
+ // in other words, never create ach/eft contributions as complete, always push back to pending and verify.
+ if ($params['contribution_status_id'] == 1) {
+ $params['contribution_status_id'] = 2;
+ }
+ break;
+
+ // UK DD contribution: update the payment instrument, fix the receive date.
+ case 'iATSServiceUKDDContribution':
+ if ($params['payment_instrument_id'] <= 1) {
+ $params['payment_instrument_id'] = 2;
+ }
+ if ($start_date = strtotime($_POST['payer_validate_start_date'])) {
+ $params['receive_date'] = date('Ymd', $start_date) . '120000';
+ }
+ break;
+
+ }
+ }
+ // watchdog('iats_civicrm','ignoring hook_civicrm_pre for objectName @id',array('@id' => $objectName));.
+ }
+ // If I've set fixed monthly recurring dates, force any iats (non uk dd) recurring contribution schedule records to comply.
+ if (('ContributionRecur' == $objectName) && ('create' == $op || 'edit' == $op) && !empty($params['payment_processor_id'])) {
+ if ($type = _iats_civicrm_is_iats($params['payment_processor_id'])) {
+ if ($type != 'iATSServiceUKDD' && !empty($params['next_sched_contribution_date'])) {
+ $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
+ $allow_days = empty($settings['days']) ? array('-1') : $settings['days'];
+ // Force one of the fixed days, and set the cycle_day at the same time.
+ if (0 < max($allow_days)) {
+ $init_time = ('create' == $op) ? time() : strtotime($params['next_sched_contribution_date']);
+ $from_time = _iats_contributionrecur_next($init_time, $allow_days);
+ $params['next_sched_contribution_date'] = date('YmdHis', $from_time);
+ // Day of month without leading 0.
+ $params['cycle_day'] = date('j', $from_time);
+ }
+ }
+ // Fix a civi bug while I'm here.
+ if (empty($params['installments'])) {
+ $params['installments'] = '0';
+ }
+ }
+ }
+}
+
+/**
+ * The contribution itself doesn't tell you which payment processor it came from
+ * So we have to dig back via the contribution_recur_id that it is associated with.
+ */
+function _iats_civicrm_get_payment_processor_id($contribution_recur_id) {
+ $params = array(
+ 'id' => $contribution_recur_id,
+ );
+ try {
+ $result = civicrm_api3('ContributionRecur', 'getsingle', $params);
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ return FALSE;
+ }
+ if (empty($result['payment_processor_id'])) {
+ return FALSE;
+ // TODO: log error.
+ }
+ return $result['payment_processor_id'];
+}
+
+/**
+ * Utility function to see if a payment processor id is using one of the iATS payment processors.
+ *
+ * This function relies on our naming convention for the iats payment processor classes, staring with the string Payment_iATSService.
+ */
+function _iats_civicrm_is_iats($payment_processor_id) {
+ if (empty($payment_processor_id)) {
+ return FALSE;
+ }
+ $params = array(
+ 'id' => $payment_processor_id,
+ );
+ try {
+ $result = civicrm_api3('PaymentProcessor', 'getsingle', $params);
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ return FALSE;
+ }
+ if (empty($result['class_name'])) {
+ return FALSE;
+ // TODO: log error.
+ }
+ $type = substr($result['class_name'], 0, 19);
+ $subtype = substr($result['class_name'], 19);
+ return ('Payment_iATSService' == $type) ? 'iATSService' . $subtype : FALSE;
+}
+
+/**
+ * Internal utility function: return the id's of any iATS processors matching various conditions.
+ *
+ * Processors: an array of payment processors indexed by id to filter by,
+ * or if NULL, it searches through all
+ * subtype: the iats service class name subtype
+ * params: an array of additional params to pass to the api call.
+ */
+function iats_civicrm_processors($processors, $subtype = '', $params = array()) {
+ $list = array();
+ $match_all = ('*' == $subtype) ? TRUE : FALSE;
+ if (!$match_all) {
+ $params['class_name'] = 'Payment_iATSService' . $subtype;
+ }
+
+ // Set the domain id if not passed in.
+ if (!array_key_exists('domain_id', $params)) {
+ $params['domain_id'] = CRM_Core_Config::domainID();
+ }
+
+ $result = civicrm_api3('PaymentProcessor', 'get', $params);
+ if (0 == $result['is_error'] && count($result['values']) > 0) {
+ foreach ($result['values'] as $paymentProcessor) {
+ $id = $paymentProcessor['id'];
+ if ((is_null($processors)) || !empty($processors[$id])) {
+ if (!$match_all || (0 === strpos($paymentProcessor['class_name'], 'Payment_iATSService'))) {
+ $list[$id] = $paymentProcessor;
+ }
+ }
+ }
+ }
+ return $list;
+}
+
+/**
+ * Customize direct debit billing blocks, per currency.
+ *
+ * Each country has different rules about direct debit, so only currencies that we explicitly handle will be
+ * customized, others will get a warning.
+ *
+ * The currency-specific functions will do things like modify labels, add exta fields,
+ * add legal requirement notice and perhaps checkbox acceptance for electronic acceptance of ACH/EFT, and
+ * make this form nicer by include a sample check with instructions for getting the various numbers
+ *
+ * Each one also includes some javascript to move the new fields around on the DOM
+ */
+function iats_acheft_form_customize($form) {
+ $currency = iats_getCurrency($form);
+ $fname = 'iats_acheft_form_customize_' . $currency;
+ /* we always want these three fields to be required, in all currencies. As of 4.6.?, this is in core */
+ if (empty($form->billingFieldSets['direct_debit']['fields']['account_holder']['is_required'])) {
+ $form->addRule('account_holder', ts('%1 is a required field.', array(1 => ts('Name of Account Holder'))), 'required');
+ }
+ if (empty($form->billingFieldSets['direct_debit']['fields']['bank_account_number']['is_required'])) {
+ $form->addRule('bank_account_number', ts('%1 is a required field.', array(1 => ts('Account Number'))), 'required');
+ }
+ if (empty($form->billingFieldSets['direct_debit']['fields']['bank_name']['is_required'])) {
+ $form->addRule('bank_name', ts('%1 is a required field.', array(1 => ts('Bank Name'))), 'required');
+ }
+ if ($currency && function_exists($fname)) {
+ $fname($form);
+ }
+ // I'm handling an unexpected currency.
+ elseif ($currency) {
+ CRM_Core_Region::instance('billing-block')->add(array(
+ 'template' => 'CRM/iATS/BillingBlockDirectDebitExtra_Other.tpl',
+ ));
+ }
+}
+
+/**
+ *
+ */
+function iats_getCurrency($form) {
+ // getting the currency depends on the form class
+ $form_class = get_class($form);
+ $currency = '';
+ switch($form_class) {
+ case 'CRM_Contribute_Form_Contribution':
+ case 'CRM_Contribute_Form_Contribution_Main':
+ case 'CRM_Member_Form_Membership':
+ $currency = $form->_values['currency'];
+ break;
+ case 'CRM_Financial_Form_Payment':
+ // This is the new ajax-loaded payment form.
+ $currency = $form->getCurrency();
+ break;
+ case 'CRM_Event_Form_Participant':
+ case 'CRM_Event_Form_Registration_Register':
+ $currency = $form->_values['event']['currency'];
+ break;
+ }
+ if (empty($currency)) {
+ // This may occur in edge cases, so don't break, though the form won't be rendered correctly.
+ // See comment on civicrm core commit f61437d
+ CRM_Core_Error::debug_var($form_class, $form);
+ }
+ return $currency;
+}
+
+/**
+ * Customization for USD ACH-EFT billing block.
+ */
+function iats_acheft_form_customize_USD($form) {
+ $form->addElement('select', 'bank_account_type', ts('Account type'), array('CHECKING' => 'Checking', 'SAVING' => 'Saving'));
+ $form->addRule('bank_account_type', ts('%1 is a required field.', array(1 => ts('Account type'))), 'required');
+ $element = $form->getElement('account_holder');
+ $element->setLabel(ts('Name of Account Holder'));
+ $element = $form->getElement('bank_account_number');
+ $element->setLabel(ts('Bank Account Number'));
+ $element = $form->getElement('bank_identification_number');
+ $element->setLabel(ts('Bank Routing Number'));
+ if (empty($form->billingFieldSets['direct_debit']['fields']['bank_identification_number']['is_required'])) {
+ $form->addRule('bank_identification_number', ts('%1 is a required field.', array(1 => ts('Bank Routing Number'))), 'required');
+ }
+ CRM_Core_Region::instance('billing-block')->add(array(
+ 'template' => 'CRM/iATS/BillingBlockDirectDebitExtra_USD.tpl',
+ ));
+}
+
+/**
+ * Customization for CAD ACH-EFT billing block.
+ */
+function iats_acheft_form_customize_CAD($form) {
+ $form->addElement('text', 'cad_bank_number', ts('Bank Number (3 digits)'));
+ $form->addRule('cad_bank_number', ts('%1 is a required field.', array(1 => ts('Bank Number'))), 'required');
+ $form->addRule('cad_bank_number', ts('%1 must contain only digits.', array(1 => ts('Bank Number'))), 'numeric');
+ $form->addRule('cad_bank_number', ts('%1 must be of length 3.', array(1 => ts('Bank Number'))), 'rangelength', array(3, 3));
+ $form->addElement('text', 'cad_transit_number', ts('Transit Number (5 digits)'));
+ $form->addRule('cad_transit_number', ts('%1 is a required field.', array(1 => ts('Transit Number'))), 'required');
+ $form->addRule('cad_transit_number', ts('%1 must contain only digits.', array(1 => ts('Transit Number'))), 'numeric');
+ $form->addRule('cad_transit_number', ts('%1 must be of length 5.', array(1 => ts('Transit Number'))), 'rangelength', array(5, 5));
+ $form->addElement('select', 'bank_account_type', ts('Account type'), array('CHECKING' => 'Chequing', 'SAVING' => 'Savings'));
+ $form->addRule('bank_account_type', ts('%1 is a required field.', array(1 => ts('Account type'))), 'required');
+ /* minor customization of labels + make them required */
+ $element = $form->getElement('account_holder');
+ $element->setLabel(ts('Name of Account Holder'));
+ $element = $form->getElement('bank_account_number');
+ $element->setLabel(ts('Account Number'));
+ $form->addRule('bank_account_number', ts('%1 must contain only digits.', array(1 => ts('Bank Account Number'))), 'numeric');
+ /* the bank_identification_number is hidden and then populated using jquery, in the custom template */
+ $element = $form->getElement('bank_identification_number');
+ $element->setLabel(ts('Bank Number + Transit Number'));
+ CRM_Core_Region::instance('billing-block')->add(array(
+ 'template' => 'CRM/iATS/BillingBlockDirectDebitExtra_CAD.tpl',
+ ));
+}
+
+/**
+ * Contribution form customization for iATS secure swipe.
+ */
+function iats_swipe_form_customize($form) {
+ // Remove two fields that are replaced by the swipe code data
+ // we need to remove them from the _paymentFields as well or they'll sneak back in!
+ $form->removeElement('credit_card_type', TRUE);
+ $form->removeElement('cvv2', TRUE);
+ unset($form->_paymentFields['credit_card_type']);
+ unset($form->_paymentFields['cvv2']);
+ // Add a single text area to store/display the encrypted cc number that the swipe device will fill.
+ $form->addElement('textarea', 'encrypted_credit_card_number', ts('Encrypted'), array('cols' => '80', 'rows' => '8'));
+ $form->addRule('encrypted_credit_card_number', ts('%1 is a required field.', array(1 => ts('Encrypted'))), 'required');
+ CRM_Core_Region::instance('billing-block')->add(array(
+ 'template' => 'CRM/iATS/BillingBlockSwipe.tpl',
+ ));
+}
+
+/**
+ * Customize direct debit billing block for UK Direct Debit.
+ *
+ * This could be handled by iats_acheft_form_customize, except there's some tricky multi-page stuff for the payer validate step.
+ */
+function iats_ukdd_form_customize($form) {
+ /* uk direct debits have to start 16 days after the initial request is made */
+ if (!$form->elementExists('is_recur')) {
+ // Todo generate an error on the page.
+ return;
+ }
+ define('IATS_UKDD_START_DELAY', 16 * 24 * 60 * 60);
+ /* For batch efficiencies, restrict to a specific set of days of the month, less than 28 */
+ // you can change these if you're sensible and careful.
+ $start_days = array('1', '15');
+ $start_dates = _iats_get_future_monthly_start_dates(time() + IATS_UKDD_START_DELAY, $start_days);
+ $service_user_number = $form->_paymentProcessor['signature'];
+ $payee = _iats_civicrm_domain_info('name');
+ $phone = _iats_civicrm_domain_info('domain_phone');
+ $email = _iats_civicrm_domain_info('domain_email');
+ $form->addRule('is_recur', ts('You can only use this form to make recurring contributions.'), 'required');
+ /* declaration checkbox at the top */
+ $form->addElement('checkbox', 'payer_validate_declaration', ts('I wish to start a Direct Debit'));
+ $form->addElement('static', 'payer_validate_contact', ts(''), ts('Organization: %1, Phone: %2, Email: %3', array('%1' => $payee, '%2' => $phone['phone'], '%3' => $email)));
+ $form->addElement('select', 'payer_validate_start_date', ts('Date of first collection'), $start_dates);
+ $form->addRule('payer_validate_declaration', ts('%1 is a required field.', array(1 => ts('The Declaration'))), 'required');
+ $form->addRule('installments', ts('%1 is a required field.', array(1 => ts('Number of installments'))), 'required');
+ /* customization of existing elements */
+ $element = $form->getElement('account_holder');
+ $element->setLabel(ts('Account Holder Name'));
+ if (empty($form->billingFieldSets['direct_debit']['fields']['bank_identification_number']['is_required'])) {
+ $form->addRule('account_holder', ts('%1 is a required field.', array(1 => ts('Name of Account Holder'))), 'required');
+ }
+ $element = $form->getElement('bank_account_number');
+ $element->setLabel(ts('Account Number'));
+ if (empty($form->billingFieldSets['direct_debit']['fields']['bank_identification_number']['is_required'])) {
+ $form->addRule('bank_account_number', ts('%1 is a required field.', array(1 => ts('Account Number'))), 'required');
+ }
+ $element = $form->getElement('bank_identification_number');
+ $element->setLabel(ts('Sort Code'));
+ if (empty($form->billingFieldSets['direct_debit']['fields']['bank_identification_number']['is_required'])) {
+ $form->addRule('bank_identification_number', ts('%1 is a required field.', array(1 => ts('Sort Code'))), 'required');
+ }
+ /* new payer validation elements */
+ $form->addElement('textarea', 'payer_validate_address', ts('Name and full postal address of your Bank or Building Society'), array('rows' => '6', 'columns' => '30'));
+ $form->addElement('text', 'payer_validate_service_user_number', ts('Service User Number'));
+ $form->addElement('text', 'payer_validate_reference', ts('Reference'), array());
+ // Date on which the validation happens, reference.
+ $form->addElement('text', 'payer_validate_date', ts('Today\'s Date'), array());
+ $form->addElement('static', 'payer_validate_instruction', ts('Instruction to your Bank or Building Society'), ts('Please pay %1 Direct Debits from the account detailed in this instruction subject to the safeguards assured by the Direct Debit Guarantee. I understand that this instruction may remain with TestingTest and, if so, details will be passed electronically to my Bank / Building Society.', array('%1' => "<strong>$payee</strong>")));
+ // $form->addRule('bank_name', ts('%1 is a required field.', array(1 => ts('Bank Name'))), 'required');
+ // $form->addRule('bank_account_type', ts('%1 is a required field.', array(1 => ts('Account type'))), 'required');
+ /* only allow recurring contributions, set date */
+ $form->setDefaults(array(
+ 'is_recur' => 1,
+ 'payer_validate_date' => date('F j, Y'),
+ 'payer_validate_start_date' => current(array_keys($start_dates)),
+ 'payer_validate_service_user_number' => $service_user_number,
+ // Make recurring contrib default to true.
+ ));
+ CRM_Core_Region::instance('billing-block')->add(array(
+ 'template' => 'CRM/iATS/BillingBlockDirectDebitExtra_GBP.tpl',
+ ));
+}
+
+/**
+ * Modifications to a (public/frontend) contribution forms.
+ * 1. set recurring to be the default, if enabled (ACH/EFT) [previously forced recurring, removed in 1.2.4]
+ * 2. add extra fields/modify labels.
+ * 3. enable public selection of future recurring contribution start date.
+ *
+ * We're handling event, contribution, and financial payment class forms here.
+ * The new 4.7 financial payment class form is used when switching payment processors (sometimes).
+ */
+function iats_civicrm_buildForm_Contribution_Frontend(&$form) {
+
+ $form_class = get_class($form);
+
+ if ($form_class == 'CRM_Financial_Form_Payment') {
+ // We're on CRM_Financial_Form_Payment, we've got just one payment processor
+ $id = $form->_paymentProcessor['id'];
+ $iats_processors = iats_civicrm_processors(array($id => $form->_paymentProcessor), '*');
+ }
+ else {
+ // Handle the event and contribution page forms
+ if (empty($form->_paymentProcessors)) {
+ if (empty($form->_paymentProcessorIDs)) {
+ return;
+ }
+ else {
+ $form_payment_processors = array_fill_keys($form->_paymentProcessorIDs,1);
+ }
+ }
+ else {
+ $form_payment_processors = $form->_paymentProcessors;
+ }
+ $iats_processors = iats_civicrm_processors($form_payment_processors, '*');
+ }
+ if (empty($iats_processors)) {
+ return;
+ }
+ $ukdd = $swipe = $acheft = array();
+ foreach ($iats_processors as $id => $processor) {
+ switch ($processor['class_name']) {
+ case 'Payment_iATSServiceACHEFT':
+ $acheft[$id] = $processor;
+ break;
+
+ case 'Payment_iATSServiceSWIPE':
+ $swipe[$id] = $processor;
+ break;
+
+ case 'Payment_iATSServiceUKDD':
+ $ukdd[$id] = $processor;
+ break;
+ }
+ }
+ // Include the required javascripts for available customized selections
+ // If a form allows ACH/EFT and enables recurring, set recurring to the default.
+ if (0 < count($acheft)) {
+ if (isset($form->_elementIndex['is_recur'])) {
+ // Make recurring contrib default to true.
+ $form->setDefaults(array('is_recur' => 1));
+ }
+ }
+ if (0 < count($swipe)) {
+ CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/swipe.js', 10);
+ }
+ if (0 < count($ukdd)) {
+ CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/dd_uk.js', 10);
+ if (isset($form->_elementIndex['is_recur'])) {
+ // Make recurring contrib default to true.
+ $form->setDefaults(array('is_recur' => 1));
+ }
+ }
+
+ // If enabled on a page with monthly recurring contributions enabled, provide a way to set future contribution dates.
+ // Uses javascript to hide/reset unless they have recurring contributions checked.
+ if (isset($form->_elementIndex['is_recur'])) {
+ $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
+ if (!empty($settings['enable_public_future_recurring_start'])) {
+ $allow_days = empty($settings['days']) ? array('-1') : $settings['days'];
+ $start_dates = _iats_get_future_monthly_start_dates(time(), $allow_days);
+ $form->addElement('select', 'receive_date', ts('Date of first contribution'), $start_dates);
+ CRM_Core_Region::instance('billing-block')->add(array(
+ 'template' => 'CRM/iATS/BillingBlockRecurringExtra.tpl',
+ ));
+ CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/recur_start.js', 10);
+ }
+ }
+
+ /* Mangle the ajax bit of the form (if any) by processor type */
+ if (!empty($form->_paymentProcessor['id'])) {
+ $id = $form->_paymentProcessor['id'];
+ /* Note that Ach/Eft is currency dependent */
+ if (!empty($acheft[$id])) {
+ iats_acheft_form_customize($form);
+ // watchdog('iats_acheft',kprint_r($form,TRUE));.
+ }
+ elseif (!empty($swipe[$id])) {
+ iats_swipe_form_customize($form);
+ }
+ /* UK Direct debit option */
+ elseif (!empty($ukdd[$id])) {
+ iats_ukdd_form_customize($form);
+ // watchdog('iats_acheft',kprint_r($form,TRUE));.
+ }
+ }
+
+}
+
+/**
+ * Fix the backend credit card contribution forms
+ * Includes CRM_Contribute_Form_Contribution, CRM_Event_Form_Participant, CRM_Member_Form_Membership
+ * 1. Remove my ACH/EFT processors
+ * Now fixed in core for contribution forms: https://issues.civicrm.org/jira/browse/CRM-14442
+ * 2. Force SWIPE (i.e. remove all others) if it's the default, and mangle the form accordingly.
+ * For now, this form doesn't refresh when you change payment processors, so I can't use swipe if it's not the default, so i have to remove it.
+ */
+function iats_civicrm_buildForm_CreditCard_Backend(&$form) {
+ // Skip if i don't have any processors.
+ if (empty($form->_processors)) {
+ return;
+ }
+ // Get all my swipe processors.
+ $swipe = iats_civicrm_processors($form->_processors, 'SWIPE');
+ // Get all my ACH/EFT processors (should be 0, but I'm fixing old core bugs)
+ $acheft = iats_civicrm_processors($form->_processors, 'ACHEFT');
+ // If an iATS SWIPE payment processor is enabled and default remove all other payment processors.
+ $swipe_id_default = 0;
+ if (0 < count($swipe)) {
+ foreach ($swipe as $id => $pp) {
+ if ($pp['is_default']) {
+ $swipe_id_default = $id;
+ break;
+ }
+ }
+ }
+ // Find the available pp options form element (update this if we ever switch from quickform, uses a quickform internals)
+ // not all invocations of the form include this, so check for non-empty value first.
+ if (!empty($form->_elementIndex['payment_processor_id'])) {
+ $pp_form_id = $form->_elementIndex['payment_processor_id'];
+ // Now cycle through them, either removing everything except the default swipe or just removing the ach/eft.
+ $element = $form->_elements[$pp_form_id]->_options;
+ foreach ($element as $option_id => $option) {
+ // Key is set to payment processor id.
+ $pp_id = $option['attr']['value'];
+ if ($swipe_id_default) {
+ // Remove any that are not my swipe default pp.
+ if ($pp_id != $swipe_id_default) {
+ unset($form->_elements[$pp_form_id]->_options[$option_id]);
+ unset($form->_processors[$pp_id]);
+ if (!empty($form->_recurPaymentProcessors[$pp_id])) {
+ unset($form->_recurPaymentProcessors[$pp_id]);
+ }
+ }
+ }
+ elseif (!empty($acheft[$pp_id]) || !empty($swipe[$pp_id])) {
+ // Remove my ach/eft and swipe, which both require form changes.
+ unset($form->_elements[$pp_form_id]->_options[$option_id]);
+ unset($form->_processors[$pp_id]);
+ if (!empty($form->_recurPaymentProcessors[$pp_id])) {
+ unset($form->_recurPaymentProcessors[$pp_id]);
+ }
+ }
+ }
+ }
+
+ // If i'm using swipe as default and I've got a billing section, then customize it.
+ if ($swipe_id_default) {
+ CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/swipe.js', 10);
+ if (!empty($form->_elementIndex['credit_card_exp_date'])) {
+ iats_swipe_form_customize($form);
+ }
+ }
+}
+
+/**
+ * Provide helpful links to backend-ish payment pages for ACH/EFT, since the backend credit card pages don't work/apply
+ * Could do the same for swipe?
+ */
+function iats_civicrm_buildForm_CRM_Contribute_Form_Search(&$form) {
+ // Ignore invocations that aren't for a specific contact, e.g. the civicontribute dashboard.
+ if (empty($form->_defaultValues['contact_id'])) {
+ return;
+ }
+ $contactID = $form->_defaultValues['contact_id'];
+ $acheft = iats_civicrm_processors(NULL, 'ACHEFT', array('is_active' => 1, 'is_test' => 0));
+ $acheft_backoffice_links = array();
+ // For each ACH/EFT payment processor, try to provide a different mechanism for 'backoffice' type contributions
+ // note: only offer payment pages that provide iATS ACH/EFT exclusively.
+ foreach (array_keys($acheft) as $pp_id) {
+ $params = array('is_active' => 1, 'payment_processor' => $pp_id);
+ $result = civicrm_api3('ContributionPage', 'get', $params);
+ if (0 == $result['is_error'] && count($result['values']) > 0) {
+ foreach ($result['values'] as $page) {
+ $url = CRM_Utils_System::url('civicrm/contribute/transact', 'reset=1&cid=' . $contactID . '&id=' . $page['id']);
+ $acheft_backoffice_links[] = array('url' => $url, 'title' => $page['title']);
+ }
+ }
+ }
+ if (count($acheft_backoffice_links)) {
+ _iats_civicrm_varset(array('backofficeLinks' => $acheft_backoffice_links));
+ CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/contribute_form_search.js');
+ }
+}
+
+/**
+ * If this recurring contribution sequence is using an iATS payment processor,
+ * modify the recurring contribution cancelation form to exclude the confusing message about sending the request to the backend.
+ */
+function iats_civicrm_buildForm_CRM_Contribute_Form_CancelSubscription(&$form) {
+ $crid = CRM_Utils_Request::retrieve('crid', 'Integer', $form, FALSE);
+ try {
+ $recur = civicrm_api3('ContributionRecur', 'getsingle', array('id' => $crid));
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ return;
+ }
+ if (_iats_civicrm_is_iats($recur['payment_processor_id'])) {
+ if ($form->elementExists('send_cancel_request')) {
+ $form->removeElement('send_cancel_request');
+ }
+ }
+}
+
+/**
+ * Modify the contribution Confirm screen for iATS UK DD
+ * 1. display extra field data injected earlier for payer validation.
+ */
+function iats_civicrm_buildForm_Contribution_Confirm_Payment_iATSServiceUKDD(&$form) {
+ $form->addElement('textarea', 'payer_validate_address', ts('Name and full postal address of your Bank or Building Society'), array('rows' => '6', 'columns' => '30'));
+ $form->addElement('text', 'payer_validate_service_user_number', ts('Service User Number'));
+ $form->addElement('text', 'payer_validate_reference', ts('Reference'));
+ // Date on which the validation happens, reference.
+ $form->addElement('text', 'payer_validate_date', ts('Today\'s Date'), array());
+ $form->addElement('text', 'payer_validate_start_date', ts('Date of first collection'));
+ $form->addElement('static', 'payer_validate_instruction', ts('Instruction to your Bank or Building Society'), ts('Please pay %1 Direct Debits from the account detailed in this instruction subject to the safeguards assured by the Direct Debit Guarantee. I understand that this instruction may remain with TestingTest and, if so, details will be passed electronically to my Bank / Building Society.', array('%1' => "<strong>$payee</strong>")));
+ $defaults = array(
+ 'payer_validate_date' => date('F j, Y'),
+ );
+ foreach (array('address', 'service_user_number', 'reference', 'date', 'start_date') as $k) {
+ $key = 'payer_validate_' . $k;
+ $defaults[$key] = $form->_params[$key];
+ };
+ $form->setDefaults($defaults);
+ CRM_Core_Region::instance('contribution-confirm-billing-block')->add(array(
+ 'template' => 'CRM/iATS/ContributeConfirmExtra_UKDD.tpl',
+ ));
+}
+
+/**
+ * Modify the contribution Thank You screen for iATS UK DD.
+ */
+function iats_civicrm_buildForm_Contribution_ThankYou_Payment_iATSServiceUKDD(&$form) {
+ foreach (array('address', 'service_user_number', 'reference', 'date', 'start_date') as $k) {
+ $key = 'payer_validate_' . $k;
+ $form->addElement('static', $key, $key, $form->_params[$key]);
+ };
+ CRM_Core_Region::instance('contribution-thankyou-billing-block')->add(array(
+ 'template' => 'CRM/iATS/ContributeThankYouExtra_UKDD.tpl',
+ ));
+}
+
+/**
+ * Add some functionality to the update subscription form for recurring contributions.
+ */
+function iats_civicrm_buildForm_CRM_Contribute_Form_UpdateSubscription(&$form) {
+
+ /* For 4.7, we implement getEditableRecurringScheduleFields but still need this for additional niceness */
+
+ // Only do this if the user is allowed to edit contributions. A more stringent permission might be smart.
+ if (!CRM_Core_Permission::check('edit contributions')) {
+ return;
+ }
+ // Only mangle this form for recurring contributions using iATS, (and not the UKDD version)
+ $payment_processor_type = empty($form->_paymentProcessor) ? substr(get_class($form->_paymentProcessorObj),9) : $form->_paymentProcessor['class_name'];
+ if (0 !== strpos($payment_processor_type, 'Payment_iATSService')) {
+ return;
+ }
+ if ('Payment_iATSServiceUKDD' == $payment_processor_type) {
+ return;
+ }
+ $settings = civicrm_api3('Setting', 'getvalue', array('name' => 'iats_settings'));
+ // don't do this if the site administrator has disabled it.
+ if (!empty($settings['no_edit_extra'])) {
+ return;
+ }
+ $allow_days = empty($settings['days']) ? array('-1') : $settings['days'];
+ if (0 < max($allow_days)) {
+ $userAlert = ts('Your next scheduled contribution date will automatically be updated to the next allowable day of the month: %1',
+ array(1 => implode(', ', $allow_days)));
+ CRM_Core_Session::setStatus($userAlert, ts('Warning'), 'alert');
+ }
+ $crid = CRM_Utils_Request::retrieve('crid', 'Integer', $form, FALSE);
+ /* get the recurring contribution record and the contact record, or quit */
+ try {
+ $recur = civicrm_api3('ContributionRecur', 'getsingle', array('id' => $crid));
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ return;
+ }
+ try {
+ $contact = civicrm_api3('Contact', 'getsingle', array('id' => $recur['contact_id']));
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ return;
+ }
+ try {
+ $pp = civicrm_api3('PaymentProcessor', 'getsingle', array('id' => $recur['payment_processor_id']));
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ $pp = array();
+ }
+ // Turn off default notification checkbox, because that's a better default.
+ $defaults = array('is_notify' => 0);
+ $edit_fields = array(
+ 'contribution_status_id' => 'Status',
+ 'next_sched_contribution_date' => 'Next Scheduled Contribution',
+ 'start_date' => 'Start Date',
+ 'is_email_receipt' => 'Email receipt for each Contribution in this Recurring Series',
+ );
+ $dupe_fields = array();
+ // To be a good citizen, I check if core or another extension hasn't already added these fields
+ // and remove them if they have.
+ foreach (array_keys($edit_fields) as $fid) {
+ if ($form->elementExists($fid)) {
+ unset($edit_fields[$fid]);
+ $dupe_fields[] = str_replace('_','-',$fid);
+ }
+ elseif (isset($recur[$fid])) {
+ $defaults[$fid] = $recur[$fid];
+ }
+ }
+ // Use this in my js to identify which fields need to be removed from the tpl I inject below
+ _iats_civicrm_varset(array('dupeSubscriptionFields' => $dupe_fields));
+ foreach ($edit_fields as $fid => $label) {
+ switch($fid) {
+ case 'contribution_status_id':
+ $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
+ $form->addElement('select', 'contribution_status_id', ts('Status'), $contributionStatus);
+ break;
+ case 'is_email_receipt':
+ $receiptStatus = array('0' => 'No', '1' => 'Yes');
+ $form->addElement('select', $fid, ts($label), $receiptStatus);
+ break;
+ default:
+ $form->addDateTime($fid, ts($label));
+ break;
+ }
+ }
+ $form->setDefaults($defaults);
+ // Now add some more fields for display only
+ /* Add in the contact's name */
+ $form->addElement('static', 'contact', $contact['display_name']);
+ // get my pp, if available.
+ $pp_label = empty($pp['name']) ? $form->_paymentProcessor['name'] : $pp['name'];
+ $form->addElement('static', 'payment_processor', $pp_label);
+ $label = CRM_Contribute_Pseudoconstant::financialType($recur['financial_type_id']);
+ $form->addElement('static', 'financial_type', $label);
+ $labels = CRM_Contribute_Pseudoconstant::paymentInstrument();
+ $label = $labels[$recur['payment_instrument_id']];
+ $form->addElement('static', 'payment_instrument', $label);
+ $form->addElement('static', 'failure_count', $recur['failure_count']);
+ CRM_Core_Region::instance('page-body')->add(array(
+ 'template' => 'CRM/iATS/Subscription.tpl',
+ ));
+ CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/subscription.js');
+}
+
+function iats_civicrm_buildForm_CRM_Contribute_Form_UpdateBilling(&$form) {
+ // add hidden form field for the contribution recur ID taken from URL
+ // if not specified directly, look it up via a membership ID
+ $crid = CRM_Utils_Array::value('crid', $_GET);
+ if (!$crid) {
+ $mid = CRM_Utils_Array::value('mid', $_GET);
+ if ($mid) {
+ try {
+ $crid = civicrm_api3('Membership', 'getvalue', array(
+ 'id' => $mid,
+ 'return' => 'contribution_recur_id',
+ ));
+ }
+ catch (CiviCRM_API3_Exception $e) {
+ $crid = 0;
+ }
+ }
+ }
+ if ($crid) {
+ $form->addElement('hidden', 'crid', $crid);
+ }
+}
+
+/**
+ * Implementation of hook_civicrm_alterSettingsFolders.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_alterSettingsFolders
+ */
+function iats_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) {
+ _iats_civix_civicrm_alterSettingsFolders($metaDataFolders);
+}
+
+/**
+ * For a recurring contribution, find a reasonable candidate for a template, where possible.
+ */
+function _iats_civicrm_getContributionTemplate($contribution) {
+ // Get the first contribution in this series that matches the same total_amount, if present.
+ $template = array();
+ $get = array('contribution_recur_id' => $contribution['contribution_recur_id'], 'options' => array('sort' => ' id', 'limit' => 1));
+ if (!empty($contribution['total_amount'])) {
+ $get['total_amount'] = $contribution['total_amount'];
+ }
+ $result = civicrm_api3('contribution', 'get', $get);
+ if (!empty($result['values'])) {
+ $contribution_ids = array_keys($result['values']);
+ $template = $result['values'][$contribution_ids[0]];
+ $template['original_contribution_id'] = $contribution_ids[0];
+ $template['line_items'] = array();
+ $get = array('entity_table' => 'civicrm_contribution', 'entity_id' => $contribution_ids[0]);
+ $result = civicrm_api3('LineItem', 'get', $get);
+ if (!empty($result['values'])) {
+ foreach ($result['values'] as $initial_line_item) {
+ $line_item = array();
+ foreach (array('price_field_id', 'qty', 'line_total', 'unit_price', 'label', 'price_field_value_id', 'financial_type_id') as $key) {
+ $line_item[$key] = $initial_line_item[$key];
+ }
+ $template['line_items'][] = $line_item;
+ }
+ }
+ }
+ return $template;
+}
+
+/**
+ * Function _iats_contributionrecur_next.
+ *
+ * @param $from_time: a unix time stamp, the function returns values greater than this
+ * @param $days: an array of allowable days of the month
+ *
+ * A utility function to calculate the next available allowable day, starting from $from_time.
+ * Strategy: increment the from_time by one day until the day of the month matches one of my available days of the month.
+ */
+function _iats_contributionrecur_next($from_time, $allow_mdays) {
+ $dp = getdate($from_time);
+ // So I don't get into an infinite loop somehow.
+ $i = 0;
+ while (($i++ < 60) && !in_array($dp['mday'], $allow_mdays)) {
+ $from_time += (24 * 60 * 60);
+ $dp = getdate($from_time);
+ }
+ return $from_time;
+}
+
+/**
+ * Function _iats_contribution_payment
+ *
+ * @param $contribution an array of a contribution to be created (or in case of future start date,
+ possibly an existing pending contribution to recycle, if it already has a contribution id).
+ * @param $options must include customer code, subtype and iats_domain, may include a membership id
+ * @param $original_contribution_id if included, use as a template for a recurring contribution.
+ *
+ * A high-level utility function for making a contribution payment from an existing recurring schedule
+ * Used in the Iatsrecurringcontributions.php job and the one-time ('card on file') form.
+ *
+ * Since 4.7.12, we can are using the new repeattransaction api.
+ */
+function _iats_process_contribution_payment(&$contribution, $options, $original_contribution_id) {
+ // By default, don't use repeattransaction
+ $use_repeattransaction = FALSE;
+ $is_recurrence = !empty($original_contribution_id);
+ // First try and get the money with iATS Payments, using my cover function.
+ // TODO: convert this into an api job?
+ $result = _iats_process_transaction($contribution, $options);
+
+ // Handle any case of a failure of some kind, either the card failed, or the system failed.
+ if (empty($result['status'])) {
+ /* set the failed transaction status, or pending if I had a server issue */
+ $contribution['contribution_status_id'] = empty($result['auth_result']) ? 2 : 4;
+ /* and include the reason in the source field */
+ $contribution['source'] .= ' ' . $result['reasonMessage'];
+ // Save my reject code here for processing by the calling function (a bit lame)
+ $contribution['iats_reject_code'] = $result['auth_result'];
+ }
+ else {
+ // I have a transaction id.
+ $trxn_id = $contribution['trxn_id'] = trim($result['remote_id']) . ':' . time();
+ // Initialize the status to pending
+ $contribution['contribution_status_id'] = 2;
+ // We'll use the repeattransaction api for successful transactions under three conditions:
+ // 1. if we want it
+ // 2. if we don't already have a contribution id
+ // 3. if we trust it
+ $use_repeattransaction = $is_recurrence && empty($contribution['id']) && _iats_civicrm_use_repeattransaction();
+ }
+ if ($use_repeattransaction) {
+ // We processed it successflly and I can try to use repeattransaction.
+ // Requires the original contribution id.
+ // Issues with this api call:
+ // 1. Always triggers an email and doesn't include trxn.
+ // 2. Date is wrong.
+ try {
+ // $status = $result['contribution_status_id'] == 1 ? 'Completed' : 'Pending';
+ $contributionResult = civicrm_api3('Contribution', 'repeattransaction', array(
+ 'original_contribution_id' => $original_contribution_id,
+ 'contribution_status_id' => 'Pending',
+ 'is_email_receipt' => 0,
+ // 'invoice_id' => $contribution['invoice_id'],
+ ///'receive_date' => $contribution['receive_date'],
+ // 'campaign_id' => $contribution['campaign_id'],
+ // 'financial_type_id' => $contribution['financial_type_id'],.
+ // 'payment_processor_id' => $contribution['payment_processor'],
+ 'contribution_recur_id' => $contribution['contribution_recur_id'],
+ ));
+
+ // watchdog('iats_civicrm','repeat transaction result <pre>@params</pre>',array('@params' => print_r($pending,TRUE)));.
+ $contribution['id'] = CRM_Utils_Array::value('id', $contributionResult);
+ }
+ catch (Exception $e) {
+ // Ignore this, though perhaps I should log it.
+ }
+ if (empty($contribution['id'])) {
+ // Assume I failed completely and I'll fall back to doing it the manual way.
+ $use_repeattransaction = FALSE;
+ }
+ else {
+ // If repeattransaction succeded.
+ // First restore/add various fields that the repeattransaction api may overwrite or ignore.
+ // TODO - fix this in core to allow these to be set above.
+ civicrm_api3('contribution', 'create', array('id' => $contribution['id'],
+ 'invoice_id' => $contribution['invoice_id'],
+ 'source' => $contribution['source'],
+ 'receive_date' => $contribution['receive_date'],
+ 'payment_instrument_id' => $contribution['payment_instrument_id'],
+ // '' => $contribution['receive_date'],
+ ));
+ // Save my status in the contribution array that was passed in.
+ $contribution['contribution_status_id'] = $result['contribution_status_id'];
+ if ($result['contribution_status_id'] == 1) {
+ // My transaction completed, so record that fact in CiviCRM, potentially sending an invoice.
+ try {
+ civicrm_api3('Contribution', 'completetransaction', array(
+ 'id' => $contribution['id'],
+ 'payment_processor_id' => $contribution['payment_processor'],
+ 'is_email_receipt' => (empty($options['is_email_receipt']) ? 0 : 1),
+ 'trxn_id' => $contribution['trxn_id'],
+ 'receive_date' => $contribution['receive_date'],
+ ));
+ }
+ catch (Exception $e) {
+ // log the error and continue
+ CRM_Core_Error::debug_var('Unexpected Exception', $e);
+ }
+ }
+ else {
+ // just save my trxn_id for ACH/EFT verification later
+ try {
+ civicrm_api3('Contribution', 'create', array(
+ 'id' => $contribution['id'],
+ 'trxn_id' => $contribution['trxn_id'],
+ ));
+ }
+ catch (Exception $e) {
+ // log the error and continue
+ CRM_Core_Error::debug_var('Unexpected Exception', $e);
+ }
+ }
+ }
+ }
+ if (!$use_repeattransaction) {
+ /* If I'm not using repeattransaction for any reason, I'll create the contribution manually */
+ // This code assumes that the contribution_status_id has been set properly above, either pending or failed.
+ $contributionResult = civicrm_api3('contribution', 'create', $contribution);
+ // Pass back the created id indirectly since I'm calling by reference.
+ $contribution['id'] = CRM_Utils_Array::value('id', $contributionResult);
+ // Connect to a membership if requested.
+ if (!empty($options['membership_id'])) {
+ try {
+ civicrm_api3('MembershipPayment', 'create', array('contribution_id' => $contribution['id'], 'membership_id' => $options['membership_id']));
+ }
+ catch (Exception $e) {
+ // Ignore.
+ }
+ }
+ /* And then I'm done unless it completed */
+ if ($result['contribution_status_id'] == 1 && !empty($result['status'])) {
+ /* success, and the transaction has completed */
+ $complete = array('id' => $contribution['id'],
+ 'payment_processor_id' => $contribution['payment_processor'],
+ 'trxn_id' => $trxn_id,
+ 'receive_date' => $contribution['receive_date']
+ );
+ $complete['is_email_receipt'] = empty($options['is_email_receipt']) ? 0 : 1;
+ try {
+ $contributionResult = civicrm_api3('contribution', 'completetransaction', $complete);
+ }
+ catch (Exception $e) {
+ // Don't throw an exception here, or else I won't have updated my next contribution date for example.
+ $contribution['source'] .= ' [with unexpected api.completetransaction error: ' . $e->getMessage() . ']';
+ }
+ // Restore my source field that ipn code irritatingly overwrites, and make sure that the trxn_id is set also.
+ civicrm_api3('contribution', 'setvalue', array('id' => $contribution['id'], 'value' => $contribution['source'], 'field' => 'source'));
+ civicrm_api3('contribution', 'setvalue', array('id' => $contribution['id'], 'value' => $trxn_id, 'field' => 'trxn_id'));
+ $message = $is_recurrence ? ts('Successfully processed contribution in recurring series id %1: ', array(1 => $contribution['contribution_recur_id'])) : ts('Successfully processed one-time contribution: ');
+ return $message . $result['auth_result'];
+ }
+ }
+ // Now return the appropriate message.
+ if (empty($result['status'])) {
+ return ts('Failed to process recurring contribution id %1: ', array(1 => $contribution['contribution_recur_id'])) . $result['reasonMessage'];
+ }
+ elseif ($result['contribution_status_id'] == 1) {
+ return ts('Successfully processed recurring contribution in series id %1: ', array(1 => $contribution['contribution_recur_id'])) . $result['auth_result'];
+ }
+ else {
+ // I'm using ACH/EFT or a processor that doesn't complete.
+ return ts('Successfully processed pending recurring contribution in series id %1: ', array(1 => $contribution['contribution_recur_id'])) . $result['auth_result'];
+ }
+}
+
+/**
+ * Function _iats_process_transaction.
+ *
+ * @param $contribution an array of properties of a contribution to be processed
+ * @param $options must include customer code, subtype and iats_domain
+ *
+ * A low-level utility function for triggering a transaction on iATS.
+ */
+function _iats_process_transaction($contribution, $options) {
+ require_once "CRM/iATS/iATSService.php";
+ switch ($options['subtype']) {
+ case 'ACHEFT':
+ $method = 'acheft_with_customer_code';
+ // Will not complete.
+ $contribution_status_id = 2;
+ break;
+
+ default:
+ $method = 'cc_with_customer_code';
+ $contribution_status_id = 1;
+ break;
+ }
+ $credentials = iATS_Service_Request::credentials($contribution['payment_processor'], $contribution['is_test']);
+ $iats_service_params = array('method' => $method, 'type' => 'process', 'iats_domain' => $credentials['domain']);
+ $iats = new iATS_Service_Request($iats_service_params);
+ // Build the request array.
+ $request = array(
+ 'customerCode' => $options['customer_code'],
+ 'invoiceNum' => $contribution['invoice_id'],
+ 'total' => $contribution['total_amount'],
+ );
+ $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
+ // remove the customerIPAddress if it's the internal loopback to prevent
+ // being locked out due to velocity checks
+ if ('127.0.0.1' == $request['customerIPAddress']) {
+ $request['customerIPAddress'] = '';
+ }
+
+ // Make the soap request.
+ $response = $iats->request($credentials, $request);
+ // Process the soap response into a readable result.
+ $result = $iats->result($response);
+ // pass back the anticipated status_id based on the method (i.e. 1 for CC, 2 for ACH/EFT)
+ $result['contribution_status_id'] = $contribution_status_id;
+ return $result;
+}
+
+/**
+ * Function _iats_get_future_start_dates
+ *
+ * @param $start_date a timestamp, only return dates after this.
+ * @param $allow_days an array of allowable days of the month.
+ *
+ * A low-level utility function for triggering a transaction on iATS.
+ */
+function _iats_get_future_monthly_start_dates($start_date, $allow_days) {
+ // Future date options.
+ $start_dates = array();
+ // special handling for today - it means immediately or now.
+ $today = date('Ymd').'030000';
+ // If not set, only allow for the first 28 days of the month.
+ if (max($allow_days) <= 0) {
+ $allow_days = range(1,28);
+ }
+ for ($j = 0; $j < count($allow_days); $j++) {
+ // So I don't get into an infinite loop somehow ..
+ $i = 0;
+ $dp = getdate($start_date);
+ while (($i++ < 60) && !in_array($dp['mday'], $allow_days)) {
+ $start_date += (24 * 60 * 60);
+ $dp = getdate($start_date);
+ }
+ $key = date('Ymd', $start_date).'030000';
+ if ($key == $today) { // special handling
+ $display = ts('Now');
+ $key = ''; // date('YmdHis');
+ }
+ else {
+ $display = strftime('%B %e, %Y', $start_date);
+ }
+ $start_dates[$key] = $display;
+ $start_date += (24 * 60 * 60);
+ }
+ return $start_dates;
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/info.xml b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/info.xml
new file mode 100644
index 00000000..55c5172d
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/info.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<extension key="com.iatspayments.civicrm" type="module">
+ <file>iats</file>
+ <name>iATS Payments</name>
+ <description>iATS Payments Web Services Payment Processor</description>
+ <urls>
+ <url desc="Support">https://github.com/iATSPayments/com.iatspayments.civicrm/issues?state=open</url>
+ <url desc="Installation Instructions">https://github.com/iATSPayments/com.iatspayments.civicrm</url>
+ <url desc="Documentation">https://github.com/iATSPayments/com.iatspayments.civicrm</url>
+ <url desc="Release Notes">https://github.com/iATSPayments/com.iatspayments.civicrm/blob/master/release-notes/1.6.2.md</url>
+ <url desc="Getting Started">https://www.semper-it.com/civicamp-iats-payments-slides</url>
+ </urls>
+ <license>AGPL-3.0</license>
+ <maintainer>
+ <author>Alan Dixon</author>
+ <email>iats@blackflysolutions.ca</email>
+ </maintainer>
+ <releaseDate>2018-08-15</releaseDate>
+ <version>1.6.2</version>
+ <develStage>stable</develStage>
+ <compatibility>
+ <ver>5.0</ver>
+ </compatibility>
+ <comments>This release resolves some 5.x compatibility issues, but should continue to work on 4.7.x. Please see the current release notes linked above for details.
+</comments>
+ <civix>
+ <namespace>CRM/iATS</namespace>
+ </civix>
+</extension>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/contribute_form_search.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/contribute_form_search.js
new file mode 100644
index 00000000..6481f57a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/contribute_form_search.js
@@ -0,0 +1,25 @@
+/*
+ * backend contribute search, individual
+ *
+ */
+
+/*jslint indent: 2 */
+/*global CRM, ts */
+
+cj(function ($) {
+ 'use strict';
+ $('#crm-main-content-wrapper').crmSnippet().on('crmLoad', function(e, data) {
+ var backofficeLinks = (typeof CRM.iatspayments != 'undefined') ? CRM.iatspayments.backofficeLinks : ((typeof CRM.vars != 'undefined') ? ((typeof CRM.vars.iatspayments != 'undefined') ? CRM.vars.iatspayments.backofficeLinks : []) : []);
+ if (0 < backofficeLinks.length
+ && typeof data != 'undefined'
+ && data.title == 'Contributions'
+ && 0 == $('form.CRM_Contribute_Form_Search #help .acheft-backend-link').length
+ && 0 < $('form.CRM_Contribute_Form_Search #help').filter(':visible').length
+ ) {
+ $.each(backofficeLinks, function(index, value) {
+ $('form#Search .action-link a.button').last().after('<a style="color: #F88;" class="button" href="'+value.url+'">'+value.title+'</a>');
+ $('form#Search #help').append('<div class="acheft-backend-link">Click <a href="'+value.url+'">'+value.title+'</a> to process a new ACH/EFT contribution or recurring contribution from this contact.</div>');
+ });
+ }
+ });
+});
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_acheft.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_acheft.js
new file mode 100644
index 00000000..43718b16
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_acheft.js
@@ -0,0 +1,12 @@
+/*jslint indent: 2 */
+/*global CRM, ts */
+
+function iatsACHEFTRefresh() {
+ cj(function ($) {
+ 'use strict';
+ if (0 < $('#iats-direct-debit-extra').length) {
+ /* move my custom fields up where they belong */
+ $('.direct_debit_info-section').prepend($('#iats-direct-debit-extra'));
+ }
+ });
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_cad.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_cad.js
new file mode 100644
index 00000000..c5a8f3a4
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_cad.js
@@ -0,0 +1,58 @@
+/* Custom cleanup/validation of Canadian ACH/EFT */
+/*jslint indent: 2 */
+/*global CRM, ts */
+function iatsACHEFTca() {
+ cj(function ($) {
+ 'use strict';
+ function onlyNumbers(jq) {
+ var myVal = jq.val();
+ jq.val(myVal.replace(/\D/g,''));
+ return jq.val().length;
+ }
+ function iatsSetBankIdenficationNumber() {
+ var bin = $('#cad_bank_number').val() + $('#cad_transit_number').val();
+ // console.log('bin: '+bin);
+ $('#bank_identification_number').val(bin);
+ }
+ /* hide the bank identiication number field */
+ $('.bank_identification_number-section').hide();
+ iatsSetBankIdenficationNumber();
+ $('#bank_account_number').blur(function(eventObj) {
+ onlyNumbers($(this));
+ });
+ $('#cad_bank_number').blur(function(eventObj) {
+ var myCount = onlyNumbers($(this));
+ if (myCount != 3) {
+ $(this).crmError(ts('Your Bank Number requires three digits, use a leading "0" if necessary'));
+ }
+ switch($(this).val()) {
+ case '001': $('#bank_name').val('Bank of Montreal'); break;
+ case '002': $('#bank_name').val('Bank of Nova Scotia'); break;
+ case '003': $('#bank_name').val('Royal Bank of Canada'); break;
+ case '004': $('#bank_name').val('Toronto Dominion Bank'); break;
+ case '006': $('#bank_name').val('National Bank of Canada'); break;
+ case '010': $('#bank_name').val('Canadian Imperial Bank of Commerce'); break;
+ case '016': $('#bank_name').val('HSBC Canada'); break;
+ case '030': $('#bank_name').val('Canadian Western Bank'); break;
+ case '326': $('#bank_name').val('President\'s Choice Financial'); break;
+
+ case '839': // Credit Union Heritage (Nova Scotia)
+ case '879': // Credit Union Central of Manitoba
+ case '889': // Credit Union Central of Saskatchewan
+ case '899': // Credit Union Central Alberta
+ case '809': // Credit Union British Columbia
+ $('#bank_name').val('Credit Union'); break;
+
+ default: $('#bank_name').val(''); break;
+ }
+ iatsSetBankIdenficationNumber();
+ });
+ $('#cad_transit_number').blur(function(eventObj) {
+ var myCount = onlyNumbers($(this));
+ if (myCount != 5) {
+ $(this).crmError(ts('Your Bank Transit Number requires exactly five digits'));
+ }
+ iatsSetBankIdenficationNumber();
+ });
+ });
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_uk.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_uk.js
new file mode 100644
index 00000000..29e54dd4
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dd_uk.js
@@ -0,0 +1,150 @@
+/* custom js for uk dd */
+/*jslint indent: 2 */
+/*global CRM, ts */
+
+function iatsUKDDRefresh() {
+ cj(function ($) {
+ 'use strict';
+ if (0 < $('#iats-direct-debit-gbp-payer-validate').length) {
+ /* move my custom fields around and make it a multistep form experience via javascript */
+ /* move my custom fields up where they belong */
+ var pgDeclaration = $('#iats-direct-debit-gbp-declaration');
+ var pgNonDeclaration = $('.crm-contribution-main-form-block');
+ var pgPayerValidate = $('#billing-payment-block');
+ // var pgPayerValidateHide = $('#billing-payment-block');
+ /* i don't want to show my default civicrm payment notice */
+ $('#payment_notice').hide();
+ /* move some fields around to better flow like the iATS DD samples */
+ $('input[name|="email"]').parents('.crm-section').prependTo('.billing_name_address-section');
+ $('.direct_debit_info-section').before($('#iats-direct-debit-extra'));
+ $('.is_recur-section').after($('#iats-direct-debit-start-date'));
+ $('.crm-contribution-main-form-block').before($('#iats-direct-debit-gbp-declaration'));
+ $('#payer_validate_amend').hide();
+ /* page 1: Declaration */
+ if ($('#payer_validate_declaration').is(':checked')) {
+ pgDeclaration.hide();
+ }
+ else {
+ pgNonDeclaration.hide();
+ }
+ $('#payer_validate_declaration').change(function() {
+ if (this.checked) {
+ pgDeclaration.hide('slow');
+ pgNonDeclaration.show('slow');
+ }
+ else {
+ pgDeclaration.hide('slow');
+ pgNonDeclaration.show('slow');
+ }
+ });
+ /* page 2: Normal CiviCRM form with extra fields */
+ // hide fields that are used for error display before validation
+ $('#iats-direct-debit-gbp-continue .crm-error').hide();
+ // hide the fields that will get validation field lookup values
+ $('#iats-direct-debit-gbp-payer-validate').hide();
+ $('.bank_name-section').hide(); // I don't ask this!
+ /* initiate a payer validation: check for required fields, then do a background POST to retrieve bank info */
+ $('#crm-submit-buttons .crm-button input').click(function( defaultSubmitEvent ) {
+ var inputButton = $(this);
+ inputButton.val('Processing ...').prop('disabled',true);
+ // reset the list of errors
+ $('#payer-validate-required').html('');
+ // the billing address group is all required (except middle name) but doesn't have the class marked
+ $('#Main .billing_name_address-group input:visible, #Main input.required:visible').each(function() {
+ // console.log(this.value.length);
+ if (0 == this.value.length && this.id != 'billing_middle_name') {
+ if ('installments' == this.id) { // todo: check out other exceptions
+ var myLabel = 'Installments';
+ }
+ else {
+ var myLabel = cj.trim($(this).parent('.content').prev('.label').find('label').text().replace('*',''));
+ }
+ if (myLabel.length > 0) { // skip any weird hidden fields (e.g. select2-offscreen class)
+ $('#payer-validate-required').append('<li>' + myLabel + ' is a required field.</li>');
+ }
+ }
+ })
+ // if all pre-validation requirements are met, I can do the sycronous POST to try to get my banking information
+ if (0 == $('#payer-validate-required').html().length) {
+ var validatePayer = {};
+ var startDateStr = $('#payer_validate_start_date').val();
+ var startDate = new Date(startDateStr);
+ validatePayer.beginDate = cj.datepicker.formatDate('yy-mm-dd',startDate);
+ var endDate = startDate;
+ var frequencyInterval = $('input[name=frequency_interval]').val();
+ var frequencyUnit = $('[name="frequency_unit"]').val();
+ var installments = $('input[name="installments"]').val();
+ switch(frequencyUnit) {
+ case 'year':
+ var myYear = endDate.getFullYear() + (frequencyInterval * installments);
+ endDate.setFullYear(myYear);
+ break;
+ case 'month':
+ var myMonth = endDate.getMonth() + (frequencyInterval * installments);
+ endDate.setMonth(myMonth);
+ break;
+ case 'week':
+ var myDay = endDate.getDate() + (frequencyInterval * installments * 7);
+ endDate.setDate(myDay);
+ break;
+ case 'day':
+ var myDay = endDate.getDate() + (frequencyInterval * installments * 1);
+ endDate.setDate(myDay);
+ break;
+ }
+ validatePayer.endDate = endDate.toISOString();
+ validatePayer.firstName = $('#billing_first_name').val();
+ validatePayer.lastName = $('#billing_last_name').val();
+ validatePayer.address = $('input[name|="billing_street_address"]').val();
+ validatePayer.city = $('input[name|="billing_city"]').val();
+ validatePayer.zipCode = $('input[name|="billing_postal_code"]').val();
+ validatePayer.country = $('input[name|="billing_country_id"]').find('selected').text();
+ validatePayer.accountCustomerName = $('#account_holder').val();
+ validatePayer.accountNum = $('#bank_identification_number').val() + $('#bank_account_number').val();
+ validatePayer.email = $('input[name|="email"]').val();
+ validatePayer.ACHEFTReferenceNum = '';
+ validatePayer.companyName = '';
+ validatePayer.type = 'customer';
+ validatePayer.method = 'direct_debit_acheft_payer_validate';
+ validatePayer.payment_processor_id = $('input[name="payment_processor"]').val();
+ var payerValidateUrl = $('input[name="payer_validate_url"]').val();
+ // console.log(payerValidateUrl);
+ // console.log(validatePayer);
+ /* this ajax call has to be sychronous! */
+ cj.ajax({
+ type: 'POST',
+ url: payerValidateUrl,
+ data: validatePayer,
+ dataType: 'json',
+ async: false,
+ success: function(result) {
+ // console.log(result);
+ if (result == null) {
+ $('#payer-validate-required').append('<li>Unexpected Error</li>');
+ }
+ else if ('string' == typeof(result.ACHREFNUM)) {
+ $('#bank_name').val(result.BANK_NAME);
+ $('#payer_validate_address').val(result.BANK_BRANCH + "\n" + result.BANKADDRESS1 + "\n" + result.BANK_CITY + ", " + result.BANK_STATE + "\n" + result.BANK_POSTCODE);
+ $('#payer_validate_reference').val(result.ACHREFNUM);
+ }
+ else {
+ $('#payer-validate-required').append('<li>' + result.reasonMessage + '</li>');
+ }
+ },
+ })
+ .fail(function() {
+ $('#payer-validate-required').append('<li>Unexpected Server Error.</li>');
+ });
+ // console.log('done');
+ }
+ if (0 < $('#payer-validate-required').html().length) {
+ $('#iats-direct-debit-gbp-continue .crm-error').show('slow');
+ inputButton.val('Retry').prop('disabled',false);
+ return false;
+ };
+ inputButton.val('Contribute').prop('disabled',false);
+ return true;
+ }); // end of click handler
+ }
+ });
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dpm.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dpm.js
new file mode 100644
index 00000000..52e1a8cd
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/dpm.js
@@ -0,0 +1,100 @@
+/*
+ * custom js for direct post method
+ *
+ * Intercept the normal submission of sensitive billing data,
+ * submit it in the background via ajax
+ * and include the results for parsing by the payment processor doDirectPayment script
+ * while masking out the credit card information
+ */
+
+/*jslint indent: 2 */
+/*global CRM, ts */
+
+cj(function ($) {
+ 'use strict';
+
+ // hide fields that are used for error display before validation
+ $('#iats-dpm-continue .crm-error').hide();
+ /* initiate an ajax submission of the billing dadta: check for required fields, then do a background POST */
+ $('#crm-submit-buttons .crm-button input').click(function( defaultSubmitEvent ) {
+ var inputButton = $(this);
+ inputButton.val('Processing ...').prop('disabled',true);
+ // reset the list of errors
+ $('#iats-dpm-required').html('');
+ // the payment information group is all required (except the middle name) but doesn't have the class marked
+ $('#Main #payment_information input:visible, #Main #payment_information select:visible, #Main input.required:visible').each(function() {
+ // console.log(this.value.length);
+ if (0 == this.value.length && this.id != 'billing_middle_name') {
+ if ('installments' == this.id) { // todo: check out other exceptions
+ var myLabel = 'Installments';
+ }
+ else {
+ var myLabel = $.trim($(this).parent('.content').prev('.label').find('label').text().replace('*',''));
+ }
+ if (myLabel.length > 0) { // skip any weird hidden fields (e.g. select2-offscreen class)
+ $('#iats-dpm-required').append('<li>' + myLabel + ' is a required field.</li>');
+ }
+ }
+ })
+ // if all pre-validation requirements are met, I can do the sycronous POST to try a submission
+ // TODO: javascript version of Luhn, perhaps http://jquerycreditcardvalidator.com/
+ // console.log($('#iats-dpm-required'));
+ if (0 == $('#iats-dpm-required').html().length) {
+ var dpmURL = (typeof CRM.vars.iatspayments != 'undefined') ? CRM.vars.iatspayments.dpmURL : CRM.iatspayments.dpmURL;
+ console.log(dpmURL);
+ var dpmPOST = {};
+ dpmPOST.IATS_DPM_ProcessID = 'PA0940D765F2BD67BD97B82EFAA4D72BE9';
+ dpmPOST.IATS_DPM_FirstName = $('#billing_first_name').val();
+ dpmPOST.IATS_DPM_LastName = $('#billing_last_name').val();
+ dpmPOST.IATS_DPM_Address = $('input[name|="billing_street_address"]').val();
+ dpmPOST.IATS_DPM_City = $('input[name|="billing_city"]').val();
+ dpmPOST.IATS_DPM_Province = $('select[name|="billing_state_province_id"]').find('selected').text();
+ dpmPOST.IATS_DPM_ZipCode = $('input[name|="billing_postal_code"]').val();
+ dpmPOST.IATS_DPM_Country = $('select[name|="billing_country_id"]').find('selected').text();
+ dpmPOST.IATS_DPM_Email = $('input[name|="email"]').val();
+
+ dpmPOST.IATS_DPM_AccountNumber = $('#credit_card_number').val();
+ var Month = $('#credit_card_exp_date_M').val();
+ var Year = $('#credit_card_exp_date_Y').val();
+ dpmPOST.IATS_DPM_ExpiryDate = Month+'/'+Year.substr(2);
+ dpmPOST.IATS_DPM_CVV2 = $('#cvv2').val();
+ // todo: translate mop values
+ dpmPOST.IATS_DPM_MOP = $('#credit_card_type').val();
+ dpmPOST.IATS_DPM_Amount = $('.contribution_amount-section input:checked').prop('data-amount');
+ console.log(dpmPOST);
+ /* this ajax call has to be sychronous! */
+ $.ajax({
+ type: 'POST',
+ url: dpmURL,
+ data: dpmPOST,
+ dataType: 'json',
+ async: false,
+ success: function(result) {
+ console.log(result);
+ if (result == null) {
+ $('#iats-dpm-required').append('<li>Unexpected Error</li>');
+ }
+ else if ('string' == typeof(result.ACHREFNUM)) {
+ $('#bank_name').val(result.BANK_NAME);
+ $('#payer_validate_address').val(result.BANK_BRANCH + "\n" + result.BANKADDRESS1 + "\n" + result.BANK_CITY + ", " + result.BANK_STATE + "\n" + result.BANK_POSTCODE);
+ $('#payer_validate_reference').val(result.ACHREFNUM);
+ }
+ else {
+ $('#iats-dpm-required').append('<li>' + result.reasonMessage + '</li>');
+ }
+ },
+ })
+ .fail(function() {
+ $('#iats-dpm-required').append('<li>Unexpected Server Error.</li>');
+ });
+ // console.log('done');
+ }
+ if (0 < $('#iats-dpm-required').html().length) {
+ $('#iats-dpm-continue .crm-error').show('slow');
+ inputButton.val('Retry').prop('disabled',false);
+ return false;
+ };
+ inputButton.val('Contribute').prop('disabled',false);
+ return true;
+ }); // end of click handler
+});
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/recur_start.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/recur_start.js
new file mode 100644
index 00000000..96e20dae
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/recur_start.js
@@ -0,0 +1,27 @@
+/* custom js for public selection of future recurring start dates */
+/* only show option when recurring is selected */
+/*jslint indent: 2 */
+/*global CRM, ts */
+
+function iatsRecurStartRefresh() {
+ cj(function ($) {
+ 'use strict';
+ $('.is_recur-section').after($('#iats-recurring-start-date'));
+ cj('input[id="is_recur"]').on('change', function() {
+ toggleRecur();
+ });
+ toggleRecur();
+
+ function toggleRecur( ) {
+ var isRecur = cj('input[id="is_recur"]:checked');
+ if (isRecur.val() > 0) {
+ cj('#iats-recurring-start-date').show().val('');
+ }
+ else {
+ cj('#iats-recurring-start-date').hide();
+ $("#iats-recurring-start-date option:selected").prop("selected", false);
+ $("#iats-recurring-start-date option:first").prop("selected", "selected");
+ }
+ }
+ });
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/subscription.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/subscription.js
new file mode 100644
index 00000000..19d5d7af
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/subscription.js
@@ -0,0 +1,17 @@
+/* custom js for the subscription form */
+
+/*jslint indent: 2 */
+/*global CRM, ts */
+
+cj(function ($) {
+ 'use strict';
+ var dupeSubscriptionFields = (typeof CRM.iatspayments != 'undefined') ? CRM.iatspayments.dupeSubscriptionFields : ((typeof CRM.vars != 'undefined') ? ((typeof CRM.vars.iatspayments != 'undefined') ? CRM.vars.iatspayments.dupeSubscriptionFields : []) : []);
+
+ if (0 < dupeSubscriptionFields.length) {
+ $.each(dupeSubscriptionFields, function(index, value) {
+ $('#contributionrecur-extra tr.'+value).remove();
+ });
+ }
+ $('.crm-recurcontrib-form-block table').append($('#contributionrecur-extra tr'));
+ $('.crm-recurcontrib-form-block table').prepend($('#contributionrecur-info tr'));
+});
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/subscription_view.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/subscription_view.js
new file mode 100644
index 00000000..e7fb1d75
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/subscription_view.js
@@ -0,0 +1,10 @@
+/* custom js for the subscription view page */
+
+/*jslint indent: 2 */
+/*global CRM, ts */
+
+cj(function ($) {
+ 'use strict';
+ $('table.crm-info-panel').append($('#iats-extra tr'));
+});
+
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/swipe.js b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/swipe.js
new file mode 100644
index 00000000..f7fa40d2
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/js/swipe.js
@@ -0,0 +1,45 @@
+/*jslint indent: 2 */
+/*global CRM, ts */
+
+function iatsSWIPERefresh() {
+ cj(function ($) {
+ 'use strict';
+
+ function iatsSetCreditCardNumber() {
+ var bin = $('#encrypted_credit_card_number').val();
+ // console.log('bin: '+bin);
+ if (bin.charAt(0) == '0') {
+ /* if 0 -> IDTech -> prefix = 00|@| */
+ var withprefix = "00|@|"+bin;
+ $('#credit_card_number').val(withprefix);
+ // console.log('withprefix: '+withprefix);
+ }
+ if (bin.charAt(0) == '%') {
+ /* if % -> MagTek -> prefix = 02|@| */
+ var withprefix = "02|@|"+bin;
+ $('#credit_card_number').val(withprefix);
+ // console.log('withprefix: '+withprefix);
+ }
+ }
+
+ function clearField() {
+ var field = $('#encrypted_credit_card_number').val();
+ /* console.log('field: '+field); */
+ if (field == ts('Click here - then swipe.')) {
+ $('#encrypted_credit_card_number').val('');
+ }
+ }
+
+ if (0 < $('#iats-swipe').length) {
+ /* move my custom fields up where they belong */
+ $('#payment_information .credit_card_info-group').prepend(cj('#iats-swipe'));
+ /* hide the number credit card number field */
+ $('.credit_card_number-section').hide();
+ /* hide some ghost fields from a bad template on the front end form */
+ $('.-section').hide();
+ iatsSetCreditCardNumber();
+ var defaultValue = ts('Click here - then swipe.');
+ $('#encrypted_credit_card_number').val(defaultValue).focus(clearField).blur(iatsSetCreditCardNumber);
+ }
+ });
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/phpunit.xml.dist b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/phpunit.xml.dist
new file mode 100644
index 00000000..91371453
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/phpunit.xml.dist
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<phpunit backupGlobals="false" backupStaticAttributes="false" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" syntaxCheck="false" bootstrap="tests/phpunit/bootstrap.php">
+ <testsuites>
+ <testsuite name="iATS Test Suite">
+ <directory>./tests/phpunit</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist>
+ <directory suffix=".php">./</directory>
+ </whitelist>
+ </filter>
+ <listeners>
+ <listener class="Civi\Test\CiviTestListener">
+ <arguments/>
+ </listener>
+ </listeners>
+</phpunit>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.0.md b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.0.md
new file mode 100644
index 00000000..a17f1301
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.0.md
@@ -0,0 +1,38 @@
+# iATS CiviCRM Extension 1.6.0
+
+Released May 2, 2017
+
+The primary goal of this release is to provide a 4.7 compatible version of the iATS payment extension for CiviCRM.
+
+If you only use iATS for one-time credit card payments, then you're not likely to see any issues, but otherwise please see below for a number of oustanding and recent issues related to CiviCRM Core. We strongly recommend you remain updated with the latest 4.7.x release.
+
+## 1. https://issues.civicrm.org/jira/browse/CRM-20401
+
+Cancel/modify URL receipt links are wrong. This affects recurring contributions.
+You can manually edit the message template to fix this.
+
+## 2. https://issues.civicrm.org/jira/browse/CRM-20387
+
+If you enable Sales Tax and Invoicing, then ACHEFT verification will no longer work for any pending payments on contributions that you issue an invoice for.
+
+This version of the extension includes a patched version of the code (linked to the issue above).
+
+## 3. https://issues.civicrm.org/jira/browse/CRM-19508
+
+ACHEFT and SWIPE TPL/fields switching only works when the Payment Processor marked as site default is one of the options on the Contribution/Event form.
+
+ACHEFT by itself only loads the proper TPL/fields if there is a default amount set on the Contribution/Event form.
+
+## 4. https://issues.civicrm.org/jira/browse/CRM-20396
+
+ACHEFT Event registrations (not affecting Contributions) are marked Completed when first processed - as a Result they will never be verified. They are also marked "Credit Card" by Core instead of "Direct Debit.
+
+A fix has been submitted to core and should be in 4.7.20, but you can apply the fix in this PR if you need it immediately: https://github.com/civicrm/civicrm-core/pull/10124
+
+## 5. UK Direct Debit
+
+We have retired the UK direct debit iATS processor, due to lack of interest and use.
+
+## 6. https://github.com/iATSPayments/com.iatspayments.civicrm/issues/197
+
+Back-end recurring contributions using future start dates not implemented.
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.1.md b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.1.md
new file mode 100644
index 00000000..566cc5ff
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.1.md
@@ -0,0 +1,25 @@
+# iATS CiviCRM Extension 1.6.1
+
+Oct 17, 2017
+
+This release is an update to the 1.6.0 (4.7 compatible version) of the iATS payment extension for CiviCRM.
+
+## 1. https://github.com/iATSPayments/com.iatspayments.civicrm/issues/184
+
+This issue may conflate a number of related problems with ACH/EFT verification. In any case, the overall strategy for
+verification of payments has undergone a significant refactoring so that the iATS Journal data is now being stored
+in a raw form locally in CiviCRM, and is available via the CiviCRM reports mechanism.
+
+More notes available here: https://github.com/iATSPayments/com.iatspayments.civicrm/pull/201
+and here: https://github.com/iATSPayments/com.iatspayments.civicrm/wiki/Verification-and-the-Journal
+
+Longer term, this change sets us up for future work to enable better error handling, iATS-managed recurring contributions, and use of the iATS-hosted (or other system) payment pages.
+
+## 2. https://github.com/iATSPayments/com.iatspayments.civicrm/issues/197
+
+Back-end recurring contributions using future start dates have been implemented, and a configuration option is
+provided that enables public forms to use this functionality as well.
+
+## 3. Automated Testing
+
+We've started adding automated tests to this extension!
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.2.md b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.2.md
new file mode 100644
index 00000000..2f5b84db
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/release-notes/1.6.2.md
@@ -0,0 +1,7 @@
+# iATS CiviCRM Extension 1.6.2
+
+July 6, 2018
+
+This release is an update to the 1.6.1 (4.7+ compatible version) of the iATS payment extension for CiviCRM, primarily for compatibility with the latest 5.x CiviCRM series, which introduced some non-critical feature breakage as noted in various issues.
+
+Newly added is a slightly modified version of the detailed contribution report that joins it to iATS journal data. This allows administrators to do reconciliation of CiviCRM contributions against transactions reported from iATS.
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/install.sql b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/install.sql
new file mode 100644
index 00000000..8f1d57b3
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/install.sql
@@ -0,0 +1,111 @@
+-- install sql for iATS Services extension, create a table to hold custom codes
+
+CREATE TABLE `civicrm_iats_customer_codes` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Custom code Id',
+ `customer_code` varchar(255) NOT NULL COMMENT 'Customer code returned from iATS',
+ `ip` varchar(255) DEFAULT NULL COMMENT 'Last IP from which this customer code was accessed or created',
+ `expiry` varchar(4) DEFAULT NULL COMMENT 'CC expiry yymm',
+ `cid` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contact id',
+ `email` varchar(255) DEFAULT NULL COMMENT 'Customer-constituent Email address',
+ `recur_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM recurring_contribution table id',
+ PRIMARY KEY ( `id` ),
+ UNIQUE INDEX (`customer_code`),
+ KEY (`cid`),
+ KEY (`email`),
+ KEY (`recur_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table to store customer codes';
+
+CREATE TABLE `civicrm_iats_request_log` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Request Log Id',
+ `invoice_num` varchar(255) NOT NULL COMMENT 'Invoice number being sent to iATS',
+ `ip` varchar(255) DEFAULT NULL COMMENT 'IP from which this request originated',
+ `cc` varchar(4) DEFAULT NULL COMMENT 'CC last four digits',
+ `customer_code` varchar(255) COMMENT 'Customer code if used',
+ `total` decimal(20,2) DEFAULT NULL COMMENT 'Charge amount request',
+ `request_datetime` datetime COMMENT 'Date time of request',
+ PRIMARY KEY ( `id` ),
+ KEY (`invoice_num`),
+ KEY (`cc`),
+ KEY (`request_datetime`),
+ KEY (`customer_code`),
+ KEY (`total`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table for request log';
+
+CREATE TABLE `civicrm_iats_response_log` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Response Log Id',
+ `invoice_num` varchar(255) NOT NULL COMMENT 'Invoice number sent to iATS',
+ `auth_result` varchar(255) NOT NULL COMMENT 'Authorization string returned from iATS',
+ `remote_id` varchar(255) NOT NULL COMMENT 'iATS-internal transaction id',
+ `response_datetime` datetime COMMENT 'Date time of response',
+ PRIMARY KEY ( `id` ),
+ KEY (`invoice_num`),
+ KEY (`auth_result`),
+ KEY (`remote_id`),
+ KEY (`response_datetime`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table for response log';
+
+CREATE TABLE `civicrm_iats_verify` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Verification Id',
+ `customer_code` varchar(255) NOT NULL COMMENT 'Customer code returned from iATS',
+ `cid` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contact id',
+ `contribution_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contribution table id',
+ `recur_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM recurring_contribution table id',
+ `contribution_status_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM new status id',
+ `verify_datetime` datetime COMMENT 'Date time of verification',
+ `auth_result` varchar(255) COMMENT 'Authorization string from iATS',
+ PRIMARY KEY ( `id` ),
+ KEY (`customer_code`),
+ KEY (`cid`),
+ KEY (`contribution_id`),
+ KEY (`recur_id`),
+ KEY (`auth_result`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table to store verification information';
+
+CREATE TABLE `civicrm_iats_ukdd_validate` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'UK DirectDebit validation Id',
+ `customer_code` varchar(255) NOT NULL COMMENT 'Customer code returned from iATS',
+ `acheft_reference_num` varchar(255) NOT NULL COMMENT 'Reference number returned from iATS',
+ `cid` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contact id',
+ `recur_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM recurring_contribution table id',
+ `validated` int(10) unsigned DEFAULT '0' COMMENT 'Status id of 0 or 1 (after validation)',
+ `validated_datetime` datetime COMMENT 'Date time of validation',
+ `xml` longtext COMMENT 'XML response to initial validation request',
+ PRIMARY KEY ( `id` ),
+ KEY (`customer_code`),
+ KEY (`acheft_reference_num`),
+ KEY (`cid`),
+ KEY (`recur_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table to store UK Direct Debit validation information';
+
+CREATE TABLE `civicrm_iats_journal` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'CiviCRM Journal Id',
+ `iats_id` int unsigned DEFAULT NULL COMMENT 'iATS Journal Id',
+ `tnid` varchar(255) NOT NULL COMMENT 'Transaction ID',
+ `tntyp` varchar(255) NOT NULL COMMENT 'Transaction type: Credit card or ACHEFT',
+ `agt` varchar(255) NOT NULL COMMENT 'Agent',
+ `cstc` varchar(255) NOT NULL COMMENT 'Customer code',
+ `inv` varchar(255) COMMENT 'Invoice Number',
+ `dtm` datetime NOT NULL COMMENT 'DateTime',
+ `amt` decimal(20,2) COMMENT 'Amount',
+ `rst` varchar(255) COMMENT 'Result',
+ `cm` varchar(255) COMMENT 'Comment',
+ `currency` varchar(3) COMMENT 'Currency',
+ `status_id` int(10) unsigned DEFAULT '0' COMMENT 'Status of the payment',
+ `financial_trxn_id` int(10) unsigned DEFAULT '0' COMMENT 'Foreign key into CiviCRM financial trxn table id',
+ `cid` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contact id',
+ `contribution_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contribution table id',
+ `recur_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM recurring_contribution table id',
+ `verify_datetime` datetime COMMENT 'Date time of verification',
+ PRIMARY KEY ( `id` ),
+ UNIQUE KEY (`tnid`),
+ UNIQUE KEY (`iats_id`),
+ KEY (`tnid`),
+ KEY (`tntyp`),
+ KEY (`inv`),
+ KEY (`rst`),
+ KEY (`dtm`),
+ KEY (`financial_trxn_id`),
+ KEY (`contribution_id`),
+ KEY (`verify_datetime`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table to iATS journal transactions imported via the iATSPayments ReportLink.'
+
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/uninstall.sql b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/uninstall.sql
new file mode 100644
index 00000000..a9df21d4
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/uninstall.sql
@@ -0,0 +1,7 @@
+RENAME TABLE `civicrm_iats_customer_codes` TO `backup_iats_customer_codes`;
+DROP TABLE IF EXISTS `civicrm_iats_customer_codes`;
+DROP TABLE IF EXISTS `civicrm_iats_request_log`;
+DROP TABLE IF EXISTS `civicrm_iats_response_log`;
+DROP TABLE IF EXISTS `civicrm_iats_verify`;
+DROP TABLE IF EXISTS `civicrm_iats_ukdd_validate`;
+DROP TABLE IF EXISTS `civicrm_iats_journal`;
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_3_001.sql b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_3_001.sql
new file mode 100644
index 00000000..69fc863c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_3_001.sql
@@ -0,0 +1,15 @@
+CREATE TABLE `civicrm_iats_ukdd_validate` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'UK DirectDebit validation Id',
+ `customer_code` varchar(255) NOT NULL COMMENT 'Customer code returned from iATS',
+ `acheft_reference_num` varchar(255) NOT NULL COMMENT 'Reference number returned from iATS',
+ `cid` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contact id',
+ `recur_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM recurring_contribution table id',
+ `validated` int(10) unsigned DEFAULT '0' COMMENT 'Status id of 0 or 1 (after validation)',
+ `validated_datetime` datetime COMMENT 'Date time of validation',
+ `xml` longtext COMMENT 'XML response to initial validation request',
+ PRIMARY KEY ( `id` ),
+ KEY (`customer_code`),
+ KEY (`acheft_reference_num`),
+ KEY (`cid`),
+ KEY (`recur_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table to store UK Direct Debit validation information';
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_5_003.sql b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_5_003.sql
new file mode 100644
index 00000000..cfdfb8e3
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_5_003.sql
@@ -0,0 +1,3 @@
+UPDATE civicrm_payment_processor_type SET payment_instrument_id = 2 WHERE (class_name = 'Payment_iATSServiceACHEFT' OR class_name = 'Payment_iATSServiceUKDD');
+UPDATE civicrm_payment_processor p INNER JOIN civicrm_payment_processor_type t ON p.payment_processor_type_id = t.id SET p.payment_instrument_id = 2 WHERE (t.class_name = 'Payment_iATSServiceACHEFT' OR t.class_name = 'Payment_iATSServiceUKDD');
+UPDATE civicrm_contribution_recur r INNER JOIN civicrm_payment_processor p ON r.payment_processor_id = p.id INNER JOIN civicrm_payment_processor_type t ON p.payment_processor_type_id = t.id SET r.payment_instrument_id = 2 WHERE (t.class_name = 'Payment_iATSServiceACHEFT' OR t.class_name = 'Payment_iATSServiceUKDD');
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_001.sql b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_001.sql
new file mode 100644
index 00000000..5d534815
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_001.sql
@@ -0,0 +1,2 @@
+ALTER TABLE civicrm_iats_verify ADD COLUMN `auth_result` varchar(255) COMMENT 'Authorization string from iATS';
+ALTER TABLE civicrm_iats_verify ADD INDEX (auth_result);
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_002.sql b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_002.sql
new file mode 100644
index 00000000..bb64d268
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_002.sql
@@ -0,0 +1,31 @@
+CREATE TABLE `civicrm_iats_journal` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'CiviCRM Journal Id',
+ `iats_id` int unsigned DEFAULT NULL COMMENT 'iATS Journal Id',
+ `tnid` varchar(255) NOT NULL COMMENT 'Transaction ID',
+ `tntyp` varchar(255) NOT NULL COMMENT 'Transaction type: Credit card or ACHEFT',
+ `agt` varchar(255) NOT NULL COMMENT 'Agent',
+ `cstc` varchar(255) NOT NULL COMMENT 'Customer code',
+ `inv` varchar(255) COMMENT 'Invoice Number',
+ `dtm` datetime NOT NULL COMMENT 'DateTime',
+ `amt` decimal(20,2) COMMENT 'Amount',
+ `rst` varchar(255) COMMENT 'Result',
+ `cm` varchar(255) COMMENT 'Comment',
+ `currency` varchar(3) COMMENT 'Currency',
+ `status_id` int(10) unsigned DEFAULT '0' COMMENT 'Status of the payment',
+ `financial_trxn_id` int(10) unsigned DEFAULT '0' COMMENT 'Foreign key into CiviCRM financial trxn table id',
+ `cid` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contact id',
+ `contribution_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contribution table id',
+ `recur_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM recurring_contribution table id',
+ `verify_datetime` datetime COMMENT 'Date time of verification',
+ PRIMARY KEY ( `id` ),
+ UNIQUE KEY (`tnid`),
+ UNIQUE KEY (`iats_id`),
+ KEY (`tnid`),
+ KEY (`tntyp`),
+ KEY (`inv`),
+ KEY (`rst`),
+ KEY (`dtm`),
+ KEY (`financial_trxn_id`),
+ KEY (`contribution_id`),
+ KEY (`verify_datetime`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table to iATS journal transactions imported via the iATSPayments ReportLink.'
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_004.sql b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_004.sql
new file mode 100644
index 00000000..6f8d3057
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/sql/upgrade_1_6_004.sql
@@ -0,0 +1 @@
+DELETE FROM civicrm_job where api_action = 'iatsacheftverify';
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDPM.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDPM.tpl
new file mode 100644
index 00000000..bcd3286c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDPM.tpl
@@ -0,0 +1,14 @@
+{*
+ iATS DPM
+ Extra html to hold errors in js validation
+ Requires js/dpm.js to to all it's proper work
+*}
+
+<div id="iats-dpm-continue">
+ <div class="messages crm-error">
+ <div class="icon red-icon alert-icon"></div>
+ {ts}Please fix the following errors in the form fields above:{/ts}
+ <ul id="iats-dpm-required">
+ </ul>
+ </div>
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_CAD.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_CAD.tpl
new file mode 100644
index 00000000..5a4a58ab
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_CAD.tpl
@@ -0,0 +1,36 @@
+{*
+ Extra fields for iats direct debit, template for CAD
+*}
+
+<script type="text/javascript" src="{crmResURL ext=com.iatspayments.civicrm file=js/dd_acheft.js}"></script>
+<script type="text/javascript" src="{crmResURL ext=com.iatspayments.civicrm file=js/dd_cad.js}"></script>
+
+ <div id="iats-direct-debit-extra">
+ <div class="crm-section cad-instructions-section">
+ <div class="label"><em>{ts domain='com.iatspayments.civicrm'}You can find your Transit number, Bank number and Account number by inspecting a cheque.{/ts}</em></div>
+ <div class="content"><img width=500 height=303 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/CDN_cheque_500x.jpg}"></div>
+ <div class="description"><em>{ts domain='com.iatspayments.civicrm'}Please enter them below without any punctuation or spaces.{/ts}</em></div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section cad-transit-number-section">
+ <div class="label">{$form.cad_transit_number.label}</div>
+ <div class="content">{$form.cad_transit_number.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section cad-bank-number-section">
+ <div class="label">{$form.cad_bank_number.label}</div>
+ <div class="content">{$form.cad_bank_number.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section bank-account-type-section">
+ <div class="label">{$form.bank_account_type.label}</div>
+ <div class="content">{$form.bank_account_type.html}</div>
+ <div class="clear"></div>
+ </div>
+ </div>
+{literal}<script type="text/javascript">
+ cj(function ($) {
+ iatsACHEFTRefresh();iatsACHEFTca();
+ });
+</script>
+{/literal}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_GBP.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_GBP.tpl
new file mode 100644
index 00000000..8510bcf7
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_GBP.tpl
@@ -0,0 +1,94 @@
+{*
+ iATS direct debit UK customization
+ Extra fields
+ Requires js/dd_uk.js to to all it's proper work
+*}
+<div id="iats-direct-debit-gbp-declaration">
+ <fieldset class="iats-direct-debit-gbp-declaration">
+ <legend>Declaration</legend>
+ <div class="crm-section">
+ <div class="label">{$form.payer_validate_declaration.label}</div>
+ <div class="content">{$form.payer_validate_declaration.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section">
+ <div class="content"><strong>{ts domain='com.iatspayments.civicrm'}Note: {/ts}</strong>{ts domain='com.iatspayments.civicrm'}All Direct Debits are protected by a guarantee. In future, if there is a change to the date, amount of frequency of your Direct Debit, we will always give you 5 working days notice in advance of your account being debited. In the event of any error, you are entitled to an immediate refund from your Bank of Building Society. You have the right to cancel at any time and this guarantee is offered by all the Banks and Building Societies that accept instructions to pay Direct Debits. A copy of the safeguards under the Direct Debit Guarantee will be sent to you with our confirmation letter.{/ts}
+ </div>
+ <div><br/></div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section">
+ <div class="label">{$form.payer_validate_contact.label}</div>
+ <div class="content"><strong>{ts domain='com.iatspayments.civicrm'}Contact Information: {/ts}</strong>{$form.payer_validate_contact.html}</div>
+ <div class="clear"></div>
+ <div class="content">
+ <img width=166 height=61 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/bacs.png}">
+ <img width=148 height=57 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/direct-debit.jpg}">
+ <img width=134 height=55 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/iats.jpg}">
+</div>
+ </div>
+ </fieldset>
+</div>
+
+<div id="iats-direct-debit-extra">
+ <div class="crm-section gbp-instructions-section">
+ <div class="label"><em>{ts domain='com.iatspayments.civicrm'}You can find your Account Number and Sort Code by inspecting a cheque.{/ts}</em></div>
+ <div class="content"><img width=500 height=303 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/GBP_cheque_500x.jpg}"></div>
+ </div>
+</div>
+<div>
+ <div id="iats-direct-debit-logos"></div>
+ <img width=166 height=61 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/bacs.png}">
+ <img width=148 height=57 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/direct-debit.jpg}">
+ <img width=134 height=55 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/iats.jpg}">
+</div>
+<div id="iats-direct-debit-start-date">
+ <div class="crm-section payer-validate-start-date">
+ <div class="label">{$form.payer_validate_start_date.label}</div>
+ <div class="content">{$form.payer_validate_start_date.html}</div>
+ <div class="content">{ts domain='com.iatspayments.civicrm'}You may select a later start date if you wish.{/ts}</div>
+ <div class="clear"></div>
+ </div>
+</div>
+<div id="iats-direct-debit-gbp-payer-validate">
+ <div class="crm-section payer-validate-address">
+ <div class="label">{$form.payer_validate_address.label}</div>
+ <div class="content">{$form.payer_validate_address.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section payer-validate-service-user-number">
+ <div class="label">{$form.payer_validate_service_user_number.label}</div>
+ <div class="content">{$form.payer_validate_service_user_number.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section payer-validate-reference">
+ <div class="label">{$form.payer_validate_reference.label}</div>
+ <div class="content">{$form.payer_validate_reference.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section payer-validate-instruction">
+ <div class="label">{$form.payer_validate_instruction.label}</div>
+ <div class="content">{$form.payer_validate_instruction.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section payer-validate-date">
+ <div class="label">{$form.payer_validate_date.label}</div>
+ <div class="content">{$form.payer_validate_date.html}</div>
+ <div class="clear"></div>
+ </div>
+ <input name="payer_validate_url" type="hidden" value="{crmURL p='civicrm/iatsjson' q='reset=1'}">
+</div>
+<div id="iats-direct-debit-gbp-continue">
+ <div class="messages crm-error">
+ <div class="icon red-icon alert-icon"></div>
+ {ts}Please fix the following errors in the form fields above:{/ts}
+ <ul id="payer-validate-required">
+ </ul>
+ </div>
+</div>
+{literal}<script type="text/javascript">
+ cj(function ($) {
+ iatsUKDDRefresh();
+ });
+</script>
+{/literal}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_Other.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_Other.tpl
new file mode 100644
index 00000000..286d107e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_Other.tpl
@@ -0,0 +1,6 @@
+{*
+ Extra fields for iats direct debit, template, for unknown currencies
+*}
+ <div id="iats-direct-debit-extra">
+ <div class="description">Your currency is not supported by this iATS Payment processor, proceed at your own risk.</div>
+ </div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_USD.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_USD.tpl
new file mode 100644
index 00000000..a6f0cd2c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_USD.tpl
@@ -0,0 +1,24 @@
+{*
+ Extra fields for iats direct debit, template for USD
+*}
+
+<script type="text/javascript" src="{crmResURL ext=com.iatspayments.civicrm file=js/dd_acheft.js}"></script>
+
+<div id="iats-direct-debit-extra">
+ <div class="crm-section usd-instructions-section">
+ <div class="label"><em>{ts domain='com.iatspayments.civicrm'}You can find your Bank Routing Number and Bank Account number by inspecting a check.{/ts}</em></div>
+ <div class="content"><img width=500 height=303 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/USD_check_500x.jpg}"></div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section bank-account-type-section">
+ <div class="label">{$form.bank_account_type.label}</div>
+ <div class="content">{$form.bank_account_type.html}</div>
+ <div class="clear"></div>
+ </div>
+</div>
+{literal}<script type="text/javascript">
+ cj(function ($) {
+ iatsACHEFTRefresh();
+ });
+</script>
+{/literal}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockRecurringExtra.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockRecurringExtra.tpl
new file mode 100644
index 00000000..049cbe74
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockRecurringExtra.tpl
@@ -0,0 +1,10 @@
+<div id="iats-recurring-start-date">
+ <div class="crm-section recurring-start-date">
+ <div class="label">{$form.receive_date.label}</div>
+ <div class="content">{$form.receive_date.html}
+ <div class="description">{ts domain='com.iatspayments.civicrm'}You may select a later start date if you wish.{/ts}</div>
+ </div>
+ <div class="clear"></div>
+ </div>
+</div>
+{literal}<script type="text/javascript">cj(function ($) { iatsRecurStartRefresh();});</script>{/literal}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockSwipe.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockSwipe.tpl
new file mode 100644
index 00000000..a57ced32
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockSwipe.tpl
@@ -0,0 +1,22 @@
+{*
+ Extra fields for iATS secure SWIPE
+*}
+
+<div id="iats-swipe">
+ <div class="crm-section cad-instructions-section">
+ <div class="label"><em>{ts domain='com.iatspayments.civicrm'}Get ready to SWIPE! Place your cursor in the Encrypted field below and swipe card.{/ts}</em></div>
+ <div class="content"><img width=220 height=220 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/usb_reader.jpg}"></div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section encrypted-credit-card-section">
+ <div class="label">{$form.encrypted_credit_card_number.label}</div>
+ <div class="content">{$form.encrypted_credit_card_number.html}</div>
+ <div class="clear"></div>
+ </div>
+</div>
+{literal}<script type="text/javascript">
+ cj(function ($) {
+ iatsSWIPERefresh();
+ });
+</script>
+{/literal}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/CDN_cheque_500x.jpg b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/CDN_cheque_500x.jpg
new file mode 100644
index 00000000..0a4b0e53
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/CDN_cheque_500x.jpg
Binary files differ
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeConfirmExtra_UKDD.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeConfirmExtra_UKDD.tpl
new file mode 100644
index 00000000..ac0997d2
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeConfirmExtra_UKDD.tpl
@@ -0,0 +1,41 @@
+{*
+ iATS direct debit UK customization
+ Extra fields in Confirmation Screen
+*}
+<div id="iats-direct-debit-gbp-payer-validate">
+ <div class="crm-section payer-validate-address">
+ <div class="label">{$form.payer_validate_address.label}</div>
+ <div class="content">{$form.payer_validate_address.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section payer-validate-service-user-number">
+ <div class="label">{$form.payer_validate_service_user_number.label}</div>
+ <div class="content">{$form.payer_validate_service_user_number.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section payer-validate-reference">
+ <div class="label">{$form.payer_validate_reference.label}</div>
+ <div class="content">{$form.payer_validate_reference.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section payer-validate-instruction">
+ <div class="label">{$form.payer_validate_instruction.label}</div>
+ <div class="content">{$form.payer_validate_instruction.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section start-date">
+ <div class="label">{$form.payer_validate_start_date.label}</div>
+ <div class="content">{$form.payer_validate_start_date.html}</div>
+ <div class="clear"></div>
+ </div>
+ <div class="crm-section payer-validate-date">
+ <div class="label">{$form.payer_validate_date.label}</div>
+ <div class="content">{$form.payer_validate_date.html}</div>
+ <div class="clear"></div>
+ <div>
+ <img width=166 height=61 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/bacs.png}">
+ <img width=148 height=57 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/direct-debit.jpg}">
+ <img width=134 height=55 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/iats.jpg}">
+ </div>
+ </div>
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeThankYouExtra_UKDD.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeThankYouExtra_UKDD.tpl
new file mode 100644
index 00000000..1acedb74
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeThankYouExtra_UKDD.tpl
@@ -0,0 +1,38 @@
+{*
+ iATS direct debit UK customization
+ Extra fields in Thank You Screen
+*}
+<div>
+ <div class="display-block">
+Bank Address: {$form.payer_validate_address.html}<br />
+Service User Number: {$form.payer_validate_service_user_number.html}<br />
+Reference: {$form.payer_validate_reference.html}<br />
+Start Date: {$form.payer_validate_start_date.html}
+Today's Date: {$form.payer_validate_date.html}
+ </div>
+ <h3>Direct Debit Guarantee</h3>
+ <ul>
+ <li>The Guarantee is offered by all banks and building societies that accept instructions to pay Direct Debits</li>
+ <li>If there are any changes to the amount, date or frequency of your Direct Debit the organisation will notify you (normally 10 working days) in advance of your account being debited or as otherwise agreed. If you request the organisation to collect a payment, confirmation of the amount and date will be given to you at the time of the request</li>
+ <li>If an error is made in the payment of your Direct Debit, by the organisation or your bank or building society, you are entitled to a full and immediate refund of the amount paid from your bank or building society</li>
+ <li>If you receive a refund you are not entitled to, you must pay it back when the organisation asks you to</li>
+ <li>You can cancel a Direct Debit at any time by simply contacting your bank or building society. Written confirmation may be required. Please also notify the organisation</li>
+ </ul>
+ <br/>
+ <div class="messages status continue_instructions-section">
+ <p>Please print this page for your records.</p>
+ <div id="printer-friendly">
+ <a title="Print this page." onclick="window.print(); return false;" href="#">
+ <div class="ui-icon ui-icon-print"></div>
+ </a>
+ </div>
+ <div class="clear"></div>
+ </div>
+ <br/>
+ <div class="clear"></div>
+ <div>
+ <img width=166 height=61 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/bacs.png}">
+ <img width=148 height=57 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/direct-debit.jpg}">
+ <img width=134 height=55 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/iats.jpg}">
+ </div>
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributionRecur.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributionRecur.tpl
new file mode 100644
index 00000000..80629a86
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/ContributionRecur.tpl
@@ -0,0 +1,9 @@
+{* more fields on recurring contribution display *}
+<table id="iats-extra">
+<tr><td class="label">Expiry</td>
+ <td class="content">{$expiry}</td></tr>
+<tr><td class="label">Last 4 digits (of original card)</td>
+ <td class="content">{$cc}</td></tr>
+<tr><td class="label">{ts}Card on File{/ts}</td>
+ <td class="content">{$customerLink}</td></tr>
+</table>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSCustomerLink.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSCustomerLink.tpl
new file mode 100644
index 00000000..08f3d8f0
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSCustomerLink.tpl
@@ -0,0 +1,17 @@
+{* HEADER *}
+<div class="crm-submit-buttons">
+{include file="CRM/common/formButtons.tpl" location="top"}
+</div>
+
+{foreach from=$elementNames item=elementName}
+ <div class="crm-section">
+ <div class="label">{$form.$elementName.label}</div>
+ <div class="content">{$form.$elementName.html}</div>
+ <div class="clear"></div>
+ </div>
+{/foreach}
+
+{* FOOTER *}
+<div class="crm-submit-buttons">
+{include file="CRM/common/formButtons.tpl" location="bottom"}
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSOneTimeCharge.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSOneTimeCharge.tpl
new file mode 100644
index 00000000..470ef777
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSOneTimeCharge.tpl
@@ -0,0 +1,22 @@
+{* HEADER *}
+<div class="crm-submit-buttons">
+{include file="CRM/common/formButtons.tpl" location="top"}
+</div>
+
+{foreach from=$elementNames item=elementName}
+ <div class="crm-section">
+ <div class="label">{$form.$elementName.label}</div>
+ <div class="content">{$form.$elementName.html}</div>
+ <div class="clear"></div>
+ </div>
+{/foreach}
+
+ <div>
+ <span>{$form.favorite_color.label}</span>
+ <span>{$form.favorite_color.html}</span>
+ </div>
+
+{* FOOTER *}
+<div class="crm-submit-buttons">
+{include file="CRM/common/formButtons.tpl" location="bottom"}
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IatsSettings.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IatsSettings.tpl
new file mode 100644
index 00000000..8a7f58ee
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IatsSettings.tpl
@@ -0,0 +1,18 @@
+{* HEADER *}
+
+<div class="crm-submit-buttons">
+{include file="CRM/common/formButtons.tpl" location="top"}
+</div>
+
+{foreach from=$elementNames item=elementName}
+ <div class="crm-section">
+ <div class="label">{$form.$elementName.label}</div>
+ <div class="content">{$form.$elementName.html}</div>
+ <div class="clear"></div>
+ </div>
+{/foreach}
+
+{* FOOTER *}
+<div class="crm-submit-buttons">
+{include file="CRM/common/formButtons.tpl" location="bottom"}
+</div>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/ACHEFTVerify.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/ACHEFTVerify.tpl
new file mode 100644
index 00000000..0119590e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/ACHEFTVerify.tpl
@@ -0,0 +1,2 @@
+{* Use the default layout *}
+{include file="CRM/Report/Form.tpl"}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Journal.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Journal.tpl
new file mode 100644
index 00000000..0119590e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Journal.tpl
@@ -0,0 +1,2 @@
+{* Use the default layout *}
+{include file="CRM/Report/Form.tpl"}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Recur.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Recur.tpl
new file mode 100644
index 00000000..0119590e
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Recur.tpl
@@ -0,0 +1,2 @@
+{* Use the default layout *}
+{include file="CRM/Report/Form.tpl"}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/GBP_cheque_500x.jpg b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/GBP_cheque_500x.jpg
new file mode 100644
index 00000000..78ee735c
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/GBP_cheque_500x.jpg
Binary files differ
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Page/IATSCustomerLink.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Page/IATSCustomerLink.tpl
new file mode 100644
index 00000000..528b8ca1
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Page/IATSCustomerLink.tpl
@@ -0,0 +1,15 @@
+<h3>Customer Information</h3>
+<table>
+<tr><th>Label</th><th>Value</th></tr>
+{foreach from=$customer item=custValue key=label}
+<tr><td>{$label}</td><td>{$custValue}</tr>
+{/foreach}
+</table>
+
+<h3>Card Information</h3>
+<table>
+<tr><th>Label</th><th>Value</th></tr>
+{foreach from=$card item=cardValue key=label}
+<tr><td>{$label}</td><td>{$cardValue}</tr>
+{/foreach}
+</table>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Page/iATSAdmin.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Page/iATSAdmin.tpl
new file mode 100644
index 00000000..b88763fc
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Page/iATSAdmin.tpl
@@ -0,0 +1,58 @@
+{if $jobLastRunWarning == 1}
+ <h3>{if $jobOverdue != ''}{ts}Warning!{/ts}{else}{ts}Cron Running{/ts}{/if}</h3>
+ <p>The current time is {$currentTime}</p>
+ <p>Your iATS Payments extension version is {$currentVersion}</p>
+ <p>{ts 1=$jobLastRun}Your iATS Payments cron last ran at %1.{/ts}</p>
+ <p>{if $jobOverdue != ''}<strong style="font-size: 120%">{ts}Your recurring contributions for iATS Payments requires a correctly setup and functioning cron job that runs daily. You need to take action now.{/ts}</strong>{else}{ts}It's all good.{/ts}{/if}</p>
+{/if}
+
+
+<h3>Recent transations using iATS Payments</h3>
+<form method="GET">
+ <fieldset><legend>Filter results</legend>
+ <div><em>Filter your results by any part of the last 4 digits of a Card Number or the Authorization Result:</em></div>
+ <table>
+ <tr>
+ <td>Card Number (last 4 digits): <input size="4" type="text" name="search_cc" value="{$search.cc}"></td>
+ <td>Authorization Result: <input size="11" type="text" name="search_auth_result" value="{$search.auth_result}"></td>
+ <td><input type="submit" value="Filter" class='crm-button'></td>
+ </tr>
+ </table>
+ </fieldset>
+</form>
+
+<table class="iats-report">
+ <caption>Recent transactions with the IATS Payment Processor</caption>
+ <tr>
+ <th>{ts}Invoice{/ts}</th>
+ <th>{ts}Contact{/ts}</th>
+ <th>{ts}Request IP{/ts}</th>
+ <th>{ts}Card Number{/ts}</th>
+ <th>{ts}Total{/ts}</th>
+ <th>{ts}Request DateTime{/ts}</th>
+ <th>{ts}Result{/ts}</th>
+ <th>{ts}Transaction ID{/ts}</th>
+ <th>{ts}Response DateTime{/ts}</th>
+ </tr>
+ {foreach from=$iATSLog item=row}
+ <tr>
+ {if $row.contributionURL != ''}
+ <td><a href="{$row.contributionURL}">{$row.invoice_num}</a></td>
+ {else}
+ <td>{$row.invoice_num}</td>
+ {/if}
+ {if $row.contactURL != ''}
+ <td><a href="{$row.contactURL}">{$row.sort_name}</a></td>
+ {else}
+ <td></td>
+ {/if}
+ <td>{$row.ip}</td>
+ <td>{$row.cc}</td>
+ <td>{$row.total}</td>
+ <td>{$row.request_datetime}</td>
+ <td>{$row.auth_result}</td>
+ <td>{$row.remote_id}</td>
+ <td>{$row.response_datetime}</td>
+ </tr>
+ {/foreach}
+</table>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Subscription.tpl b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Subscription.tpl
new file mode 100644
index 00000000..9f9f32d1
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/Subscription.tpl
@@ -0,0 +1,28 @@
+{*
+ Extra fields for updating the subscription, buried in a fake table so I
+ can move the rows where they belong
+*}
+<table id="contributionrecur-extra">
+<tr class="contribution-status-id"><td class="label">{$form.contribution_status_id.label}</td>
+ <td class="content">{$form.contribution_status_id.html}</td></tr>
+<tr class="payment-processor-id"><td class="label">{$form.payment_processor_id.label}</td>
+ <td class="content">{$form.payment_processor_id.html}</td></tr>
+<tr class="start-date"><td class="label">{$form.start_date.label}</td>
+ <td class="content">{$form.start_date.html}</td></tr>
+<tr class="next-sched-contribution-date"><td class="label">{$form.next_sched_contribution_date.label}</td>
+ <td class="content">{$form.next_sched_contribution_date.html}</td></tr>
+<tr class="is-email-receipt"><td class="label">{$form.is_email_receipt.label}</td>
+ <td class="content">{$form.is_email_receipt.html}</td></tr>
+</table>
+<table id="contributionrecur-info">
+<tr><td class="label">Contact Name</td>
+ <td class="content"><strong>{$form.contact.label}</strong></td></tr>
+<tr><td class="label">Payment Processor</td>
+ <td class="content"><strong>{$form.payment_processor.label}</strong></td></tr>
+<tr><td class="label">Financial Type</td>
+ <td class="content"><strong>{$form.financial_type.label}</strong></td></tr>
+<tr><td class="label">Payment Instrument</td>
+ <td class="content"><strong>{$form.payment_instrument.label}</strong></td></tr>
+<tr><td class="label">Failure Count</td>
+ <td class="content"><strong>{$form.failure_count.label}</strong></td></tr>
+</table>
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/USD_check_500x.jpg b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/USD_check_500x.jpg
new file mode 100644
index 00000000..51842efa
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/USD_check_500x.jpg
Binary files differ
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/bacs.png b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/bacs.png
new file mode 100644
index 00000000..d0e1fe25
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/bacs.png
Binary files differ
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/credit_card_reader.jpg b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/credit_card_reader.jpg
new file mode 100644
index 00000000..891234f2
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/credit_card_reader.jpg
Binary files differ
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/direct-debit.jpg b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/direct-debit.jpg
new file mode 100644
index 00000000..0fc1c6a6
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/direct-debit.jpg
Binary files differ
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/iats.jpg b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/iats.jpg
new file mode 100644
index 00000000..f0929209
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/iats.jpg
Binary files differ
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/usb_reader.jpg b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/usb_reader.jpg
new file mode 100644
index 00000000..fed3ae17
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/templates/CRM/iATS/usb_reader.jpg
Binary files differ
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/BaseTestClass.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/BaseTestClass.php
new file mode 100644
index 00000000..e33a4f9f
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/BaseTestClass.php
@@ -0,0 +1,196 @@
+<?php
+
+use Civi\Test\HeadlessInterface;
+use Civi\Test\HookInterface;
+use Civi\Test\TransactionalInterface;
+
+/**
+ * FIXME - Add test description.
+ *
+ * Tips:
+ * - With HookInterface, you may implement CiviCRM hooks directly in the test class.
+ * Simply create corresponding functions (e.g. "hook_civicrm_post(...)" or similar).
+ * - With TransactionalInterface, any data changes made by setUp() or test****() functions will
+ * rollback automatically -- as long as you don't manipulate schema or truncate tables.
+ * If this test needs to manipulate schema or truncate tables, then either:
+ * a. Do all that using setupHeadless() and Civi\Test.
+ * b. Disable TransactionalInterface, and handle all setup/teardown yourself.
+ */
+abstract class BaseTestClass extends \PHPUnit_Framework_TestCase implements HeadlessInterface, HookInterface, TransactionalInterface {
+ //class BaseTestClass extends \PHPUnit_Framework_TestCase implements HeadlessInterface, HookInterface {
+
+ /**
+ * Configure the headless environment.
+ */
+ public function setUpHeadless() {
+ // Civi\Test has many helpers, like install(), uninstall(), sql(), and sqlFile().
+ // See: https://github.com/civicrm/org.civicrm.testapalooza/blob/master/civi-test.md
+ return \Civi\Test::headless()
+ ->installMe(__DIR__)
+ ->apply();
+ }
+
+ private $_apiversion = 3;
+
+ /**
+ * wrap api functions.
+ * so we can ensure they succeed & throw exceptions without litterering the test with checks
+ *
+ * @param string $entity
+ * @param string $action
+ * @param array $params
+ * @param mixed $checkAgainst
+ * Optional value to check result against, implemented for getvalue,.
+ * getcount, getsingle. Note that for getvalue the type is checked rather than the value
+ * for getsingle the array is compared against an array passed in - the id is not compared (for
+ * better or worse )
+ *
+ * @return array|int
+ */
+ public function callAPISuccess($entity, $action, $params, $checkAgainst = NULL) {
+ $params = array_merge(array(
+ 'version' => $this->_apiversion,
+ 'debug' => 1,
+ ),
+ $params
+ );
+ switch (strtolower($action)) {
+ case 'getvalue':
+ return $this->callAPISuccessGetValue($entity, $params, $checkAgainst);
+
+ case 'getsingle':
+ return $this->callAPISuccessGetSingle($entity, $params, $checkAgainst);
+
+ case 'getcount':
+ return $this->callAPISuccessGetCount($entity, $params, $checkAgainst);
+ }
+ $result = $this->civicrm_api($entity, $action, $params);
+ $this->assertAPISuccess($result, "Failure in api call for $entity $action");
+ return $result;
+ }
+ /**
+ * This function exists to wrap api getValue function & check the result
+ * so we can ensure they succeed & throw exceptions without litterering the test with checks
+ * There is a type check in this
+ *
+ * @param string $entity
+ * @param array $params
+ * @param string $type
+ * Per http://php.net/manual/en/function.gettype.php possible types.
+ * - boolean
+ * - integer
+ * - double
+ * - string
+ * - array
+ * - object
+ *
+ * @return array|int
+ */
+ public function callAPISuccessGetValue($entity, $params, $type = NULL) {
+ $params += array(
+ 'version' => $this->_apiversion,
+ 'debug' => 1,
+ );
+ $result = $this->civicrm_api($entity, 'getvalue', $params);
+ if ($type) {
+ if ($type == 'integer') {
+ // api seems to return integers as strings
+ $this->assertTrue(is_numeric($result), "expected a numeric value but got " . print_r($result, 1));
+ }
+ else {
+ $this->assertType($type, $result, "returned result should have been of type $type but was ");
+ }
+ }
+ return $result;
+ }
+ /**
+ * This function exists to wrap api getValue function & check the result
+ * so we can ensure they succeed & throw exceptions without litterering the test with checks
+ * There is a type check in this
+ * @param string $entity
+ * @param array $params
+ * @param null $count
+ * @throws Exception
+ * @return array|int
+ */
+ public function callAPISuccessGetCount($entity, $params, $count = NULL) {
+ $params += array(
+ 'version' => $this->_apiversion,
+ 'debug' => 1,
+ );
+ $result = $this->civicrm_api($entity, 'getcount', $params);
+ if (!is_int($result) || !empty($result['is_error']) || isset($result['values'])) {
+ throw new Exception('Invalid getcount result : ' . print_r($result, TRUE) . " type :" . gettype($result));
+ }
+ if (is_int($count)) {
+ $this->assertEquals($count, $result, "incorrect count returned from $entity getcount");
+ }
+ return $result;
+ }
+ /**
+ * This function exists to wrap api getsingle function & check the result
+ * so we can ensure they succeed & throw exceptions without litterering the test with checks
+ *
+ * @param string $entity
+ * @param array $params
+ * @param array $checkAgainst
+ * Array to compare result against.
+ * - boolean
+ * - integer
+ * - double
+ * - string
+ * - array
+ * - object
+ *
+ * @throws Exception
+ * @return array|int
+ */
+ public function callAPISuccessGetSingle($entity, $params, $checkAgainst = NULL) {
+ $params += array(
+ 'version' => $this->_apiversion,
+ 'debug' => 1,
+ );
+ $result = $this->civicrm_api($entity, 'getsingle', $params);
+ if (!is_array($result) || !empty($result['is_error']) || isset($result['values'])) {
+ throw new Exception('Invalid getsingle result' . print_r($result, TRUE));
+ }
+ if ($checkAgainst) {
+ // @todo - have gone with the fn that unsets id? should we check id?
+ $this->checkArrayEquals($result, $checkAgainst);
+ }
+ return $result;
+ }
+ /**
+ * Check that api returned 'is_error' => 0.
+ *
+ * @param array $apiResult
+ * Api result.
+ * @param string $prefix
+ * Extra test to add to message.
+ */
+ public function assertAPISuccess($apiResult, $prefix = '') {
+ if (!empty($prefix)) {
+ $prefix .= ': ';
+ }
+ $errorMessage = empty($apiResult['error_message']) ? '' : " " . $apiResult['error_message'];
+ if (!empty($apiResult['debug_information'])) {
+ $errorMessage .= "\n " . print_r($apiResult['debug_information'], TRUE);
+ }
+ if (!empty($apiResult['trace'])) {
+ $errorMessage .= "\n" . print_r($apiResult['trace'], TRUE);
+ }
+ $this->assertEquals(0, $apiResult['is_error'], $prefix . $errorMessage);
+ }
+ /**
+ * A stub for the API interface. This can be overriden by subclasses to change how the API is called.
+ *
+ * @param $entity
+ * @param $action
+ * @param array $params
+ * @return array|int
+ */
+ public function civicrm_api($entity, $action, $params) {
+ return civicrm_api($entity, $action, $params);
+ }
+
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/ContributionIATSTest.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/ContributionIATSTest.php
new file mode 100644
index 00000000..81def34a
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/ContributionIATSTest.php
@@ -0,0 +1,278 @@
+<?php
+
+// KG I need this - BestTestClass does not always autoload in local IDE
+require_once __DIR__ . '/BaseTestClass.php';
+
+use Civi\Test\HeadlessInterface;
+use Civi\Test\HookInterface;
+use Civi\Test\TransactionalInterface;
+
+// KG
+use Civi\Payment\System;
+
+
+/**
+ * FIXME - Add test description.
+ *
+ * Tips:
+ * - With HookInterface, you may implement CiviCRM hooks directly in the test class.
+ * Simply create corresponding functions (e.g. "hook_civicrm_post(...)" or similar).
+ * - With TransactionalInterface, any data changes made by setUp() or test****() functions will
+ * rollback automatically -- as long as you don't manipulate schema or truncate tables.
+ * If this test needs to manipulate schema or truncate tables, then either:
+ * a. Do all that using setupHeadless() and Civi\Test.
+ * b. Disable TransactionalInterface, and handle all setup/teardown yourself.
+ *
+ * @group headless
+ */
+class CRM_iATS_ContributioniATSTest extends BaseTestClass {
+
+ public function setUpHeadless() {
+ // Civi\Test has many helpers, like install(), uninstall(), sql(), and sqlFile().
+ // See: https://github.com/civicrm/org.civicrm.testapalooza/blob/master/civi-test.md
+ return \Civi\Test::headless()
+ ->installMe(__DIR__)
+ ->apply();
+ }
+
+ public function setUp() {
+ $this->_apiversion = 3;
+ parent::setUp();
+ }
+
+ public function tearDown() {
+ parent::tearDown();
+ }
+
+ /**
+ * Test a Credit Card Contribution - one time iATS Credit Card - TEST41 - Backend
+ */
+ public function testIATSCreditCardBackend() {
+
+ $params = array(
+ 'sequential' => 1,
+ 'first_name' => "Can",
+ 'last_name' => "ada",
+ 'contact_type' => "Individual",
+ );
+
+ $individual = $this->callAPISuccess('contact', 'create', $params);
+
+ // Need to create a Payment Processor - iATS Credit Card - TE4188
+ $this->paymentProcessor = $this->iATSCCProcessorCreate();
+
+ $processor = $this->paymentProcessor->getPaymentProcessor();
+ $this->paymentProcessorID = $processor['id'];
+
+ $form = new CRM_Contribute_Form_Contribution();
+ $form->_mode = 'Live';
+
+ $contribution_params = array(
+ 'total_amount' => 1.00,
+ 'financial_type_id' => 1,
+ 'receive_date' => '08/03/2017',
+ 'receive_date_time' => '11:59PM',
+ 'contact_id' => $individual['id'],
+ 'payment_instrument_id' => 1,
+ 'contribution_status_id' => 1,
+ 'credit_card_number' => 4222222222222220,
+ 'cvv2' => 123,
+ 'credit_card_exp_date' => array(
+ 'M' => 12,
+ 'Y' => 2025,
+ ),
+ 'credit_card_type' => 'Visa',
+ 'billing_first_name' => 'Karin',
+ 'billing_middle_name' => '',
+ 'billing_last_name' => 'G',
+ 'billing_street_address-5' => '39 St',
+ 'billing_city-5' => 'Calgary',
+ 'billing_state_province_id-5' => 1031,
+ 'billing_postal_code-5' => 10545,
+ 'billing_country_id-5' => 1228,
+ 'frequency_interval' => 1,
+ 'frequency_unit' => 'month',
+ 'installments' => '',
+ 'hidden_AdditionalDetail' => 1,
+ 'hidden_Premium' => 1,
+ 'receipt_date' => '',
+ 'receipt_date_time' => '',
+ 'payment_processor_id' => $this->paymentProcessorID,
+ 'currency' => 'CAD',
+ 'source' => 'iATS CC TEST88',
+ );
+
+ $form->testSubmit($contribution_params, CRM_Core_Action::ADD);
+
+ $contribution = $this->callAPISuccessGetSingle('Contribution', array(
+ 'contact_id' => $individual['id'],
+ 'contribution_status_id' => 'Completed',
+ ));
+ $this->assertEquals('1.00', $contribution['total_amount']);
+ $this->assertEquals(0, $contribution['non_deductible_amount']);
+
+ // Make sure that we have a Transaction ID and that it contains a : (unique to iATS);
+ $this->assertRegExp('/:/', $contribution['trxn_id']);
+
+ // LineItems; Financial Tables;
+ }
+
+ /**
+ * Test a SWIPE Contribution - one time iATS SWIPE - TEST41 - Backend
+ */
+ public function testIATSSWIPEBackend() {
+
+ $params = array(
+ 'sequential' => 1,
+ 'first_name' => "Can",
+ 'last_name' => "ada",
+ 'contact_type' => "Individual",
+ );
+
+ $individual = $this->callAPISuccess('contact', 'create', $params);
+
+ // Need to create a Payment Processor - iATS SWIPE - TE4188
+ $this->paymentProcessor = $this->iATSSWIPEProcessorCreate();
+
+ $processor = $this->paymentProcessor->getPaymentProcessor();
+ $this->paymentProcessorID = $processor['id'];
+
+ $form = new CRM_Contribute_Form_Contribution();
+ $form->_mode = 'Live';
+
+ $contribution_params = array(
+ 'total_amount' => 2.00,
+ 'financial_type_id' => 1,
+ 'receive_date' => '08/03/2017',
+ 'receive_date_time' => '11:59PM',
+ 'contact_id' => $individual['id'],
+ 'payment_instrument_id' => 1,
+ 'contribution_status_id' => 1,
+ // we have some JS that pre-pends e.g. 00|@| when we have an IDTECH encrypted swiper - not sure how to test that - so pre-pending it here (for now)
+ 'credit_card_number' => '00|@|02B701801F422300039B%*4222********2220^PAYMENTSTESTCARD/IATS^***********************?*;4222********2220=***************?*FED6CC57978E86AD50F2F9ED1F6C5C46DFA701B5AC802A4419DDAC1EE1BC1C12CD18DC31DA214C1D14C40550D3282C01E1F81900A46990876624179BD99164C523C37C0C78797BFDB52B378F47B7E14F39C9D3956F02D53F0E1A4B8774BCD74721F7D1E15BFEF934E9FB6BC38107960572ECC0405546DCF6035E78D7BDCC3A43A5EED1CD223A07AB70232D8A3FC073D3C8170736F266783AFFFF73813900042002705F8303',
+ // cvv2 is not required
+ 'credit_card_exp_date' => array(
+ 'M' => 12,
+ 'Y' => 2025,
+ ),
+ 'credit_card_type' => 'Visa',
+ 'billing_first_name' => 'Karin',
+ 'billing_middle_name' => '',
+ 'billing_last_name' => 'G',
+ 'billing_street_address-5' => '39 St',
+ 'billing_city-5' => 'Calgary',
+ 'billing_state_province_id-5' => 1031,
+ 'billing_postal_code-5' => 10545,
+ 'billing_country_id-5' => 1228,
+ 'frequency_interval' => 1,
+ 'frequency_unit' => 'month',
+ 'installments' => '',
+ 'hidden_AdditionalDetail' => 1,
+ 'hidden_Premium' => 1,
+ 'receipt_date' => '',
+ 'receipt_date_time' => '',
+ 'payment_processor_id' => $this->paymentProcessorID,
+ 'currency' => 'CAD',
+ 'source' => 'iATS SWIPE TEST88',
+ );
+
+ $form->testSubmit($contribution_params, CRM_Core_Action::ADD);
+
+ $contribution = $this->callAPISuccessGetSingle('Contribution', array(
+ 'contact_id' => $individual['id'],
+ 'contribution_status_id' => 'Completed',
+ ));
+ $this->assertEquals('2.00', $contribution['total_amount']);
+ $this->assertEquals(0, $contribution['non_deductible_amount']);
+
+ // Make sure that we have a Transaction ID and that it contains a : (unique to iATS);
+ $this->assertRegExp('/:/', $contribution['trxn_id']);
+
+ // LineItems; Financial Tables;
+ }
+
+
+ /**
+ * Create iATS - TEST41 CC Payment Processor.
+ *
+ * @param array $processorParams
+ *
+ * @return Instance of CC Payment Processor
+ */
+ public function iATSCCProcessorCreate($processorParams = array()) {
+ $paymentProcessorID = $this->processorCreateCC($processorParams);
+ return System::singleton()->getById($paymentProcessorID);
+ }
+
+ /**
+ * Create iATS - TEST41 SWIPE Payment Processor.
+ *
+ * @param array $processorParams
+ *
+ * @return Instance of SWIPE Payment Processor
+ */
+ public function iATSSWIPEProcessorCreate($processorParams = array()) {
+ $paymentProcessorID = $this->processorCreateSWIPE($processorParams);
+ return System::singleton()->getById($paymentProcessorID);
+ }
+
+ /**
+ * Create iATS Credit Card - TEST41 Payment Processor.
+ * Payment Processor Type: 13 is iATS Payments Credit Card
+ *
+ * @return int
+ * Id Payment Processor
+ */
+ public function processorCreateCC($params = array()) {
+ $processorParams = array(
+ 'domain_id' => 1,
+ 'name' => 'iATS Credit Card - TE4188',
+ 'payment_processor_type_id' => 13,
+ 'financial_account_id' => 12,
+ 'is_test' => FALSE,
+ 'is_active' => 1,
+ 'user_name' => 'TE4188',
+ 'password' => 'abcde01',
+ 'url_site' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'url_recur' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'class_name' => 'Payment_iATSService',
+ 'is_recur' => 1,
+ 'sequential' => 1,
+ 'payment_type' => 1,
+ 'payment_instrument_id' => 1,
+ );
+ $processorParams = array_merge($processorParams, $params);
+ $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
+ return $processor['id'];
+ }
+
+ /**
+ * Create iATS SWIPE - TEST41 Payment Processor.
+ * Payment Processor Type: 15 is iATS Payments SWIPE
+ *
+ * @return int
+ * Id Payment Processor
+ */
+ public function processorCreateSWIPE($params = array()) {
+ $processorParams = array(
+ 'domain_id' => 1,
+ 'name' => 'iATS Credit Card - TE4188',
+ 'payment_processor_type_id' => 15,
+ 'financial_account_id' => 12,
+ 'is_test' => FALSE,
+ 'is_active' => 1,
+ 'user_name' => 'TE4188',
+ 'password' => 'abcde01',
+ 'url_site' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'url_recur' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+ 'class_name' => 'Payment_iATSServiceSWIPE',
+ 'is_recur' => 1,
+ 'sequential' => 1,
+ 'payment_type' => 1,
+ 'payment_instrument_id' => 1,
+ );
+ $processorParams = array_merge($processorParams, $params);
+ $processor = $this->callAPISuccess('PaymentProcessor', 'create', $processorParams);
+ return $processor['id'];
+ }
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php
new file mode 100644
index 00000000..5bef1214
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php
@@ -0,0 +1,51 @@
+<?php
+
+ini_set('memory_limit', '2G');
+ini_set('safe_mode', 0);
+eval(cv('php:boot --level=classloader', 'phpcode'));
+
+require_once __DIR__ . '/CRM/iATS/BaseTestClass.php';
+
+/**
+ * Call the "cv" command.
+ *
+ * @param string $cmd
+ * The rest of the command to send.
+ * @param string $decode
+ * Ex: 'json' or 'phpcode'.
+ * @return string
+ * Response output (if the command executed normally).
+ * @throws \RuntimeException
+ * If the command terminates abnormally.
+ */
+function cv($cmd, $decode = 'json') {
+ $cmd = 'cv ' . $cmd;
+ $descriptorSpec = array(0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => STDERR);
+ $oldOutput = getenv('CV_OUTPUT');
+ putenv("CV_OUTPUT=json");
+ $process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__);
+ putenv("CV_OUTPUT=$oldOutput");
+ fclose($pipes[0]);
+ $result = stream_get_contents($pipes[1]);
+ fclose($pipes[1]);
+ if (proc_close($process) !== 0) {
+ throw new RuntimeException("Command failed ($cmd):\n$result");
+ }
+ switch ($decode) {
+ case 'raw':
+ return $result;
+
+ case 'phpcode':
+ // If the last output is /*PHPCODE*/, then we managed to complete execution.
+ if (substr(trim($result), 0, 12) !== "/*BEGINPHP*/" || substr(trim($result), -10) !== "/*ENDPHP*/") {
+ throw new \RuntimeException("Command failed ($cmd):\n$result");
+ }
+ return $result;
+
+ case 'json':
+ return json_decode($result, 1);
+
+ default:
+ throw new RuntimeException("Bad decoder format ($decode)");
+ }
+}
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/xml/Menu/iats.xml b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/xml/Menu/iats.xml
new file mode 100644
index 00000000..6c93d409
--- /dev/null
+++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/iatspayments/xml/Menu/iats.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<menu>
+ <item>
+ <path>civicrm/iATSAdmin</path>
+ <page_callback>CRM_iATS_Page_iATSAdmin</page_callback>
+ <title>iATS Payments Administration</title>
+ <access_arguments>access CiviContribute</access_arguments>
+ </item>
+ <item>
+ <path>civicrm/iatsjson</path>
+ <page_callback>CRM_iATS_Page_json</page_callback>
+ <access_arguments>make online contributions</access_arguments>
+ </item>
+ <item>
+ <path>civicrm/admin/contribute/iatssettings</path>
+ <page_callback>CRM_iATS_Form_IatsSettings</page_callback>
+ <title>iATS Payments Settings</title>
+ <access_arguments>access CiviContribute,administer CiviCRM</access_arguments>
+ </item>
+ <item>
+ <path>civicrm/contact/view/iatscustomerlink</path>
+ <page_callback>CRM_iATS_Page_IATSCustomerLink</page_callback>
+ <title>CustomerLink Information at iATS</title>
+ <access_arguments>access CiviContribute</access_arguments>
+ </item>
+ <item>
+ <path>civicrm/contact/edit/iatscustomerlink</path>
+ <page_callback>CRM_iATS_Form_IATSCustomerLink</page_callback>
+ <title>Edit CustomerLink Information at iATS</title>
+ <access_arguments>access CiviContribute</access_arguments>
+ </item>
+ <item>
+ <path>civicrm/contact/iatsprocesslink</path>
+ <page_callback>CRM_iATS_Form_IATSOneTimeCharge</page_callback>
+ <title>IATSOneTimeCharge</title>
+ <access_arguments>access CiviCRM</access_arguments>
+ </item>
+</menu>