From 9f043270a463f198547d6d9f3d060ee234b48443 Mon Sep 17 00:00:00 2001
From: Kevin Cristiano <kcristiano@tadpole.cc>
Date: Sat, 11 Aug 2018 19:24:41 -0400
Subject: [PATCH] civicrm: 5.4.0 release

---
 assets/templates/civicrm.shortcode.php        |    2 +-
 civicrm.php                                   |   11 +-
 civicrm/CONTRIBUTORS.txt                      |   25 +-
 civicrm/CRM/ACL/BAO/ACL.php                   |    2 +-
 civicrm/CRM/Activity/BAO/Activity.php         |   10 +-
 civicrm/CRM/Activity/BAO/Query.php            |   21 +-
 civicrm/CRM/Activity/Form/Task.php            |   36 +-
 civicrm/CRM/Admin/Form/Job.php                |    2 +-
 civicrm/CRM/Admin/Form/LocationType.php       |    2 +-
 civicrm/CRM/Admin/Form/OptionGroup.php        |   12 +-
 .../CRM/Admin/Form/PaymentProcessorType.php   |    2 +-
 civicrm/CRM/Admin/Form/ScheduleReminders.php  |   91 +-
 civicrm/CRM/Admin/Form/Setting.php            |    1 +
 .../Form/Setting/UpdateConfigBackend.php      |    1 +
 civicrm/CRM/Admin/Page/Job.php                |   20 +
 civicrm/CRM/Batch/BAO/Batch.php               |    2 +
 civicrm/CRM/Campaign/BAO/Petition.php         |    2 +-
 civicrm/CRM/Campaign/Form/Task.php            |   30 +-
 civicrm/CRM/Case/BAO/Case.php                 |   16 +-
 civicrm/CRM/Case/BAO/CaseType.php             |    8 +-
 .../Case/Form/Activity/ChangeCaseStatus.php   |    2 +-
 civicrm/CRM/Case/Form/ActivityView.php        |   32 +
 civicrm/CRM/Case/Form/Task.php                |    9 +
 civicrm/CRM/Case/Task.php                     |    2 +-
 civicrm/CRM/Case/XMLProcessor/Process.php     |  157 +++
 civicrm/CRM/Contact/BAO/Contact.php           |    4 -
 civicrm/CRM/Contact/BAO/ContactType.php       |    1 +
 civicrm/CRM/Contact/BAO/Group.php             |   13 +-
 civicrm/CRM/Contact/BAO/Query.php             |    8 +-
 civicrm/CRM/Contact/Form/Contact.php          |    2 +-
 civicrm/CRM/Contact/Form/Search/Builder.php   |   18 +-
 civicrm/CRM/Contact/Form/Search/Criteria.php  |   75 +-
 .../Form/Search/Custom/ContribSYBNT.php       |    6 +-
 .../Contact/Form/Search/Custom/DateAdded.php  |   59 +-
 civicrm/CRM/Contact/Form/Task.php             |   42 +-
 civicrm/CRM/Contact/Import/Parser.php         |    3 -
 civicrm/CRM/Contact/Task.php                  |   21 +-
 civicrm/CRM/Contribute/BAO/Contribution.php   |   65 +-
 .../CRM/Contribute/BAO/Contribution/Utils.php |    4 +-
 .../CRM/Contribute/BAO/ContributionRecur.php  |   36 +-
 .../FutureContributionPageException.php       |   25 +
 .../PastContributionPageException.php         |   25 +
 .../Contribute/Form/CancelSubscription.php    |    1 -
 .../Contribute/Form/Contribution/Confirm.php  |   33 +-
 .../CRM/Contribute/Form/Contribution/Main.php |    3 +
 .../CRM/Contribute/Form/ContributionBase.php  |   17 +-
 .../CRM/Contribute/Form/ContributionPage.php  |   13 +-
 .../Form/ContributionPage/Settings.php        |   13 +-
 .../Form/ContributionPage/Widget.php          |   18 +-
 civicrm/CRM/Contribute/Form/Task.php          |   38 +-
 civicrm/CRM/Contribute/Page/Tab.php           |  119 +-
 civicrm/CRM/Core/BAO/Cache.php                |  171 ++-
 civicrm/CRM/Core/BAO/ConfigSetting.php        |    1 +
 civicrm/CRM/Core/BAO/CustomField.php          |   92 +-
 civicrm/CRM/Core/BAO/CustomQuery.php          |    2 +-
 civicrm/CRM/Core/BAO/CustomValue.php          |    7 +-
 civicrm/CRM/Core/BAO/Domain.php               |    8 +
 civicrm/CRM/Core/BAO/Job.php                  |   21 +
 civicrm/CRM/Core/BAO/OptionGroup.php          |    6 -
 civicrm/CRM/Core/BAO/OptionValue.php          |   15 +-
 civicrm/CRM/Core/BAO/RecurringEntity.php      |    1 -
 civicrm/CRM/Core/Config.php                   |    7 +-
 civicrm/CRM/Core/DAO.php                      |   73 ++
 civicrm/CRM/Core/DAO/CustomField.php          |    7 +-
 civicrm/CRM/Core/DAO/UFGroup.php              |   20 +-
 civicrm/CRM/Core/Form.php                     |   25 +-
 civicrm/CRM/Core/Form/Task.php                |   59 +-
 civicrm/CRM/Core/Invoke.php                   |    2 +-
 civicrm/CRM/Core/JobManager.php               |    3 +
 civicrm/CRM/Core/OptionGroup.php              |    4 +-
 civicrm/CRM/Core/Payment.php                  |   17 +-
 civicrm/CRM/Core/Payment/AuthorizeNetIPN.php  |    2 +-
 civicrm/CRM/Core/Payment/Form.php             |   50 +-
 civicrm/CRM/Core/Payment/PayPalIPN.php        |  139 +-
 civicrm/CRM/Core/Payment/PayPalImpl.php       |   30 +-
 civicrm/CRM/Core/Payment/PayPalProIPN.php     |  137 +-
 civicrm/CRM/Core/Payment/PayflowPro.php       |   81 --
 civicrm/CRM/Core/Permission.php               |    3 +
 civicrm/CRM/Core/PseudoConstant.php           |    2 +-
 civicrm/CRM/Core/Session.php                  |    2 +-
 civicrm/CRM/Cxn/CiviCxnHttp.php               |   11 +-
 civicrm/CRM/Dedupe/Merger.php                 |   54 +-
 .../CRM/Event/Form/Registration/Confirm.php   |    3 +-
 civicrm/CRM/Event/Form/Task.php               |   30 +-
 civicrm/CRM/Export/BAO/Export.php             |  294 +++--
 civicrm/CRM/Export/Form/Select.php            |   60 +-
 civicrm/CRM/Export/Form/Select/Case.php       |   53 +
 civicrm/CRM/Extension/Mapper.php              |    6 +-
 civicrm/CRM/Financial/Page/AJAX.php           |    2 +
 civicrm/CRM/Friend/BAO/Friend.php             |  120 +-
 civicrm/CRM/Friend/Form.php                   |   11 +-
 civicrm/CRM/Grant/Form/Task.php               |   32 +-
 civicrm/CRM/Group/Form/Edit.php               |    6 +-
 civicrm/CRM/Group/Form/Search.php             |   13 +-
 civicrm/CRM/Group/Page/AJAX.php               |    1 +
 civicrm/CRM/Logging/Schema.php                |    2 +-
 civicrm/CRM/Mailing/BAO/Mailing.php           |    2 +-
 civicrm/CRM/Mailing/BAO/MailingJob.php        |   56 +-
 civicrm/CRM/Mailing/Event/BAO/Confirm.php     |    8 +-
 civicrm/CRM/Mailing/Event/BAO/Reply.php       |   12 +-
 civicrm/CRM/Mailing/Event/BAO/Resubscribe.php |    8 +-
 civicrm/CRM/Mailing/Event/BAO/Subscribe.php   |    2 +-
 civicrm/CRM/Mailing/Event/BAO/Unsubscribe.php |    6 +-
 civicrm/CRM/Mailing/Form/Browse.php           |    1 +
 civicrm/CRM/Mailing/Form/Search.php           |    2 +-
 civicrm/CRM/Mailing/Form/Task.php             |   29 +-
 civicrm/CRM/Mailing/Page/Browse.php           |   17 +
 civicrm/CRM/Mailing/Selector/Browse.php       |   20 +-
 civicrm/CRM/Member/BAO/Membership.php         |    4 +-
 civicrm/CRM/Member/BAO/MembershipType.php     |   60 +-
 civicrm/CRM/Member/Form/Task.php              |   37 +-
 .../Member/Page/RecurringContributions.php    |    6 +-
 civicrm/CRM/Member/Task.php                   |   10 +
 civicrm/CRM/Pledge/Form/Task.php              |   29 +-
 civicrm/CRM/Profile/Form/Edit.php             |    1 +
 civicrm/CRM/Report/BAO/ReportInstance.php     |   33 +-
 civicrm/CRM/Report/Form.php                   |   35 +-
 .../Report/Form/Contribute/Bookkeeping.php    |    9 +
 civicrm/CRM/Report/Form/Contribute/Detail.php |  163 ++-
 .../CRM/Report/Form/Contribute/History.php    |    2 +
 civicrm/CRM/Report/Form/Contribute/Lybunt.php |    2 +-
 civicrm/CRM/Report/Info.php                   |    4 +
 civicrm/CRM/SMS/Form/Provider.php             |    2 +-
 civicrm/CRM/Tag/Form/Tag.php                  |    2 +-
 civicrm/CRM/UF/Form/AdvanceSetting.php        |    2 +
 civicrm/CRM/UF/Form/Group.php                 |    1 +
 civicrm/CRM/Upgrade/Form.php                  |    1 +
 civicrm/CRM/Upgrade/Incremental/Base.php      |   12 +
 civicrm/CRM/Upgrade/Incremental/General.php   |   26 +-
 .../Upgrade/Incremental/MessageTemplates.php  |  155 +++
 .../CRM/Upgrade/Incremental/php/FiveFour.php  |  137 ++
 .../Upgrade/Incremental/sql/5.3.0.mysql.tpl   |   18 -
 .../Upgrade/Incremental/sql/5.3.1.mysql.tpl   |   19 -
 .../Upgrade/Incremental/sql/5.3.2.mysql.tpl   |    1 -
 .../Incremental/sql/5.3.alpha1.mysql.tpl      |   17 +
 .../Upgrade/Incremental/sql/5.4.0.mysql.tpl   |    1 +
 .../Incremental/sql/5.4.alpha1.mysql.tpl      |   13 +
 .../Incremental/sql/5.4.beta1.mysql.tpl       |    1 +
 civicrm/CRM/Utils/Cache.php                   |   32 +-
 civicrm/CRM/Utils/Cache/APCcache.php          |   43 +-
 civicrm/CRM/Utils/Cache/ArrayCache.php        |   47 +-
 civicrm/CRM/Utils/Cache/CacheException.php    |   36 +
 civicrm/CRM/Utils/Cache/Interface.php         |   72 +-
 .../Utils/Cache/InvalidArgumentException.php  |   36 +
 civicrm/CRM/Utils/Cache/Memcache.php          |   81 +-
 civicrm/CRM/Utils/Cache/Memcached.php         |  127 +-
 civicrm/CRM/Utils/Cache/NaiveHasTrait.php     |   49 +
 .../CRM/Utils/Cache/NaiveMultipleTrait.php    |  121 ++
 civicrm/CRM/Utils/Cache/NoCache.php           |   15 +-
 civicrm/CRM/Utils/Cache/Redis.php             |   52 +-
 civicrm/CRM/Utils/Cache/SerializeCache.php    |   32 +-
 civicrm/CRM/Utils/Cache/SqlGroup.php          |  138 +-
 civicrm/CRM/Utils/Check.php                   |    2 +-
 civicrm/CRM/Utils/Date.php                    |   52 +
 civicrm/CRM/Utils/DeprecatedUtils.php         |    2 +-
 civicrm/CRM/Utils/Hook.php                    |   32 +
 civicrm/CRM/Utils/Migrate/Import.php          |    9 +
 civicrm/CRM/Utils/SQL/TempTable.php           |  272 ++++
 civicrm/CRM/Utils/System.php                  |   17 +-
 civicrm/Civi.php                              |   11 +-
 civicrm/Civi/Angular/Manager.php              |    2 +-
 civicrm/Civi/Core/Container.php               |   14 +-
 civicrm/Civi/Core/SettingsManager.php         |    2 +-
 civicrm/ang/crmCaseType.js                    |  163 ++-
 civicrm/ang/crmCaseType/timelineTable.html    |   44 +-
 civicrm/ang/crmMailing/BlockPreview.html      |    4 +-
 .../ang/crmMailing/EditMailingCtrl/2step.html |    5 +-
 .../crmMailing/EditMailingCtrl/unified.html   |    5 +-
 .../crmMailing/EditMailingCtrl/unified2.html  |    5 +-
 .../crmMailing/EditMailingCtrl/wizard.html    |    5 +-
 .../crmMailing/EditMailingCtrl/workflow.html  |    7 +-
 civicrm/ang/crmMailing/Recipients.js          |   17 +-
 civicrm/ang/crmUi/wizard.html                 |    4 +-
 civicrm/api/v3/Case.php                       |   75 +-
 civicrm/api/v3/ContributionRecur.php          |    2 +-
 civicrm/api/v3/Job.php                        |   40 +-
 civicrm/api/v3/Membership.php                 |   26 +-
 civicrm/api/v3/ReportTemplate.php             |    2 +
 civicrm/api/v3/examples/Setting/GetFields.php |   15 -
 civicrm/api/v3/utils.php                      |   36 +-
 .../bower_components/jquery-ui/.bower.json    |    2 +-
 civicrm/civicrm-version.php                   |    2 +-
 civicrm/composer.json                         |    3 +-
 civicrm/composer.lock                         |   50 +-
 civicrm/css/searchForm.css                    |   29 +
 civicrm/ext/iatspayments/iATS_4.4.14.diff     |   49 +
 civicrm/ext/iatspayments/iATS_4.5.8.diff      |   37 +
 civicrm/js/crm.ajax.js                        |    2 +-
 .../packages/Cache/IntegrationTests/LICENSE   |   22 +
 .../LegacySimpleCacheTest.php                 |  757 +++++++++++
 .../packages/Cache/IntegrationTests/README.md |    7 +
 civicrm/packages/DB.php                       |    2 +-
 civicrm/packages/DB/common.php                |    6 +-
 civicrm/packages/HTML/QuickForm/date.php      |   19 +
 civicrm/packages/Mail/mime.php                | 1135 ++++++++++-------
 civicrm/packages/Mail/mimeDecode.php          |  323 ++++-
 civicrm/packages/Mail/mimePart.php            |  642 +++++-----
 civicrm/release-notes.md                      |   30 +
 civicrm/release-notes/5.4.0.md                |  667 ++++++++++
 civicrm/settings/Core.setting.php             |   15 -
 civicrm/sql/civicrm.mysql                     |    3 +-
 civicrm/sql/civicrm_data.mysql                |   48 +-
 civicrm/sql/civicrm_generated.mysql           |    2 +-
 civicrm/sql/civicrm_sample_custom_data.mysql  |    6 +-
 .../templates/CRM/Admin/Page/APIExplorer.js   |    3 +-
 .../templates/CRM/Admin/Page/OptionGroup.tpl  |   31 +-
 .../CRM/Contact/Form/Search/Builder.js        |    9 +-
 .../Contact/Form/Search/Criteria/Basic.tpl    |  193 +--
 .../Form/Search/Criteria/Fields/group.tpl     |   49 +
 .../Fields/preferred_communication_method.tpl |   10 +
 .../Search/Criteria/Fields/privacy_toggle.tpl |   28 +
 .../Form/Search/Criteria/Fields/tag_set.tpl   |    5 +
 .../Form/ContributionPage/Settings.tpl        |    8 +-
 .../Form/ContributionPage/Widget.tpl          |    5 +-
 civicrm/templates/CRM/Contribute/Page/Tab.tpl |  106 +-
 .../Form/Registration/EventInfoBlock.tpl      |    6 +-
 .../templates/CRM/Event/Page/EventInfo.tpl    |    8 +-
 civicrm/templates/CRM/Group/Form/Search.tpl   |   79 +-
 .../CRM/Member/Form/MembershipType.tpl        |    2 +-
 .../templates/CRM/Profile/Form/Dynamic.tpl    |   18 +-
 .../templates/CRM/UF/Form/AdvanceSetting.tpl  |   19 +-
 civicrm/templates/CRM/UF/Form/Group.hlp       |   11 +-
 .../CRM/common/civicrm.settings.php.template  |    8 +
 civicrm/vendor/autoload.php                   |    2 +-
 civicrm/vendor/composer/autoload_psr4.php     |    1 +
 civicrm/vendor/composer/autoload_real.php     |   14 +-
 civicrm/vendor/composer/autoload_static.php   |   15 +-
 civicrm/vendor/composer/installed.json        |   50 +
 civicrm/vendor/psr/simple-cache/.editorconfig |   12 +
 civicrm/vendor/psr/simple-cache/LICENSE.md    |   21 +
 civicrm/vendor/psr/simple-cache/README.md     |    8 +
 civicrm/vendor/psr/simple-cache/composer.json |   25 +
 .../psr/simple-cache/src/CacheException.php   |   10 +
 .../psr/simple-cache/src/CacheInterface.php   |  114 ++
 .../src/InvalidArgumentException.php          |   13 +
 civicrm/xml/schema/Core/CustomField.xml       |    5 +
 civicrm/xml/schema/Core/UFGroup.xml           |    8 +
 .../contribution_online_receipt_html.tpl      |    2 +-
 .../contribution_online_receipt_text.tpl      |    3 +-
 .../event_online_receipt_html.tpl             |    4 +-
 .../event_online_receipt_subject.tpl          |    2 +-
 .../event_online_receipt_text.tpl             |    5 +-
 .../membership_online_receipt_html.tpl        |    2 +-
 .../membership_online_receipt_text.tpl        |    1 +
 civicrm/xml/version.xml                       |    2 +-
 includes/civicrm.basepage.php                 |    2 +-
 includes/civicrm.shortcodes.modal.php         |    2 +-
 includes/civicrm.shortcodes.php               |    2 +-
 includes/civicrm.users.php                    |    2 +-
 languages/civicrm.pot                         |    2 +-
 uninstall.php                                 |    2 +-
 wp-cli/civicrm.php                            |    8 +-
 252 files changed, 7497 insertions(+), 2860 deletions(-)
 create mode 100644 civicrm/CRM/Contribute/Exception/FutureContributionPageException.php
 create mode 100644 civicrm/CRM/Contribute/Exception/PastContributionPageException.php
 create mode 100644 civicrm/CRM/Export/Form/Select/Case.php
 create mode 100644 civicrm/CRM/Upgrade/Incremental/MessageTemplates.php
 create mode 100644 civicrm/CRM/Upgrade/Incremental/php/FiveFour.php
 delete mode 100644 civicrm/CRM/Upgrade/Incremental/sql/5.3.0.mysql.tpl
 delete mode 100644 civicrm/CRM/Upgrade/Incremental/sql/5.3.1.mysql.tpl
 delete mode 100644 civicrm/CRM/Upgrade/Incremental/sql/5.3.2.mysql.tpl
 create mode 100644 civicrm/CRM/Upgrade/Incremental/sql/5.4.0.mysql.tpl
 create mode 100644 civicrm/CRM/Upgrade/Incremental/sql/5.4.alpha1.mysql.tpl
 create mode 100644 civicrm/CRM/Upgrade/Incremental/sql/5.4.beta1.mysql.tpl
 create mode 100644 civicrm/CRM/Utils/Cache/CacheException.php
 create mode 100644 civicrm/CRM/Utils/Cache/InvalidArgumentException.php
 create mode 100644 civicrm/CRM/Utils/Cache/NaiveHasTrait.php
 create mode 100644 civicrm/CRM/Utils/Cache/NaiveMultipleTrait.php
 create mode 100644 civicrm/CRM/Utils/SQL/TempTable.php
 create mode 100644 civicrm/ext/iatspayments/iATS_4.4.14.diff
 create mode 100644 civicrm/ext/iatspayments/iATS_4.5.8.diff
 create mode 100644 civicrm/packages/Cache/IntegrationTests/LICENSE
 create mode 100644 civicrm/packages/Cache/IntegrationTests/LegacySimpleCacheTest.php
 create mode 100644 civicrm/packages/Cache/IntegrationTests/README.md
 create mode 100644 civicrm/release-notes/5.4.0.md
 create mode 100644 civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/group.tpl
 create mode 100644 civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/preferred_communication_method.tpl
 create mode 100644 civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/privacy_toggle.tpl
 create mode 100644 civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/tag_set.tpl
 create mode 100644 civicrm/vendor/psr/simple-cache/.editorconfig
 create mode 100644 civicrm/vendor/psr/simple-cache/LICENSE.md
 create mode 100644 civicrm/vendor/psr/simple-cache/README.md
 create mode 100644 civicrm/vendor/psr/simple-cache/composer.json
 create mode 100644 civicrm/vendor/psr/simple-cache/src/CacheException.php
 create mode 100644 civicrm/vendor/psr/simple-cache/src/CacheInterface.php
 create mode 100644 civicrm/vendor/psr/simple-cache/src/InvalidArgumentException.php

diff --git a/assets/templates/civicrm.shortcode.php b/assets/templates/civicrm.shortcode.php
index 02f45e08df..c590390b01 100644
--- a/assets/templates/civicrm.shortcode.php
+++ b/assets/templates/civicrm.shortcode.php
@@ -2,7 +2,7 @@
 
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 5                                                  |
+ | CiviCRM version 4.7                                                |
  +--------------------------------------------------------------------+
  | Copyright CiviCRM LLC (c) 2004-2018                                |
  +--------------------------------------------------------------------+
diff --git a/civicrm.php b/civicrm.php
index 9f5d00f363..05a481b92f 100644
--- a/civicrm.php
+++ b/civicrm.php
@@ -2,11 +2,10 @@
 /*
 Plugin Name: CiviCRM
 Description: CiviCRM - Growing and Sustaining Relationships
-Version: 5.3.2
+Version: 5.4.0
 Author: CiviCRM LLC
 Author URI: https://civicrm.org/
 Plugin URI: https://wiki.civicrm.org/confluence/display/CRMDOC/Installing+CiviCRM+for+WordPress
-GitLab Plugin URI: https://develop.tadpole.cc/plugins/civicrm
 License: AGPL3
 Text Domain: civicrm
 Domain Path: /languages
@@ -15,7 +14,7 @@ Domain Path: /languages
 
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 5                                                  |
+ | CiviCRM version 4.7                                                |
  +--------------------------------------------------------------------+
  | Copyright CiviCRM LLC (c) 2004-2018                                |
  +--------------------------------------------------------------------+
@@ -71,7 +70,7 @@ if ( ! defined( 'ABSPATH' ) ) exit;
 
 
 // set version here: when it changes, will force JS to reload
-define( 'CIVICRM_PLUGIN_VERSION', '5' );
+define( 'CIVICRM_PLUGIN_VERSION', '4.7' );
 
 // store reference to this file
 if (!defined('CIVICRM_PLUGIN_FILE')) {
@@ -309,9 +308,7 @@ class CiviCRM_For_WordPress {
     if ( $this->civicrm_in_wordpress() ) {
       // this is required for AJAX calls in WordPress admin
       $_GET['noheader'] = TRUE;
-    }
-
-    if ( !CIVICRM_INSTALLED && !$this->civicrm_in_wordpress() ) {
+    } else {
       $_GET['civicrm_install_type'] = 'wordpress';
     }
 
diff --git a/civicrm/CONTRIBUTORS.txt b/civicrm/CONTRIBUTORS.txt
index 60a8f1ac4e..6af5bf8853 100644
--- a/civicrm/CONTRIBUTORS.txt
+++ b/civicrm/CONTRIBUTORS.txt
@@ -9,17 +9,28 @@ CiviCRM - Coleman Watts, Tim Otten
 AGH Strategies - Alice Frumin, Andrew Hunt, Eli Lisseck
 Agileware - Alok Patel, Francis Whittle, Justin Freeman
 Andrew Thompson
+applicado
 Australian Greens - Seamus Lee
-CiviDesk - Yashodha Chaku
-CompuCorp - Michael Devery, Mukesh Ram, Omar Abu Hussein, René Olivo, Vinu
-    Varshith Sekar
-Coop SymbioTIC - Samuel Vanhove
+Bastien Ho
+Blackfly Solutions - Alan Dixon
+Caltha - Tomasz Pietrzkowski
+CEDC - Laryn Kragt Bakker
+Chris Burgess
+CiviCoop - Jaap Jansma
+CiviDesk - Sunil Pawar, Yashodha Chaku
+CompuCorp - Camilo Rodriguez, Davi Alexandre, Debarshi Bhaumik, Michael Devery,
+    Mukesh Ram, Omar Abu Hussein, René Olivo, Vinu Varshith Sekar
+Coop SymbioTIC - Mathieu Lutfy, Samuel Vanhove
 Davis Media Access - Darrick Servis
+Electronic Frontier Foundation - Mark Burdett
 Fuzion - Jitendra Purohit
 Ginkgo Street Labs - Frank Gómez
+Hossein Amin
 JMA Consulting - Monish Deb
+Johan Vervloet
 John Kingsnorth
 Joinery - Allen Shaw
+Kanzu Code - Carl Andrew Lema
 Kompetenzzentrum Technik-Diversity-Chancengleichheit - Niels Heinemann
 Left Join Labs - Sean Madsen
 Lighthouse Design and Consulting - Brian Shaughnessy
@@ -30,11 +41,17 @@ myDropWizard - David Snopek
 Naomi Rosenberg
 Olivier Tétard
 Oxfam Germany - Thomas Schüttler, Yuliyana Liyana
+Pradeep Nayak
 Progressive Technology Project - Jamie McClelland
+Romain Thouvenin
+Squiffle Consulting - Aidan Saunders
 Systopia - Björn Endres
 Tadpole Collective - Kevin Cristiano
 Third Sector Design - Michael McAndrew
+Tom Bloor
 Wikimedia Foundation - Eileen McNaughton
+Wildsight - Lars Sanders-Green
+Will Long
 
 ************************************************
 Key Contributors and Sponsors for 4.7
diff --git a/civicrm/CRM/ACL/BAO/ACL.php b/civicrm/CRM/ACL/BAO/ACL.php
index cd9c25dfee..15221f7456 100644
--- a/civicrm/CRM/ACL/BAO/ACL.php
+++ b/civicrm/CRM/ACL/BAO/ACL.php
@@ -877,7 +877,7 @@ SELECT g.*
       $aclKeys = array_keys($acls);
       $aclKeys = implode(',', $aclKeys);
 
-      $cacheKey = "$tableName-$aclKeys";
+      $cacheKey = CRM_Core_BAO_Cache::cleanKey("$tableName-$aclKeys");
       $cache = CRM_Utils_Cache::singleton();
       $ids = $cache->get($cacheKey);
       if (!$ids) {
diff --git a/civicrm/CRM/Activity/BAO/Activity.php b/civicrm/CRM/Activity/BAO/Activity.php
index a0a6b8e499..31c3c253d9 100644
--- a/civicrm/CRM/Activity/BAO/Activity.php
+++ b/civicrm/CRM/Activity/BAO/Activity.php
@@ -920,8 +920,7 @@ class CRM_Activity_BAO_Activity extends CRM_Activity_DAO_Activity {
 
     $config = CRM_Core_Config::singleton();
 
-    $randomNum = md5(uniqid());
-    $activityTempTable = "civicrm_temp_activity_details_{$randomNum}";
+    $activityTempTable = CRM_Utils_SQL_TempTable::build()->setCategory('actdetail')->getName();
 
     $tableFields = array(
       'activity_id' => 'int unsigned',
@@ -1012,7 +1011,7 @@ LEFT JOIN  civicrm_case_activity ON ( civicrm_case_activity.activity_id = tbl.ac
 
     // step 2: Get target and assignee contacts for above activities
     // create temp table for target contacts
-    $activityContactTempTable = "civicrm_temp_activity_contact_{$randomNum}";
+    $activityContactTempTable = CRM_Utils_SQL_TempTable::build()->setCategory('actcontact')->getName();
     $query = "CREATE TEMPORARY TABLE {$activityContactTempTable} (
                 activity_id int unsigned, contact_id int unsigned, record_type_id varchar(16),
                  contact_name varchar(255), is_deleted int unsigned, counter int unsigned, INDEX index_activity_id( activity_id ) )
@@ -2537,7 +2536,10 @@ AND cl.modified_id  = c.id
       // my case hence we have defined fields as case_*
       if ($name == 'Activity') {
         $exportableFields = CRM_Activity_DAO_Activity::export();
-        $exportableFields['source_contact_id']['title'] = ts('Source Contact ID');
+        $exportableFields['source_contact_id'] = [
+          'title' => ts('Source Contact ID'),
+          'type' => CRM_Utils_Type::T_INT,
+        ];
         $exportableFields['source_contact'] = array(
           'title' => ts('Source Contact'),
           'type' => CRM_Utils_Type::T_STRING,
diff --git a/civicrm/CRM/Activity/BAO/Query.php b/civicrm/CRM/Activity/BAO/Query.php
index 724a30642a..ddfb01aedd 100644
--- a/civicrm/CRM/Activity/BAO/Query.php
+++ b/civicrm/CRM/Activity/BAO/Query.php
@@ -136,7 +136,13 @@ class CRM_Activity_BAO_Query {
     if (!empty($query->_returnProperties['source_contact'])) {
       $query->_select['source_contact'] = 'source_contact.sort_name as source_contact';
       $query->_element['source_contact'] = 1;
-      $query->_tables['source_contact'] = $query->_whereTables['source_contact'] = 1;
+      $query->_tables['civicrm_activity'] = $query->_tables['source_contact'] = $query->_whereTables['source_contact'] = 1;
+    }
+
+    if (!empty($query->_returnProperties['source_contact_id'])) {
+      $query->_select['source_contact_id'] = 'source_contact.id as source_contact_id';
+      $query->_element['source_contact_id'] = 1;
+      $query->_tables['civicrm_activity'] = $query->_tables['source_contact'] = $query->_whereTables['source_contact'] = 1;
     }
 
     if (!empty($query->_returnProperties['activity_result'])) {
@@ -348,6 +354,14 @@ class CRM_Activity_BAO_Query {
           $query->_qill[$grouping][] = ts('Activities which are not Followup Activities');
         }
         break;
+
+      case 'source_contact':
+      case 'source_contact_id':
+        $columnName = strstr($name, '_id') ? 'id' : 'sort_name';
+        $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("source_contact.{$columnName}", $op, $value, CRM_Utils_Type::typeToString($fields[$name]['type']));
+        list($op, $value) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Contact_DAO_Contact', $columnName, $value, $op);
+        $query->_qill[$grouping][] = ts('%1 %2 %3', array(1 => $fields[$name]['title'], 2 => $op, 3 => $value));
+        break;
     }
   }
 
@@ -402,9 +416,8 @@ class CRM_Activity_BAO_Query {
         $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
         $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
         $from = "
-        LEFT JOIN civicrm_activity_contact ac
-                      ON ( ac.activity_id = civicrm_activity_contact.activity_id AND ac.record_type_id = {$sourceID})
-        INNER JOIN civicrm_contact source_contact ON (ac.contact_id = source_contact.id)";
+        INNER JOIN civicrm_contact source_contact ON
+          (civicrm_activity_contact.contact_id = source_contact.id) AND civicrm_activity_contact.record_type_id = {$sourceID}";
         break;
 
       case 'parent_id':
diff --git a/civicrm/CRM/Activity/Form/Task.php b/civicrm/CRM/Activity/Form/Task.php
index 254579876b..3a052beec4 100644
--- a/civicrm/CRM/Activity/Form/Task.php
+++ b/civicrm/CRM/Activity/Form/Task.php
@@ -32,37 +32,10 @@
  */
 
 /**
- * Class for activity task actions.
+ * Class for activity form task actions.
+ * FIXME: This needs refactoring to properly inherit from CRM_Core_Form_Task and share more functions.
  */
-class CRM_Activity_Form_Task extends CRM_Core_Form {
-
-  /**
-   * The task being performed.
-   *
-   * @var int
-   */
-  protected $_task;
-
-  /**
-   * The additional clause that we restrict the search with.
-   *
-   * @var string
-   */
-  protected $_componentClause = NULL;
-
-  /**
-   * The array that holds all the component ids.
-   *
-   * @var array
-   */
-  protected $_componentIds;
-
-  /**
-   * The array that holds all the contact ids.
-   *
-   * @var array
-   */
-  public $_contactIds;
+class CRM_Activity_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * The array that holds all the member ids.
@@ -82,9 +55,8 @@ class CRM_Activity_Form_Task extends CRM_Core_Form {
    * Common pre-process function.
    *
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_activityHolderIds = array();
 
     $values = $form->controller->exportValues($form->get('searchFormName'));
diff --git a/civicrm/CRM/Admin/Form/Job.php b/civicrm/CRM/Admin/Form/Job.php
index 74776dd0d4..e319f498ca 100644
--- a/civicrm/CRM/Admin/Form/Job.php
+++ b/civicrm/CRM/Admin/Form/Job.php
@@ -180,7 +180,7 @@ class CRM_Admin_Form_Job extends CRM_Admin_Form {
    */
   public function postProcess() {
 
-    CRM_Utils_System::flushCache('CRM_Core_DAO_Job');
+    CRM_Utils_System::flushCache();
 
     if ($this->_action & CRM_Core_Action::DELETE) {
       CRM_Core_BAO_Job::del($this->_id);
diff --git a/civicrm/CRM/Admin/Form/LocationType.php b/civicrm/CRM/Admin/Form/LocationType.php
index 7979db2dab..b1e1a97763 100644
--- a/civicrm/CRM/Admin/Form/LocationType.php
+++ b/civicrm/CRM/Admin/Form/LocationType.php
@@ -86,7 +86,7 @@ class CRM_Admin_Form_LocationType extends CRM_Admin_Form {
    * Process the form submission.
    */
   public function postProcess() {
-    CRM_Utils_System::flushCache('CRM_Core_DAO_LocationType');
+    CRM_Utils_System::flushCache();
 
     if ($this->_action & CRM_Core_Action::DELETE) {
       CRM_Core_BAO_LocationType::del($this->_id);
diff --git a/civicrm/CRM/Admin/Form/OptionGroup.php b/civicrm/CRM/Admin/Form/OptionGroup.php
index 059a5bf878..c1216fcd83 100644
--- a/civicrm/CRM/Admin/Form/OptionGroup.php
+++ b/civicrm/CRM/Admin/Form/OptionGroup.php
@@ -110,22 +110,24 @@ class CRM_Admin_Form_OptionGroup extends CRM_Admin_Form {
   public function postProcess() {
     CRM_Utils_System::flushCache();
 
-    $params = $this->exportValues();
     if ($this->_action & CRM_Core_Action::DELETE) {
       CRM_Core_BAO_OptionGroup::del($this->_id);
       CRM_Core_Session::setStatus(ts('Selected option group has been deleted.'), ts('Record Deleted'), 'success');
     }
     else {
-
-      $params = $ids = array();
       // store the submitted values in an array
       $params = $this->exportValues();
 
+      // If we are adding option group via UI it should not be marked reserved.
+      if (!isset($params['is_reserved'])) {
+        $params['is_reserved'] = 0;
+      }
+
       if ($this->_action & CRM_Core_Action::UPDATE) {
-        $ids['optionGroup'] = $this->_id;
+        $params['id'] = $this->_id;
       }
 
-      $optionGroup = CRM_Core_BAO_OptionGroup::add($params, $ids);
+      $optionGroup = CRM_Core_BAO_OptionGroup::add($params);
       CRM_Core_Session::setStatus(ts('The Option Group \'%1\' has been saved.', array(1 => $optionGroup->name)), ts('Saved'), 'success');
     }
   }
diff --git a/civicrm/CRM/Admin/Form/PaymentProcessorType.php b/civicrm/CRM/Admin/Form/PaymentProcessorType.php
index f816170567..6dfdaf7647 100644
--- a/civicrm/CRM/Admin/Form/PaymentProcessorType.php
+++ b/civicrm/CRM/Admin/Form/PaymentProcessorType.php
@@ -203,7 +203,7 @@ class CRM_Admin_Form_PaymentProcessorType extends CRM_Admin_Form {
    * Process the form submission.
    */
   public function postProcess() {
-    CRM_Utils_System::flushCache('CRM_Financial_DAO_PaymentProcessorType');
+    CRM_Utils_System::flushCache();
 
     if ($this->_action & CRM_Core_Action::DELETE) {
       CRM_Financial_BAO_PaymentProcessorType::del($this->_id);
diff --git a/civicrm/CRM/Admin/Form/ScheduleReminders.php b/civicrm/CRM/Admin/Form/ScheduleReminders.php
index 106487c5e3..1067dad2c5 100644
--- a/civicrm/CRM/Admin/Form/ScheduleReminders.php
+++ b/civicrm/CRM/Admin/Form/ScheduleReminders.php
@@ -43,6 +43,22 @@ class CRM_Admin_Form_ScheduleReminders extends CRM_Admin_Form {
 
   public $_freqUnits;
 
+  protected $_compId;
+
+  /**
+   * @return mixed
+   */
+  public function getComponentID() {
+    return $this->_compId;
+  }
+
+  /**
+   * @param mixed $compId
+   */
+  public function setComponentID($compId) {
+    $this->_compId = $compId;
+  }
+
   /**
    * Build the form object.
    */
@@ -50,57 +66,46 @@ class CRM_Admin_Form_ScheduleReminders extends CRM_Admin_Form {
     parent::buildQuickForm();
     $this->_mappingID = $mappingID = NULL;
     $providersCount = CRM_SMS_BAO_Provider::activeProviderCount();
-    $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this);
+    $this->setContext();
+    $isEvent = $this->getContext() == 'event';
 
-    //CRM-16777: Don't provide access to administer schedule reminder page, with user that does not have 'administer CiviCRM' permission
-    if (empty($this->_context) && !CRM_Core_Permission::check('administer CiviCRM')) {
-      CRM_Core_Error::fatal(ts('You do not have permission to access this page.'));
-    }
-    //CRM-16777: When user have ACLs 'edit' permission for specific event, do not give access to add, delete & updtae
-    //schedule reminder for other events.
-    else {
-      $this->_compId = CRM_Utils_Request::retrieve('compId', 'Integer', $this);
-      if (!CRM_Event_BAO_Event::checkPermission($this->_compId, CRM_Core_Permission::EDIT)) {
-        CRM_Core_Error::fatal(ts('You do not have permission to access this page.'));
+    if ($isEvent) {
+      $this->setComponentID(CRM_Utils_Request::retrieve('compId', 'Integer', $this));
+      if (!CRM_Event_BAO_Event::checkPermission($this->getComponentID(), CRM_Core_Permission::EDIT)) {
+        throw new CRM_Core_Exception(ts('You do not have permission to access this page.'));
       }
     }
+    elseif (!CRM_Core_Permission::check('administer CiviCRM')) {
+      throw new CRM_Core_Exception(ts('You do not have permission to access this page.'));
+    }
 
     if ($this->_action & (CRM_Core_Action::DELETE)) {
       $reminderName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionSchedule', $this->_id, 'title');
-      if ($this->_context == 'event') {
-        $this->_compId = CRM_Utils_Request::retrieve('compId', 'Integer', $this);
-      }
       $this->assign('reminderName', $reminderName);
       return;
     }
     elseif ($this->_action & (CRM_Core_Action::UPDATE)) {
       $this->_mappingID = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionSchedule', $this->_id, 'mapping_id');
-      if ($this->_context == 'event') {
-        $this->_compId = CRM_Utils_Request::retrieve('compId', 'Integer', $this);
-      }
     }
-    elseif (!empty($this->_context)) {
-      if ($this->_context == 'event') {
-        $this->_compId = CRM_Utils_Request::retrieve('compId', 'Integer', $this);
-        $isTemplate = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $this->_compId, 'is_template');
-        $mapping = CRM_Utils_Array::first(CRM_Core_BAO_ActionSchedule::getMappings(array(
-          'id' => $isTemplate ? CRM_Event_ActionMapping::EVENT_TPL_MAPPING_ID : CRM_Event_ActionMapping::EVENT_NAME_MAPPING_ID,
-        )));
-        if ($mapping) {
-          $this->_mappingID = $mapping->getId();
-        }
-        else {
-          CRM_Core_Error::fatal('Could not find mapping for event scheduled reminders.');
-        }
+    if ($isEvent) {
+      $isTemplate = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $this->getComponentID(), 'is_template');
+      $mapping = CRM_Utils_Array::first(CRM_Core_BAO_ActionSchedule::getMappings(array(
+        'id' => $isTemplate ? CRM_Event_ActionMapping::EVENT_TPL_MAPPING_ID : CRM_Event_ActionMapping::EVENT_NAME_MAPPING_ID,
+      )));
+      if ($mapping) {
+        $this->_mappingID = $mapping->getId();
+      }
+      else {
+        throw new CRM_Core_Exception('Could not find mapping for event scheduled reminders.');
       }
     }
 
-    if (!empty($_POST) && !empty($_POST['entity']) && empty($this->_context)) {
+    if (!empty($_POST) && !empty($_POST['entity']) && empty($this->getContext())) {
       $mappingID = $_POST['entity'][0];
     }
     elseif ($this->_mappingID) {
       $mappingID = $this->_mappingID;
-      if ($this->_context == 'event') {
+      if ($isEvent) {
         $this->add('hidden', 'mappingID', $mappingID);
       }
     }
@@ -123,7 +128,7 @@ class CRM_Admin_Form_ScheduleReminders extends CRM_Admin_Form {
       array_combine(array_keys($entityRecipientLabels), array_keys($entityRecipientLabels))
     ));
 
-    if (empty($this->_context)) {
+    if (!$this->getContext()) {
       $sel = &$this->add(
         'hierselect',
         'entity',
@@ -158,7 +163,7 @@ class CRM_Admin_Form_ScheduleReminders extends CRM_Admin_Form {
       $attributes = array('multiple' => 'multiple', 'class' => 'crm-select2 huge', 'placeholder' => $options[0]);
       unset($options[0]);
       $this->add('select', 'entity', ts('Recipient(s)'), $options, TRUE, $attributes);
-      $this->assign('context', $this->_context);
+      $this->assign('context', $this->getContext());
     }
 
     //get the frequency units.
@@ -243,7 +248,7 @@ class CRM_Admin_Form_ScheduleReminders extends CRM_Admin_Form {
     );
 
     if (!empty($this->_submitValues['recipient_listing'])) {
-      if (!empty($this->_context)) {
+      if ($this->getContext()) {
         $recipientListingOptions = CRM_Core_BAO_ActionSchedule::getRecipientListing($this->_mappingID, $this->_submitValues['recipient']);
       }
       else {
@@ -325,7 +330,7 @@ class CRM_Admin_Form_ScheduleReminders extends CRM_Admin_Form {
       $errors['html_message'] = ts('The HTML message is a required field.');
     }
 
-    if (empty($self->_context) && CRM_Utils_System::isNull(CRM_Utils_Array::value(1, $fields['entity']))) {
+    if (empty($self->getContext()) && CRM_Utils_System::isNull(CRM_Utils_Array::value(1, $fields['entity']))) {
       $errors['entity'] = ts('Please select entity value');
     }
 
@@ -384,7 +389,7 @@ class CRM_Admin_Form_ScheduleReminders extends CRM_Admin_Form {
       $defaults = $this->_values;
       $entityValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, CRM_Utils_Array::value('entity_value', $defaults));
       $entityStatus = explode(CRM_Core_DAO::VALUE_SEPARATOR, CRM_Utils_Array::value('entity_status', $defaults));
-      if (empty($this->_context)) {
+      if (empty($this->getContext())) {
         $defaults['entity'][0] = CRM_Utils_Array::value('mapping_id', $defaults);
         $defaults['entity'][1] = $entityValue;
         $defaults['entity'][2] = $entityStatus;
@@ -430,9 +435,9 @@ class CRM_Admin_Form_ScheduleReminders extends CRM_Admin_Form {
       // delete reminder
       CRM_Core_BAO_ActionSchedule::del($this->_id);
       CRM_Core_Session::setStatus(ts('Selected Reminder has been deleted.'), ts('Record Deleted'), 'success');
-      if ($this->_context == 'event' && $this->_compId) {
+      if ($this->getContext() == 'event' && $this->getComponentID()) {
         $url = CRM_Utils_System::url('civicrm/event/manage/reminder',
-          "reset=1&action=browse&id={$this->_compId}&component={$this->_context}&setTab=1"
+          "reset=1&action=browse&id=" . $this->getComponentID() . "&component=" . $this->getContext() . "&setTab=1"
         );
         $session = CRM_Core_Session::singleton();
         $session->pushUserContext($url);
@@ -457,8 +462,8 @@ class CRM_Admin_Form_ScheduleReminders extends CRM_Admin_Form {
         );
       }
 
-      if ($this->_context == 'event' && $this->_compId) {
-        $url = CRM_Utils_System::url('civicrm/event/manage/reminder', "reset=1&action=browse&id={$this->_compId}&component={$this->_context}&setTab=1");
+      if ($this->getContext() == 'event' && $this->getComponentID()) {
+        $url = CRM_Utils_System::url('civicrm/event/manage/reminder', "reset=1&action=browse&id=" . $this->getComponentID() . "&component=" . $this->getContext() . "&setTab=1");
         $session = CRM_Core_Session::singleton();
         $session->pushUserContext($url);
       }
@@ -543,9 +548,9 @@ class CRM_Admin_Form_ScheduleReminders extends CRM_Admin_Form {
       $params['group_id'] = $params['recipient_manual'] = $params['recipient_listing'] = 'null';
     }
 
-    if (!empty($this->_mappingID) && !empty($this->_compId)) {
+    if (!empty($this->_mappingID) && !empty($this->getComponentID())) {
       $params['mapping_id'] = $this->_mappingID;
-      $params['entity_value'] = $this->_compId;
+      $params['entity_value'] = $this->getComponentID();
       $params['entity_status'] = implode(CRM_Core_DAO::VALUE_SEPARATOR, $values['entity']);
     }
     else {
diff --git a/civicrm/CRM/Admin/Form/Setting.php b/civicrm/CRM/Admin/Form/Setting.php
index e19ff51286..695f6f097d 100644
--- a/civicrm/CRM/Admin/Form/Setting.php
+++ b/civicrm/CRM/Admin/Form/Setting.php
@@ -233,6 +233,7 @@ class CRM_Admin_Form_Setting extends CRM_Core_Form {
     }
 
     CRM_Core_Config::clearDBCache();
+    Civi::cache('session')->clear(); // This doesn't make a lot of sense to me, but it maintains pre-existing behavior.
     CRM_Utils_System::flushCache();
     CRM_Core_Resources::singleton()->resetCacheCode();
 
diff --git a/civicrm/CRM/Admin/Form/Setting/UpdateConfigBackend.php b/civicrm/CRM/Admin/Form/Setting/UpdateConfigBackend.php
index 68711ff42a..b5c7bb8c57 100644
--- a/civicrm/CRM/Admin/Form/Setting/UpdateConfigBackend.php
+++ b/civicrm/CRM/Admin/Form/Setting/UpdateConfigBackend.php
@@ -65,6 +65,7 @@ class CRM_Admin_Form_Setting_UpdateConfigBackend extends CRM_Admin_Form_Setting
 
       // clear all caches
       CRM_Core_Config::clearDBCache();
+      Civi::cache('session')->clear();
       CRM_Utils_System::flushCache();
 
       parent::rebuildMenu();
diff --git a/civicrm/CRM/Admin/Page/Job.php b/civicrm/CRM/Admin/Page/Job.php
index 92237cccda..9d9d31699d 100644
--- a/civicrm/CRM/Admin/Page/Job.php
+++ b/civicrm/CRM/Admin/Page/Job.php
@@ -96,6 +96,12 @@ class CRM_Admin_Page_Job extends CRM_Core_Page_Basic {
           'qs' => 'action=delete&id=%%id%%',
           'title' => ts('Delete Scheduled Job'),
         ),
+        CRM_Core_Action::COPY => array(
+          'name' => ts('Copy'),
+          'url' => 'civicrm/admin/job',
+          'qs' => 'action=copy&id=%%id%%',
+          'title' => ts('Copy Scheduled Job'),
+        ),
       );
     }
     return self::$_links;
@@ -128,11 +134,25 @@ class CRM_Admin_Page_Job extends CRM_Core_Page_Basic {
       $this, FALSE, 0
     );
 
+    // FIXME: Why are we comparing an integer with a string here?
     if ($this->_action == 'export') {
       $session = CRM_Core_Session::singleton();
       $session->pushUserContext(CRM_Utils_System::url('civicrm/admin/job', 'reset=1'));
     }
 
+    if (($this->_action & CRM_Core_Action::COPY) && (!empty($this->_id))) {
+      try {
+        $jobResult = civicrm_api3('Job', 'clone', array('id' => $this->_id));
+        if ($jobResult['count'] > 0) {
+          CRM_Core_Session::setStatus($jobResult['values'][$jobResult['id']]['name'], ts('Job copied successfully'), 'success');
+        }
+        CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/job', 'reset=1'));
+      }
+      catch (Exception $e) {
+        CRM_Core_Session::setStatus(ts('Failed to copy job'), 'Error');
+      }
+    }
+
     return parent::run();
   }
 
diff --git a/civicrm/CRM/Batch/BAO/Batch.php b/civicrm/CRM/Batch/BAO/Batch.php
index ceae073122..ee73a515f0 100644
--- a/civicrm/CRM/Batch/BAO/Batch.php
+++ b/civicrm/CRM/Batch/BAO/Batch.php
@@ -719,6 +719,8 @@ LEFT JOIN civicrm_contribution_soft ON civicrm_contribution_soft.contribution_id
       'contribution_date_low',
       'contribution_check_number',
       'contribution_status_id',
+      'financial_trxn_card_type_id',
+      'financial_trxn_pan_truncation',
     );
     $values = array();
     foreach ($searchFields as $field) {
diff --git a/civicrm/CRM/Campaign/BAO/Petition.php b/civicrm/CRM/Campaign/BAO/Petition.php
index 43246cc66f..f8b0500ca2 100644
--- a/civicrm/CRM/Campaign/BAO/Petition.php
+++ b/civicrm/CRM/Campaign/BAO/Petition.php
@@ -586,7 +586,7 @@ AND         tag_id = ( SELECT id FROM civicrm_tag WHERE name = %2 )";
 
     $toName = CRM_Contact_BAO_Contact::displayName($params['contactId']);
 
-    $replyTo = "do-not-reply@$emailDomain";
+    $replyTo = CRM_Core_BAO_Domain::getNoReplyEmailAddress();
 
     // set additional general message template params (custom tokens to use in email msg templates)
     // tokens then available in msg template as {$petition.title}, etc
diff --git a/civicrm/CRM/Campaign/Form/Task.php b/civicrm/CRM/Campaign/Form/Task.php
index 5d7bbd6a9b..a7e464ad83 100644
--- a/civicrm/CRM/Campaign/Form/Task.php
+++ b/civicrm/CRM/Campaign/Form/Task.php
@@ -34,35 +34,7 @@
 /**
  * This class generates form components for relationship.
  */
-class CRM_Campaign_Form_Task extends CRM_Core_Form {
-
-  /**
-   * The additional clause that we restrict the search.
-   *
-   * @var string
-   */
-  protected $_componentClause = NULL;
-
-  /**
-   * The task being performed
-   *
-   * @var int
-   */
-  protected $_task;
-
-  /**
-   * The array that holds all the contact ids
-   *
-   * @var array
-   */
-  public $_contactIds;
-
-  /**
-   * The array that holds all the component ids
-   *
-   * @var array
-   */
-  protected $_componentIds;
+class CRM_Campaign_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * The array that holds all the voter ids
diff --git a/civicrm/CRM/Case/BAO/Case.php b/civicrm/CRM/Case/BAO/Case.php
index 8048c8ff75..0fa87b459e 100644
--- a/civicrm/CRM/Case/BAO/Case.php
+++ b/civicrm/CRM/Case/BAO/Case.php
@@ -274,16 +274,16 @@ WHERE civicrm_case.id = %1";
    *   ID of the case.
    *
    * @param int $contactID
+   * @param int $startArrayAt This is to support legacy calls to Case.Get API which may rely on the first array index being set to 1
    *
    * @return array
    */
-  public static function retrieveContactIdsByCaseId($caseId, $contactID = NULL) {
+  public static function retrieveContactIdsByCaseId($caseId, $contactID = NULL, $startArrayAt = 0) {
     $caseContact = new CRM_Case_DAO_CaseContact();
     $caseContact->case_id = $caseId;
     $caseContact->find();
     $contactArray = array();
-    // FIXME: Why does this return a 1-based array?
-    $count = 1;
+    $count = $startArrayAt;
     while ($caseContact->fetch()) {
       if ($contactID != $caseContact->contact_id) {
         $contactArray[$count] = $caseContact->contact_id;
@@ -840,12 +840,13 @@ SELECT case_status.label AS case_status, status_id, civicrm_case_type.title AS c
    * @param int $caseID
    *   Case id.
    * @param int $relationshipID
+   * @param bool $activeOnly
    *
    * @return array
    *   case role / relationships
    *
    */
-  public static function getCaseRoles($contactID, $caseID, $relationshipID = NULL) {
+  public static function getCaseRoles($contactID, $caseID, $relationshipID = NULL, $activeOnly = TRUE) {
     $query = '
     SELECT  rel.id as civicrm_relationship_id,
             con.sort_name as sort_name,
@@ -861,7 +862,11 @@ SELECT case_status.label AS case_status, status_id, civicrm_case_type.title AS c
  LEFT JOIN  civicrm_phone ON (civicrm_phone.contact_id = con.id AND civicrm_phone.is_primary = 1)
  LEFT JOIN  civicrm_email ON (civicrm_email.contact_id = con.id AND civicrm_email.is_primary = 1)
      WHERE  (rel.contact_id_a = %1 OR rel.contact_id_b = %1) AND rel.case_id = %2
-       AND  rel.is_active = 1 AND con.is_deleted = 0 AND (rel.end_date IS NULL OR rel.end_date > NOW())';
+       AND con.is_deleted = 0';
+
+    if ($activeOnly) {
+      $query .= ' AND rel.is_active = 1 AND (rel.end_date IS NULL OR rel.end_date > NOW())';
+    }
 
     $params = array(
       1 => array($contactID, 'Positive'),
@@ -1085,7 +1090,6 @@ SELECT case_status.label AS case_status, status_id, civicrm_case_type.title AS c
 
     $contactViewUrl = CRM_Utils_System::url("civicrm/contact/view", "reset=1&cid=", FALSE, NULL, FALSE);
     $hasViewContact = CRM_Core_Permission::giveMeAllACLs();
-    $clientIds = self::retrieveContactIdsByCaseId($caseID);
 
     if (!$userID) {
       $session = CRM_Core_Session::singleton();
diff --git a/civicrm/CRM/Case/BAO/CaseType.php b/civicrm/CRM/Case/BAO/CaseType.php
index 42cc1baedd..89baec6098 100644
--- a/civicrm/CRM/Case/BAO/CaseType.php
+++ b/civicrm/CRM/Case/BAO/CaseType.php
@@ -242,6 +242,8 @@ class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType {
     // set activity sets
     if (isset($xml->ActivitySets)) {
       $definition['activitySets'] = array();
+      $definition['timelineActivityTypes'] = array();
+
       foreach ($xml->ActivitySets->ActivitySet as $activitySetXML) {
         // parse basic properties
         $activitySet = array();
@@ -257,7 +259,11 @@ class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType {
         if (isset($activitySetXML->ActivityTypes)) {
           $activitySet['activityTypes'] = array();
           foreach ($activitySetXML->ActivityTypes->ActivityType as $activityTypeXML) {
-            $activitySet['activityTypes'][] = json_decode(json_encode($activityTypeXML), TRUE);
+            $activityType = json_decode(json_encode($activityTypeXML), TRUE);
+            $activitySet['activityTypes'][] = $activityType;
+            if ($activitySetXML->timeline) {
+              $definition['timelineActivityTypes'][] = $activityType;
+            }
           }
         }
         $definition['activitySets'][] = $activitySet;
diff --git a/civicrm/CRM/Case/Form/Activity/ChangeCaseStatus.php b/civicrm/CRM/Case/Form/Activity/ChangeCaseStatus.php
index c00901c9ab..3452a51ea7 100644
--- a/civicrm/CRM/Case/Form/Activity/ChangeCaseStatus.php
+++ b/civicrm/CRM/Case/Form/Activity/ChangeCaseStatus.php
@@ -194,7 +194,7 @@ class CRM_Case_Form_Activity_ChangeCaseStatus {
 
       // Reopen case-specific relationships (roles)
       foreach ($params['target_contact_id'] as $cid) {
-        $rels = CRM_Case_BAO_Case::getCaseRoles($cid, $params['case_id']);
+        $rels = CRM_Case_BAO_Case::getCaseRoles($cid, $params['case_id'], NULL, FALSE);
         // FIXME: Is there an existing function?
         $query = 'UPDATE civicrm_relationship SET end_date=NULL WHERE id=%1';
         foreach ($rels as $relId => $relData) {
diff --git a/civicrm/CRM/Case/Form/ActivityView.php b/civicrm/CRM/Case/Form/ActivityView.php
index e18b086d19..16247cda22 100644
--- a/civicrm/CRM/Case/Form/ActivityView.php
+++ b/civicrm/CRM/Case/Form/ActivityView.php
@@ -172,6 +172,38 @@ class CRM_Case_Form_ActivityView extends CRM_Core_Form {
       $recentContactDisplay,
       $recentOther
     );
+
+    // Set breadcrumb to take the user back to the case being viewed
+    $caseTypeId = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $caseID, 'case_type_id');
+    $caseType = CRM_Core_PseudoConstant::getLabel('CRM_Case_BAO_Case', 'case_type_id', $caseTypeId);
+    $caseContact = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseContact', $caseID, 'contact_id', 'case_id');
+
+    CRM_Utils_System::resetBreadCrumb();
+    $breadcrumb = [
+      [
+        'title' => ts('Home'),
+        'url' => CRM_Utils_System::url(),
+      ],
+      [
+        'title' => ts('CiviCRM'),
+        'url' => CRM_Utils_System::url('civicrm', 'reset=1'),
+      ],
+      [
+        'title' => ts('CiviCase Dashboard'),
+        'url' => CRM_Utils_System::url('civicrm/case', 'reset=1'),
+      ],
+      [
+        'title' => $caseType,
+        'url' => CRM_Utils_System::url('civicrm/contact/view/case', [
+          'reset' => 1,
+          'id' => $caseID,
+          'context' => 'case',
+          'action' => 'view',
+          'cid' => $caseContact,
+        ]),
+      ],
+    ];
+    CRM_Utils_System::appendBreadCrumb($breadcrumb);
   }
 
 }
diff --git a/civicrm/CRM/Case/Form/Task.php b/civicrm/CRM/Case/Form/Task.php
index b9d05e6875..513ee62e05 100644
--- a/civicrm/CRM/Case/Form/Task.php
+++ b/civicrm/CRM/Case/Form/Task.php
@@ -49,4 +49,13 @@ class CRM_Case_Form_Task extends CRM_Core_Form_Task {
     );
   }
 
+  /**
+   * Get the query mode (eg. CRM_Core_BAO_Query::MODE_CASE)
+   *
+   * @return int
+   */
+  public function getQueryMode() {
+    return CRM_Contact_BAO_Query::MODE_CASE;
+  }
+
 }
diff --git a/civicrm/CRM/Case/Task.php b/civicrm/CRM/Case/Task.php
index 956ff16662..954786cde5 100644
--- a/civicrm/CRM/Case/Task.php
+++ b/civicrm/CRM/Case/Task.php
@@ -67,7 +67,7 @@ class CRM_Case_Task extends CRM_Core_Task {
         self::TASK_EXPORT => array(
           'title' => ts('Export cases'),
           'class' => array(
-            'CRM_Export_Form_Select',
+            'CRM_Export_Form_Select_Case',
             'CRM_Export_Form_Map',
           ),
           'result' => FALSE,
diff --git a/civicrm/CRM/Case/XMLProcessor/Process.php b/civicrm/CRM/Case/XMLProcessor/Process.php
index f125355ec9..1929c38fb7 100644
--- a/civicrm/CRM/Case/XMLProcessor/Process.php
+++ b/civicrm/CRM/Case/XMLProcessor/Process.php
@@ -31,6 +31,8 @@
  * @copyright CiviCRM LLC (c) 2004-2018
  */
 class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
+  protected $defaultAssigneeOptionsValues = [];
+
   /**
    * Run.
    *
@@ -314,6 +316,7 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
 
   /**
    * @param SimpleXMLElement $caseTypeXML
+   *
    * @return array<string> symbolic activity-type names
    */
   public function getDeclaredActivityTypes($caseTypeXML) {
@@ -342,6 +345,7 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
 
   /**
    * @param SimpleXMLElement $caseTypeXML
+   *
    * @return array<string> symbolic relationship-type names
    */
   public function getDeclaredRelationshipTypes($caseTypeXML) {
@@ -474,6 +478,8 @@ AND        a.is_deleted = 0
       );
     }
 
+    $activityParams['assignee_contact_id'] = $this->getDefaultAssigneeForActivity($activityParams, $activityTypeXML);
+
     //parsing date to default preference format
     $params['activity_date_time'] = CRM_Utils_Date::processDate($params['activity_date_time']);
 
@@ -568,6 +574,155 @@ AND        a.is_deleted = 0
     return TRUE;
   }
 
+  /**
+   * Return the default assignee contact for the activity.
+   *
+   * @param array $activityParams
+   * @param object $activityTypeXML
+   *
+   * @return int|null the ID of the default assignee contact or null if none.
+   */
+  protected function getDefaultAssigneeForActivity($activityParams, $activityTypeXML) {
+    if (!isset($activityTypeXML->default_assignee_type)) {
+      return NULL;
+    }
+
+    $defaultAssigneeOptionsValues = $this->getDefaultAssigneeOptionValues();
+
+    switch ($activityTypeXML->default_assignee_type) {
+      case $defaultAssigneeOptionsValues['BY_RELATIONSHIP']:
+        return $this->getDefaultAssigneeByRelationship($activityParams, $activityTypeXML);
+
+      break;
+      case $defaultAssigneeOptionsValues['SPECIFIC_CONTACT']:
+        return $this->getDefaultAssigneeBySpecificContact($activityTypeXML);
+
+      break;
+      case $defaultAssigneeOptionsValues['USER_CREATING_THE_CASE']:
+        return $activityParams['source_contact_id'];
+
+      break;
+      case $defaultAssigneeOptionsValues['NONE']:
+      default:
+        return NULL;
+    }
+  }
+
+  /**
+   * Fetches and caches the activity's default assignee options.
+   *
+   * @return array
+   */
+  protected function getDefaultAssigneeOptionValues() {
+    if (!empty($this->defaultAssigneeOptionsValues)) {
+      return $this->defaultAssigneeOptionsValues;
+    }
+
+    $defaultAssigneeOptions = civicrm_api3('OptionValue', 'get', [
+      'option_group_id' => 'activity_default_assignee',
+      'options' => [ 'limit' => 0 ]
+    ]);
+
+    foreach ($defaultAssigneeOptions['values'] as $option) {
+      $this->defaultAssigneeOptionsValues[$option['name']] = $option['value'];
+    }
+
+    return $this->defaultAssigneeOptionsValues;
+  }
+
+  /**
+   * Returns the default assignee for the activity by searching for the target's
+   * contact relationship type defined in the activity's details.
+   *
+   * @param array $activityParams
+   * @param object $activityTypeXML
+   *
+   * @return int|null the ID of the default assignee contact or null if none.
+   */
+  protected function getDefaultAssigneeByRelationship($activityParams, $activityTypeXML) {
+    $isDefaultRelationshipDefined = isset($activityTypeXML->default_assignee_relationship)
+      && preg_match('/\d+_[ab]_[ab]/', $activityTypeXML->default_assignee_relationship);
+
+    if (!$isDefaultRelationshipDefined) {
+      return NULL;
+    }
+
+    $targetContactId = is_array($activityParams['target_contact_id'])
+      ? CRM_Utils_Array::first($activityParams['target_contact_id'])
+      : $activityParams['target_contact_id'];
+    list($relTypeId, $a, $b) = explode('_', $activityTypeXML->default_assignee_relationship);
+
+    $params = [
+      'relationship_type_id' => $relTypeId,
+      "contact_id_$b" => $targetContactId,
+      'is_active' => 1,
+    ];
+
+    if ($this->isBidirectionalRelationshipType($relTypeId)) {
+      $params["contact_id_$a"] = $targetContactId;
+      $params['options']['or'] = [['contact_id_a', 'contact_id_b']];
+    }
+
+    $relationships = civicrm_api3('Relationship', 'get', $params);
+
+    if ($relationships['count']) {
+      $relationship = CRM_Utils_Array::first($relationships['values']);
+
+      // returns the contact id on the other side of the relationship:
+      return (int) $relationship['contact_id_a'] === (int) $targetContactId
+        ? $relationship['contact_id_b']
+        : $relationship['contact_id_a'];
+    }
+    else {
+      return NULL;
+    }
+  }
+
+  /**
+   * Determines if the given relationship type is bidirectional or not by
+   * comparing their labels.
+   *
+   * @return bool
+   */
+  protected function isBidirectionalRelationshipType($relationshipTypeId) {
+    $relationshipTypeResult = civicrm_api3('RelationshipType', 'get', [
+      'id' => $relationshipTypeId,
+      'options' => ['limit' => 1]
+    ]);
+
+    if ($relationshipTypeResult['count'] === 0) {
+      return FALSE;
+    }
+
+    $relationshipType = CRM_Utils_Array::first($relationshipTypeResult['values']);
+
+    return $relationshipType['label_b_a'] === $relationshipType['label_a_b'];
+  }
+
+  /**
+   * Returns the activity's default assignee for a specific contact if the contact exists,
+   * otherwise returns null.
+   *
+   * @param object $activityTypeXML
+   *
+   * @return int|null
+   */
+  protected function getDefaultAssigneeBySpecificContact($activityTypeXML) {
+    if (!$activityTypeXML->default_assignee_contact) {
+      return NULL;
+    }
+
+    $contact = civicrm_api3('Contact', 'get', [
+      'id' => $activityTypeXML->default_assignee_contact
+    ]);
+
+    if ($contact['count'] == 1) {
+      return $activityTypeXML->default_assignee_contact;
+    }
+
+    return NULL;
+  }
+
   /**
    * @param $activitySetsXML
    *
@@ -617,6 +772,7 @@ AND        a.is_deleted = 0
 
   /**
    * @param string $caseType
+   *
    * @return array<\Civi\CCase\CaseChangeListener>
    */
   public function getListeners($caseType) {
@@ -662,6 +818,7 @@ AND        a.is_deleted = 0
    * @param string $settingKey
    * @param string $xmlTag
    * @param mixed $default
+   *
    * @return int
    */
   private function getBoolSetting($settingKey, $xmlTag, $default = 0) {
diff --git a/civicrm/CRM/Contact/BAO/Contact.php b/civicrm/CRM/Contact/BAO/Contact.php
index 0149986863..f222e19594 100644
--- a/civicrm/CRM/Contact/BAO/Contact.php
+++ b/civicrm/CRM/Contact/BAO/Contact.php
@@ -1022,10 +1022,6 @@ WHERE     civicrm_contact.id = " . CRM_Utils_Type::escape($id, 'Integer');
       CRM_Utils_Hook::post('delete', $contactType, $contact->id, $contact);
     }
 
-    // also reset the DB_DO global array so we can reuse the memory
-    // http://issues.civicrm.org/jira/browse/CRM-4387
-    CRM_Core_DAO::freeResult();
-
     return TRUE;
   }
 
diff --git a/civicrm/CRM/Contact/BAO/ContactType.php b/civicrm/CRM/Contact/BAO/ContactType.php
index a299e9e591..cb13346f55 100644
--- a/civicrm/CRM/Contact/BAO/ContactType.php
+++ b/civicrm/CRM/Contact/BAO/ContactType.php
@@ -387,6 +387,7 @@ WHERE  type.name IS NOT NULL
     $argString = $all ? 'CRM_CT_GSE_1' : 'CRM_CT_GSE_0';
     $argString .= $isSeparator ? '_1' : '_0';
     $argString .= $separator;
+    $argString = CRM_Core_BAO_Cache::cleanKey($argString);
     if (!array_key_exists($argString, $_cache)) {
       $cache = CRM_Utils_Cache::singleton();
       $_cache[$argString] = $cache->get($argString);
diff --git a/civicrm/CRM/Contact/BAO/Group.php b/civicrm/CRM/Contact/BAO/Group.php
index 759a6b3eac..8c08654256 100644
--- a/civicrm/CRM/Contact/BAO/Group.php
+++ b/civicrm/CRM/Contact/BAO/Group.php
@@ -882,7 +882,7 @@ class CRM_Contact_BAO_Group extends CRM_Contact_DAO_Group {
     // CRM-9936
     $reservedPermission = CRM_Core_Permission::check('administer reserved groups');
 
-    $links = self::actionLinks();
+    $links = self::actionLinks($params);
 
     $allTypes = CRM_Core_OptionGroup::values('group_type');
     $values = array();
@@ -1254,12 +1254,17 @@ WHERE {$whereClause}";
    * @return array
    *   array of action links
    */
-  public static function actionLinks() {
+  public static function actionLinks($params) {
+    // If component_mode is set we change the "View" link to match the requested component type
+    if (!isset($params['component_mode'])) {
+      $params['component_mode'] = CRM_Contact_BAO_Query::MODE_CONTACTS;
+    }
+    $modeValue = CRM_Contact_Form_Search::getModeValue($params['component_mode']);
     $links = array(
       CRM_Core_Action::VIEW => array(
-        'name' => ts('Contacts'),
+        'name' => $modeValue['selectorLabel'],
         'url' => 'civicrm/group/search',
-        'qs' => 'reset=1&force=1&context=smog&gid=%%id%%',
+        'qs' => 'reset=1&force=1&context=smog&gid=%%id%%&component_mode=' . $params['component_mode'],
         'title' => ts('Group Contacts'),
       ),
       CRM_Core_Action::UPDATE => array(
diff --git a/civicrm/CRM/Contact/BAO/Query.php b/civicrm/CRM/Contact/BAO/Query.php
index 4e02c364dd..eda7792aa7 100644
--- a/civicrm/CRM/Contact/BAO/Query.php
+++ b/civicrm/CRM/Contact/BAO/Query.php
@@ -4066,7 +4066,7 @@ WHERE  $smartGroupClause
       $relationType[2] = (array) $relationType[2];
       foreach ($relationType[2] as $relType) {
         $rel = explode('_', $relType);
-        self::$_relType .= $rel[1];
+        self::$_relType = $rel[1];
         $params = array('id' => $rel[0]);
         $typeValues = array();
         $rTypeValue = CRM_Contact_BAO_RelationshipType::retrieve($params, $typeValues);
@@ -5662,11 +5662,11 @@ SELECT COUNT( conts.total_amount ) as cancel_count,
         return $clause;
 
       case 'IS EMPTY':
-        $clause = " (NULLIF($field, '') IS NULL) ";
+        $clause = ($dataType == 'Date') ? " $field IS NULL " : " (NULLIF($field, '') IS NULL) ";
         return $clause;
 
       case 'IS NOT EMPTY':
-        $clause = " (NULLIF($field, '') IS NOT NULL) ";
+        $clause = ($dataType == 'Date') ? " $field IS NOT NULL " : " (NULLIF($field, '') IS NOT NULL) ";
         return $clause;
 
       case 'IN':
@@ -5677,7 +5677,7 @@ SELECT COUNT( conts.total_amount ) as cancel_count,
         }
 
       default:
-        if (empty($dataType)) {
+        if (empty($dataType) || $dataType == 'Date') {
           $dataType = 'String';
         }
         if (is_array($value)) {
diff --git a/civicrm/CRM/Contact/Form/Contact.php b/civicrm/CRM/Contact/Form/Contact.php
index 9239530190..1deb5ea20a 100644
--- a/civicrm/CRM/Contact/Form/Contact.php
+++ b/civicrm/CRM/Contact/Form/Contact.php
@@ -1461,7 +1461,7 @@ class CRM_Contact_Form_Contact extends CRM_Core_Form {
           'max_related' => $dao->max_related,
         );
 
-        CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray);
+        CRM_Member_BAO_MembershipLog::add($membershipLog);
 
         //create activity when membership status is changed
         $activityParam = array(
diff --git a/civicrm/CRM/Contact/Form/Search/Builder.php b/civicrm/CRM/Contact/Form/Search/Builder.php
index 2a5dd79351..4b5f0ddc29 100644
--- a/civicrm/CRM/Contact/Form/Search/Builder.php
+++ b/civicrm/CRM/Contact/Form/Search/Builder.php
@@ -95,18 +95,10 @@ class CRM_Contact_Form_Search_Builder extends CRM_Contact_Form_Search {
     // This array contain list of available fields and their corresponding data type,
     //  later assigned as json string, to be used to filter list of mysql operators
     $fieldNameTypes = [];
-    $dataType = [
-      CRM_Utils_Type::T_STRING => 'String',
-      CRM_Utils_Type::T_TEXT => 'String',
-      CRM_Utils_Type::T_LONGTEXT => 'String',
-      CRM_Utils_Type::T_BOOLEAN => 'Boolean',
-      CRM_Utils_Type::T_DATE => 'Date',
-      CRM_Utils_Type::T_TIMESTAMP => 'Date',
-    ];
     foreach ($fields as $name => $field) {
       // Assign date type to respective field name, which will be later used to modify operator list
-      if (isset($field['type']) && array_key_exists($field['type'], $dataType)) {
-        $fieldNameTypes[$name] = $dataType[$field['type']];
+      if ($type = CRM_Utils_Array::key(CRM_Utils_Array::value('type', $field), CRM_Utils_Type::getValidTypes())) {
+        $fieldNameTypes[$name] = $type;
       }
       // it's necessary to know which of the fields are searchable by label
       if (isset($field['searchByLabel']) && $field['searchByLabel']) {
@@ -477,8 +469,10 @@ class CRM_Contact_Form_Search_Builder extends CRM_Contact_Form_Search {
             $options[substr($field, 0, -3)] = $entity;
           }
         }
-        elseif (!empty($info['data_type']) && in_array($info['data_type'], array('StateProvince', 'Country'))) {
-          $options[$field] = $entity;
+        elseif (!empty($info['data_type'])) {
+          if (in_array($info['data_type'], array('StateProvince', 'Country'))) {
+            $options[$field] = $entity;
+          }
         }
         elseif (in_array(substr($field, 0, 3), array(
               'is_',
diff --git a/civicrm/CRM/Contact/Form/Search/Criteria.php b/civicrm/CRM/Contact/Form/Search/Criteria.php
index 3c1b3e7ea9..52d7a10016 100644
--- a/civicrm/CRM/Contact/Form/Search/Criteria.php
+++ b/civicrm/CRM/Contact/Form/Search/Criteria.php
@@ -35,6 +35,7 @@ class CRM_Contact_Form_Search_Criteria {
    * @param CRM_Core_Form $form
    */
   public static function basic(&$form) {
+    self::setBasicSearchFields($form);
     $form->addElement('hidden', 'hidden_basic', 1);
 
     if ($form->_searchOptions['contactType']) {
@@ -69,7 +70,7 @@ class CRM_Contact_Form_Search_Criteria {
       $contactTags = CRM_Core_BAO_Tag::getTags();
 
       if ($contactTags) {
-        $form->add('select', 'contact_tags', ts('Tags'), $contactTags, FALSE,
+        $form->add('select', 'contact_tags', ts('Select Tag(s)'), $contactTags, FALSE,
           array('id' => 'contact_tags', 'multiple' => 'multiple', 'class' => 'crm-select2', 'style' => 'width: 100%;')
         );
       }
@@ -99,10 +100,10 @@ class CRM_Contact_Form_Search_Criteria {
     }
 
     // add text box for last name, first name, street name, city
-    $form->addElement('text', 'sort_name', ts('Find...'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name'));
+    $form->addElement('text', 'sort_name', ts('Complete OR Partial Name'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name'));
 
     // add text box for last name, first name, street name, city
-    $form->add('text', 'email', ts('Contact Email'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name'));
+    $form->add('text', 'email', ts('Complete OR Partial Email'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name'));
 
     //added contact source
     $form->add('text', 'contact_source', ts('Contact Source'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'contact_source'));
@@ -248,6 +249,74 @@ class CRM_Contact_Form_Search_Criteria {
     $form->add('select', 'phone_phone_type_id', ts('Phone Type'), array('' => ts('- any -')) + $phoneType, FALSE, array('class' => 'crm-select2'));
   }
 
+  /**
+   * Defines the fields that can be displayed for the basic search section.
+   *
+   * @param CRM_Core_Form $form
+   */
+  protected static function setBasicSearchFields($form) {
+    $userFramework = CRM_Core_Config::singleton()->userFramework;
+
+    $form->assign('basicSearchFields', [
+      'sort_name' => ['name' => 'sort_name'],
+      'email' => ['name' => 'email'],
+      'contact_type' => ['name' => 'contact_type'],
+      'group' => [
+        'name' => 'group',
+        'template' => 'CRM/Contact/Form/Search/Criteria/Fields/group.tpl',
+      ],
+      'contact_tags' => ['name' => 'contact_tags'],
+      'tag_types_text' => ['name' => 'tag_types_text'],
+      'tag_search' => [
+        'name' => 'tag_search',
+        'help' => ['id' => 'id-all-tags'],
+      ],
+      'tag_set' => [
+        'name' => 'tag_set',
+        'is_custom' => TRUE,
+        'template' => 'CRM/Contact/Form/Search/Criteria/Fields/tag_set.tpl',
+      ],
+      'all_tag_types' => [
+        'name' => 'all_tag_types',
+        'class' => 'search-field__span-3 search-field__checkbox',
+        'help' => ['id' => 'id-all-tag-types']
+      ],
+      'phone_numeric' => [
+        'name' => 'phone_numeric',
+        'description' => ts('Punctuation and spaces are ignored.'),
+      ],
+      'phone_location_type_id' => ['name' => 'phone_location_type_id'],
+      'phone_phone_type_id' => ['name' => 'phone_phone_type_id'],
+      'privacy_toggle' => [
+        'name' => 'privacy_toggle',
+        'class' => 'search-field__span-2',
+        'template' => 'CRM/Contact/Form/Search/Criteria/Fields/privacy_toggle.tpl',
+      ],
+      'preferred_communication_method' => [
+        'name' => 'preferred_communication_method',
+        'template' => 'CRM/Contact/Form/Search/Criteria/Fields/preferred_communication_method.tpl',
+      ],
+      'contact_source' => [
+        'name' => 'contact_source',
+        'help' => ['id' => 'id-source', 'file' => 'CRM/Contact/Form/Contact'],
+      ],
+      'job_title' => ['name' => 'job_title'],
+      'preferred_language' => ['name' => 'preferred_language'],
+      'contact_id' => [
+        'name' => 'contact_id',
+        'help' => ['id' => 'id-contact-id', 'file' => 'CRM/Contact/Form/Contact'],
+      ],
+      'external_identifier' => [
+        'name' => 'external_identifier',
+        'help' => ['id' => 'id-external-id', 'file' => 'CRM/Contact/Form/Contact'],
+      ],
+      'uf_user' => [
+        'name' => 'uf_user',
+        'description' => ts('Does the contact have a %1 Account?', [$userFramework]),
+      ],
+    ]);
+  }
+
 
   /**
    * @param CRM_Core_Form $form
diff --git a/civicrm/CRM/Contact/Form/Search/Custom/ContribSYBNT.php b/civicrm/CRM/Contact/Form/Search/Custom/ContribSYBNT.php
index bfd961b1c2..cf2b1e950b 100644
--- a/civicrm/CRM/Contact/Form/Search/Custom/ContribSYBNT.php
+++ b/civicrm/CRM/Contact/Form/Search/Custom/ContribSYBNT.php
@@ -192,10 +192,8 @@ ORDER BY   donation_amount desc
 ";
 
     if ($justIDs) {
-      CRM_Core_DAO::executeQuery("DROP TEMPORARY TABLE IF EXISTS CustomSearch_SYBNT_temp");
-      $query = "CREATE TEMPORARY TABLE CustomSearch_SYBNT_temp AS ({$sql})";
-      CRM_Core_DAO::executeQuery($query);
-      $sql = "SELECT contact_a.id as contact_id FROM CustomSearch_SYBNT_temp as contact_a";
+      $tempTable = CRM_Utils_SQL_TempTable::build()->createWithQuery($sql);
+      $sql = "SELECT contact_a.id as contact_id FROM {$tempTable->getName()} as contact_a";
     }
     return $sql;
   }
diff --git a/civicrm/CRM/Contact/Form/Search/Custom/DateAdded.php b/civicrm/CRM/Contact/Form/Search/Custom/DateAdded.php
index 3ec22422e3..34ec772f3c 100644
--- a/civicrm/CRM/Contact/Form/Search/Custom/DateAdded.php
+++ b/civicrm/CRM/Contact/Form/Search/Custom/DateAdded.php
@@ -36,6 +36,8 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
   protected $_aclFrom = NULL;
   protected $_aclWhere = NULL;
 
+  protected $_datesTable = NULL, $_xgTable = NULL, $_igTable = NULL;
+
   /**
    * Class constructor.
    *
@@ -177,11 +179,12 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
    */
   public function from() {
     //define table name
-    $randomNum = md5(uniqid());
-    $this->_tableName = "civicrm_temp_custom_{$randomNum}";
+    $this->_datesTable = CRM_Utils_SQL_TempTable::build()->setCategory('dates')->getName();
+    $this->_xgTable = CRM_Utils_SQL_TempTable::build()->setCategory('xg')->getName();
+    $this->_igTable = CRM_Utils_SQL_TempTable::build()->setCategory('ig')->getName();
 
     //grab the contacts added in the date range first
-    $sql = "CREATE TEMPORARY TABLE dates_{$this->_tableName} ( id int primary key, date_added date ) ENGINE=HEAP";
+    $sql = "CREATE TEMPORARY TABLE {$this->_datesTable} ( id int primary key, date_added date ) ENGINE=HEAP";
     if ($this->_debug > 0) {
       print "-- Date range query: <pre>";
       print "$sql;";
@@ -197,7 +200,7 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
       $endDateFix = "AND date_added <= '" . substr($endDate, 0, 10) . " 23:59:00'";
     }
 
-    $dateRange = "INSERT INTO dates_{$this->_tableName} ( id, date_added )
+    $dateRange = "INSERT INTO {$this->_datesTable} ( id, date_added )
           SELECT
               civicrm_contact.id,
               min(civicrm_log.modified_date) AS date_added
@@ -249,16 +252,16 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
         $xGroups = 0;
       }
 
-      $sql = "DROP TEMPORARY TABLE IF EXISTS Xg_{$this->_tableName}";
+      $sql = "DROP TEMPORARY TABLE IF EXISTS {$this->_xgTable}";
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
-      $sql = "CREATE TEMPORARY TABLE Xg_{$this->_tableName} ( contact_id int primary key) ENGINE=HEAP";
+      $sql = "CREATE TEMPORARY TABLE {$this->_xgTable} ( contact_id int primary key) ENGINE=HEAP";
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
 
       //used only when exclude group is selected
       if ($xGroups != 0) {
-        $excludeGroup = "INSERT INTO  Xg_{$this->_tableName} ( contact_id )
+        $excludeGroup = "INSERT INTO  {$this->_xgTable} ( contact_id )
                   SELECT  DISTINCT civicrm_group_contact.contact_id
-                  FROM civicrm_group_contact, dates_{$this->_tableName} AS d
+                  FROM civicrm_group_contact, {$this->_datesTable} AS d
                   WHERE
                      d.id = civicrm_group_contact.contact_id AND
                      civicrm_group_contact.status = 'Added' AND
@@ -277,16 +280,16 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
                               SELECT contact_id FROM civicrm_group_contact
                               WHERE civicrm_group_contact.group_id = {$values} AND civicrm_group_contact.status = 'Removed')";
 
-            $smartGroupQuery = " INSERT IGNORE INTO Xg_{$this->_tableName}(contact_id) $smartSql";
+            $smartGroupQuery = " INSERT IGNORE INTO {$this->_xgTable}(contact_id) $smartSql";
 
             CRM_Core_DAO::executeQuery($smartGroupQuery, CRM_Core_DAO::$_nullArray);
           }
         }
       }
 
-      $sql = "DROP TEMPORARY TABLE IF EXISTS Ig_{$this->_tableName}";
+      $sql = "DROP TEMPORARY TABLE IF EXISTS {$this->_igTable}";
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
-      $sql = "CREATE TEMPORARY TABLE Ig_{$this->_tableName}
+      $sql = "CREATE TEMPORARY TABLE {$this->_igTable}
                 ( id int PRIMARY KEY AUTO_INCREMENT,
                   contact_id int,
                   group_names varchar(64)) ENGINE=HEAP";
@@ -299,9 +302,9 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
 
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
 
-      $includeGroup = "INSERT INTO Ig_{$this->_tableName} (contact_id, group_names)
+      $includeGroup = "INSERT INTO {$this->_igTable} (contact_id, group_names)
                  SELECT      d.id as contact_id, civicrm_group.name as group_name
-                 FROM        dates_{$this->_tableName} AS d
+                 FROM        {$this->_datesTable} AS d
                  INNER JOIN  civicrm_group_contact
                  ON          civicrm_group_contact.contact_id = d.id
                  LEFT JOIN   civicrm_group
@@ -309,8 +312,8 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
 
       //used only when exclude group is selected
       if ($xGroups != 0) {
-        $includeGroup .= " LEFT JOIN        Xg_{$this->_tableName}
-                                          ON        d.id = Xg_{$this->_tableName}.contact_id";
+        $includeGroup .= " LEFT JOIN        {$this->_xgTable}
+                                          ON        d.id = {$this->_xgTable}.contact_id";
       }
       $includeGroup .= " WHERE
                                      civicrm_group_contact.status = 'Added'  AND
@@ -318,7 +321,7 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
 
       //used only when exclude group is selected
       if ($xGroups != 0) {
-        $includeGroup .= " AND  Xg_{$this->_tableName}.contact_id IS null";
+        $includeGroup .= " AND  {$this->_xgTable}.contact_id IS null";
       }
 
       if ($this->_debug > 0) {
@@ -339,7 +342,7 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
 
           $smartSql .= " AND contact_a.id IN (
                                    SELECT id AS contact_id
-                                   FROM dates_{$this->_tableName} )";
+                                   FROM {$this->_datesTable} )";
 
           $smartSql .= " AND contact_a.id NOT IN (
                                    SELECT contact_id FROM civicrm_group_contact
@@ -347,11 +350,11 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
 
           //used only when exclude group is selected
           if ($xGroups != 0) {
-            $smartSql .= " AND contact_a.id NOT IN (SELECT contact_id FROM  Xg_{$this->_tableName})";
+            $smartSql .= " AND contact_a.id NOT IN (SELECT contact_id FROM  {$this->_xgTable})";
           }
 
           $smartGroupQuery = " INSERT IGNORE INTO
-                        Ig_{$this->_tableName}(contact_id)
+                        {$this->_igTable}(contact_id)
                         $smartSql";
 
           CRM_Core_DAO::executeQuery($smartGroupQuery, CRM_Core_DAO::$_nullArray);
@@ -360,11 +363,11 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
             print "$smartGroupQuery;";
             print "</pre>";
           }
-          $insertGroupNameQuery = "UPDATE IGNORE Ig_{$this->_tableName}
+          $insertGroupNameQuery = "UPDATE IGNORE {$this->_igTable}
                         SET group_names = (SELECT title FROM civicrm_group
                             WHERE civicrm_group.id = $values)
-                        WHERE Ig_{$this->_tableName}.contact_id IS NOT NULL
-                            AND Ig_{$this->_tableName}.group_names IS NULL";
+                        WHERE {$this->_igTable}.contact_id IS NOT NULL
+                            AND {$this->_igTable}.group_names IS NULL";
           CRM_Core_DAO::executeQuery($insertGroupNameQuery, CRM_Core_DAO::$_nullArray);
           if ($this->_debug > 0) {
             print "-- Smart group query: <pre>";
@@ -380,12 +383,12 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
 
     /* We need to join to this again to get the date_added value */
 
-    $from .= " INNER JOIN dates_{$this->_tableName} d ON (contact_a.id = d.id) {$this->_aclFrom}";
+    $from .= " INNER JOIN {$this->_datesTable} d ON (contact_a.id = d.id) {$this->_aclFrom}";
 
     // Only include groups in the search query of one or more Include OR Exclude groups has been selected.
     // CRM-6356
     if ($this->_groups) {
-      $from .= " INNER JOIN Ig_{$this->_tableName} temptable1 ON (contact_a.id = temptable1.contact_id)";
+      $from .= " INNER JOIN {$this->_igTable} temptable1 ON (contact_a.id = temptable1.contact_id)";
     }
 
     return $from;
@@ -437,13 +440,13 @@ class CRM_Contact_Form_Search_Custom_DateAdded extends CRM_Contact_Form_Search_C
 
   public function __destruct() {
     //drop the temp. tables if they exist
-    if (!empty($this->_includeGroups)) {
-      $sql = "DROP TEMPORARY TABLE IF EXISTS Ig_{$this->_tableName}";
+    if ($this->_igTable && !empty($this->_includeGroups)) {
+      $sql = "DROP TEMPORARY TABLE IF EXISTS {$this->_igTable}";
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
     }
 
-    if (!empty($this->_excludeGroups)) {
-      $sql = "DROP TEMPORARY TABLE IF EXISTS  Xg_{$this->_tableName}";
+    if ($this->_xgTable && !empty($this->_excludeGroups)) {
+      $sql = "DROP TEMPORARY TABLE IF EXISTS {$this->_xgTable}";
       CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray);
     }
   }
diff --git a/civicrm/CRM/Contact/Form/Task.php b/civicrm/CRM/Contact/Form/Task.php
index 5136641e3b..2d6e7637bf 100644
--- a/civicrm/CRM/Contact/Form/Task.php
+++ b/civicrm/CRM/Contact/Form/Task.php
@@ -34,7 +34,7 @@
 /**
  * This class generates form components for search-result tasks.
  */
-class CRM_Contact_Form_Task extends CRM_Core_Form {
+class CRM_Contact_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * The task being performed
@@ -94,12 +94,15 @@ class CRM_Contact_Form_Task extends CRM_Core_Form {
    * Common pre-processing function.
    *
    * @param CRM_Core_Form $form
-   * @param bool $useTable
+   *
+   * @throws \CRM_Core_Exception
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_contactIds = array();
     $form->_contactTypes = array();
 
+    $useTable = (CRM_Utils_System::getClassName($form->controller->getStateMachine()) == 'CRM_Export_StateMachine_Standalone');
+
     $isStandAlone = in_array('task', $form->urlPath) || in_array('standalone', $form->urlPath);
     if ($isStandAlone) {
       list($form->_task, $title) = CRM_Contact_Task::getTaskAndTitleByClass(get_class($form));
@@ -150,7 +153,7 @@ class CRM_Contact_Form_Task extends CRM_Core_Form {
     $form->assign('taskName', CRM_Utils_Array::value($form->_task, $crmContactTaskTasks));
 
     if ($useTable) {
-      $form->_componentTable = CRM_Core_DAO::createTempTableName('civicrm_task_action', TRUE, $qfKey);
+      $form->_componentTable = CRM_Utils_SQL_TempTable::build()->setCategory('tskact')->setDurable()->setId($qfKey)->getName();
       $sql = " DROP TABLE IF EXISTS {$form->_componentTable}";
       CRM_Core_DAO::executeQuery($sql);
 
@@ -172,7 +175,7 @@ class CRM_Contact_Form_Task extends CRM_Core_Form {
         $allCids = CRM_Core_BAO_PrevNextCache::getSelection($cacheKey, "getall");
       }
       else {
-        $allCids[$cacheKey] = $form->getContactIds();
+        $allCids[$cacheKey] = self::getContactIds($form);
       }
 
       $form->_contactIds = array();
@@ -283,36 +286,41 @@ class CRM_Contact_Form_Task extends CRM_Core_Form {
   }
 
   /**
-   * Get the contact id for custom search.
+   * Get the contact ids for:
+   *   - "Select Records: All xx records"
+   *   - custom search (FIXME: does this still apply to custom search?).
+   * When we call this function we are not using the prev/next cache
+   *
+   * @param $form CRM_Core_Form
    *
-   * we are not using prev/next table in case of custom search
+   * @return array $contactIds
    */
-  public function getContactIds() {
+  public static function getContactIds($form) {
     // need to perform action on all contacts
     // fire the query again and get the contact id's + display name
     $sortID = NULL;
-    if ($this->get(CRM_Utils_Sort::SORT_ID)) {
-      $sortID = CRM_Utils_Sort::sortIDValue($this->get(CRM_Utils_Sort::SORT_ID),
-        $this->get(CRM_Utils_Sort::SORT_DIRECTION)
+    if ($form->get(CRM_Utils_Sort::SORT_ID)) {
+      $sortID = CRM_Utils_Sort::sortIDValue($form->get(CRM_Utils_Sort::SORT_ID),
+        $form->get(CRM_Utils_Sort::SORT_DIRECTION)
       );
     }
 
-    $selectorName = $this->controller->selectorName();
+    $selectorName = $form->controller->selectorName();
 
-    $fv = $this->get('formValues');
-    $customClass = $this->get('customSearchClass');
+    $fv = $form->get('formValues');
+    $customClass = $form->get('customSearchClass');
     $returnProperties = CRM_Core_BAO_Mapping::returnProperties(self::$_searchFormValues);
 
     $selector = new $selectorName($customClass, $fv, NULL, $returnProperties);
 
-    $params = $this->get('queryParams');
+    $params = $form->get('queryParams');
 
     // fix for CRM-5165
-    $sortByCharacter = $this->get('sortByCharacter');
+    $sortByCharacter = $form->get('sortByCharacter');
     if ($sortByCharacter && $sortByCharacter != 1) {
       $params[] = array('sortByCharacter', '=', $sortByCharacter, 0, 0);
     }
-    $queryOperator = $this->get('queryOperator');
+    $queryOperator = $form->get('queryOperator');
     if (!$queryOperator) {
       $queryOperator = 'AND';
     }
diff --git a/civicrm/CRM/Contact/Import/Parser.php b/civicrm/CRM/Contact/Import/Parser.php
index 552ec453f6..6a40dea23f 100644
--- a/civicrm/CRM/Contact/Import/Parser.php
+++ b/civicrm/CRM/Contact/Import/Parser.php
@@ -275,9 +275,6 @@ abstract class CRM_Contact_Import_Parser extends CRM_Import_Parser {
         break;
       }
 
-      // clean up memory from dao's
-      CRM_Core_DAO::freeResult();
-
       // see if we've hit our timeout yet
       /* if ( $the_thing_with_the_stuff ) {
       do_something( );
diff --git a/civicrm/CRM/Contact/Task.php b/civicrm/CRM/Contact/Task.php
index 71cf992bda..f314b02849 100644
--- a/civicrm/CRM/Contact/Task.php
+++ b/civicrm/CRM/Contact/Task.php
@@ -297,18 +297,17 @@ class CRM_Contact_Task extends CRM_Core_Task {
         self::LABEL_CONTACTS => self::$_tasks[self::LABEL_CONTACTS]['title'],
       );
 
-      if (isset(self::$_tasks[self::MAP_CONTACTS]) &&
-        !empty(self::$_tasks[self::MAP_CONTACTS]['title'])
-      ) {
-        $tasks[self::MAP_CONTACTS] = self::$_tasks[self::MAP_CONTACTS]['title'];
-      }
-
-      if (isset(self::$_tasks[self::CREATE_MAILING]) &&
-        !empty(self::$_tasks[self::CREATE_MAILING]['title'])
-      ) {
-        $tasks[self::CREATE_MAILING] = self::$_tasks[self::CREATE_MAILING]['title'];
+      foreach ([
+        self::MAP_CONTACTS,
+        self::CREATE_MAILING,
+        self::TASK_SMS
+      ] as $task) {
+        if (isset(self::$_tasks[$task]) &&
+          !empty(self::$_tasks[$task]['title'])
+        ) {
+          $tasks[$task] = self::$_tasks[$task]['title'];
+        }
       }
-
     }
 
     $tasks = parent::corePermissionedTaskTitles($tasks, $permission, $params);
diff --git a/civicrm/CRM/Contribute/BAO/Contribution.php b/civicrm/CRM/Contribute/BAO/Contribution.php
index f58bd9f9ea..5682ae6fea 100644
--- a/civicrm/CRM/Contribute/BAO/Contribution.php
+++ b/civicrm/CRM/Contribute/BAO/Contribution.php
@@ -1967,7 +1967,7 @@ LEFT JOIN  civicrm_contribution contribution ON ( componentPayment.contribution_
             $membershipLog['modified_date'] = date('Ymd');
             $membershipLog['membership_type_id'] = $membership->membership_type_id;
 
-            CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray);
+            CRM_Member_BAO_MembershipLog::add($membershipLog);
 
             //update related Memberships.
             CRM_Member_BAO_Membership::updateRelatedMemberships($membership->id, $formattedParams);
@@ -2441,7 +2441,7 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac
       }
     }
 
-    $this->loadRelatedMembershipObjects($ids);
+    $ids = $this->loadRelatedMembershipObjects($ids);
 
     if ($this->_component != 'contribute') {
       // we are in event mode
@@ -3840,7 +3840,6 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac
    * @return null|object
    */
   public static function recordAdditionalPayment($contributionId, $trxnsData, $paymentType = 'owed', $participantId = NULL, $updateStatus = TRUE) {
-    $statusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
     $getInfoOf['id'] = $contributionId;
     $defaults = array();
     $contributionDAO = CRM_Contribute_BAO_Contribution::retrieve($getInfoOf, $defaults, CRM_Core_DAO::$_nullArray);
@@ -3849,8 +3848,7 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac
     }
 
     // load related memberships on basis of $contributionDAO object
-    $membershipIDs = array();
-    $contributionDAO->loadRelatedMembershipObjects($membershipIDs);
+    $contributionDAO->loadRelatedMembershipObjects();
 
     // build params for recording financial trxn entry
     $params['contribution'] = $contributionDAO;
@@ -3859,7 +3857,7 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac
     $trxnsData['trxn_date'] = !empty($trxnsData['trxn_date']) ? $trxnsData['trxn_date'] : date('YmdHis');
     $arAccountId = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($contributionDAO->financial_type_id, 'Accounts Receivable Account is');
 
-    // get the paid status id
+    $completedStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
     $paidStatus = CRM_Core_PseudoConstant::getKey('CRM_Financial_DAO_FinancialItem', 'status_id', 'Paid');
 
     if ($paymentType == 'owed') {
@@ -3894,7 +3892,7 @@ LEFT JOIN civicrm_entity_financial_trxn eft
 WHERE eft.entity_table = 'civicrm_contribution'
   AND eft.entity_id = {$contributionId}
   AND ft.to_financial_account_id != {$toFinancialAccount}
-  AND ft.status_id = {$statusId}
+  AND ft.status_id = {$completedStatusId}
 ";
       $query = CRM_Core_DAO::executeQuery($sql);
       $query->fetch();
@@ -3904,7 +3902,7 @@ WHERE eft.entity_table = 'civicrm_contribution'
       if ($contributionDAO->total_amount == $sumOfPayments) {
         // update contribution status and
         // clean cancel info (if any) if prev. contribution was updated in case of 'Refunded' => 'Completed'
-        $contributionDAO->contribution_status_id = $statusId;
+        $contributionDAO->contribution_status_id = $completedStatusId;
         $contributionDAO->cancel_date = 'null';
         $contributionDAO->cancel_reason = NULL;
         $netAmount = !empty($trxnsData['net_amount']) ? NULL : $trxnsData['total_amount'];
@@ -3913,7 +3911,7 @@ WHERE eft.entity_table = 'civicrm_contribution'
         $contributionDAO->save();
 
         //Change status of financial record too
-        $financialTrxn->status_id = $statusId;
+        $financialTrxn->status_id = $completedStatusId;
         $financialTrxn->save();
 
         // note : not using the self::add method,
@@ -3932,15 +3930,11 @@ WHERE eft.entity_table = 'civicrm_contribution'
           }
         }
 
-        // update membership details
-        if (!empty($contributionDAO->_relatedObjects['membership'])) {
-          self::updateMembershipBasedOnCompletionOfContribution(
-            $contributionDAO,
-            $contributionDAO->_relatedObjects['membership'],
-            $contributionId,
-            $trxnsData['trxn_date']
-          );
-        }
+        self::updateMembershipBasedOnCompletionOfContribution(
+          $contributionDAO,
+          $contributionId,
+          $trxnsData['trxn_date']
+        );
 
         // update financial item statuses
         $baseTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contributionId);
@@ -3966,7 +3960,7 @@ WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']})
       // which in 'Pending Refund' => 'Completed' is not useful, instead specific financial record updates
       // are coded below i.e. just updating financial_item status to 'Paid'
       if ($updateStatus) {
-        $contributionDetails = CRM_Core_DAO::setFieldValue('CRM_Contribute_BAO_Contribution', $contributionId, 'contribution_status_id', $statusId);
+        CRM_Core_DAO::setFieldValue('CRM_Contribute_BAO_Contribution', $contributionId, 'contribution_status_id', $completedStatusId);
       }
       // add financial item entry
       $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($contributionDAO->id);
@@ -4553,7 +4547,6 @@ WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']})
     }
 
     $participant = CRM_Utils_Array::value('participant', $objects);
-    $memberships = CRM_Utils_Array::value('membership', $objects);
     $recurContrib = CRM_Utils_Array::value('contributionRecur', $objects);
     $recurringContributionID = (empty($recurContrib->id)) ? NULL : $recurContrib->id;
     $event = CRM_Utils_Array::value('event', $objects);
@@ -4601,10 +4594,6 @@ WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']})
     self::repeatTransaction($contribution, $input, $contributionParams, $paymentProcessorId);
     $contributionParams['financial_type_id'] = $contribution->financial_type_id;
 
-    if (is_numeric($memberships)) {
-      $memberships = array($objects['membership']);
-    }
-
     $values = array();
     if (isset($input['is_email_receipt'])) {
       $values['is_email_receipt'] = $input['is_email_receipt'];
@@ -4632,15 +4621,12 @@ WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']})
         $values['is_email_receipt'] = $recurContrib->is_email_receipt;
       }
 
-      if (!empty($memberships)) {
-        self::updateMembershipBasedOnCompletionOfContribution(
-          $contribution,
-          $memberships,
-          $primaryContributionID,
-          $changeDate,
-          CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_Contribution', 'contribution_status_id', CRM_Utils_Array::value('contribution_status_id', $input))
-        );
-      }
+      self::updateMembershipBasedOnCompletionOfContribution(
+        $contribution,
+        $primaryContributionID,
+        $changeDate,
+        CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_Contribution', 'contribution_status_id', CRM_Utils_Array::value('contribution_status_id', $input))
+      );
     }
     else {
       if (empty($input['IAmAHorribleNastyBeyondExcusableHackInTheCRMEventFORMTaskClassThatNeedsToBERemoved'])) {
@@ -4823,9 +4809,11 @@ WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']})
    *
    * @param array $ids
    *
+   * @return array $ids
+   *
    * @throws Exception
    */
-  public function loadRelatedMembershipObjects(&$ids) {
+  public function loadRelatedMembershipObjects($ids = []) {
     $query = "
       SELECT membership_id
       FROM   civicrm_membership_payment
@@ -4856,6 +4844,7 @@ WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']})
         }
       }
     }
+    return $ids;
   }
 
   /**
@@ -5400,7 +5389,6 @@ LEFT JOIN  civicrm_contribution on (civicrm_contribution.contact_id = civicrm_co
    * load them in this function. Code clean up would compensate for any minor performance implication.
    *
    * @param \CRM_Contribute_BAO_Contribution $contribution
-   * @param array $memberships
    * @param int $primaryContributionID
    * @param string $changeDate
    * @param string $contributionStatus
@@ -5408,7 +5396,12 @@ LEFT JOIN  civicrm_contribution on (civicrm_contribution.contact_id = civicrm_co
    *
    * @todo investigate completely bypassing this function if $contributionStatus != Completed.
    */
-  protected static function updateMembershipBasedOnCompletionOfContribution($contribution, $memberships, $primaryContributionID, $changeDate, $contributionStatus = 'Completed') {
+  protected static function updateMembershipBasedOnCompletionOfContribution($contribution, $primaryContributionID, $changeDate, $contributionStatus = 'Completed') {
+    $contribution->loadRelatedMembershipObjects();
+    if (empty($contribution->_relatedObjects['membership'])) {
+      return;
+    }
+    $memberships = $contribution->_relatedObjects['membership'];
     foreach ($memberships as $membershipTypeIdKey => $membership) {
       if ($membership) {
         $membershipParams = array(
diff --git a/civicrm/CRM/Contribute/BAO/Contribution/Utils.php b/civicrm/CRM/Contribute/BAO/Contribution/Utils.php
index 2b99789923..0b2c99b898 100644
--- a/civicrm/CRM/Contribute/BAO/Contribution/Utils.php
+++ b/civicrm/CRM/Contribute/BAO/Contribution/Utils.php
@@ -74,6 +74,8 @@ class CRM_Contribute_BAO_Contribution_Utils {
 
     // add some financial type details to the params list
     // if folks need to use it
+    //CRM-15297 deprecate contributionTypeID
+    $paymentParams['financial_type_id'] = $paymentParams['financialTypeID'] = $paymentParams['contributionTypeID'] = $financialType->id;
     //CRM-15297 - contributionType is obsolete - pass financial type as well so people can deprecate it
     $paymentParams['financialType_name'] = $paymentParams['contributionType_name'] = $form->_params['contributionType_name'] = $financialType->name;
     //CRM-11456
@@ -154,8 +156,6 @@ class CRM_Contribute_BAO_Contribution_Utils {
       }
 
       $paymentParams['contributionID'] = $contribution->id;
-      //CRM-15297 deprecate contributionTypeID
-      $paymentParams['financialTypeID'] = $paymentParams['contributionTypeID'] = $contribution->financial_type_id;
       $paymentParams['contributionPageID'] = $contribution->contribution_page_id;
       if (isset($paymentParams['contribution_source'])) {
         $paymentParams['source'] = $paymentParams['contribution_source'];
diff --git a/civicrm/CRM/Contribute/BAO/ContributionRecur.php b/civicrm/CRM/Contribute/BAO/ContributionRecur.php
index aa1d02b5ab..a5c76722c4 100644
--- a/civicrm/CRM/Contribute/BAO/ContributionRecur.php
+++ b/civicrm/CRM/Contribute/BAO/ContributionRecur.php
@@ -32,6 +32,13 @@
  */
 class CRM_Contribute_BAO_ContributionRecur extends CRM_Contribute_DAO_ContributionRecur {
 
+  /**
+   * Array with statuses that mark a recurring contribution as inactive.
+   *
+   * @var array
+   */
+  private static $inactiveStatuses = array('Cancelled', 'Chargeback', 'Refunded', 'Completed');
+
   /**
    * Create recurring contribution.
    *
@@ -233,15 +240,12 @@ SELECT r.payment_processor_id
    *
    * @param int $recurId
    *   Recur contribution id.
-   * @param array $objects
-   *   An array of objects that is to be cancelled like.
-   *                          contribution, membership, event. At least contribution object is a must.
    *
    * @param array $activityParams
    *
    * @return bool
    */
-  public static function cancelRecurContribution($recurId, $objects, $activityParams = array()) {
+  public static function cancelRecurContribution($recurId, $activityParams = array()) {
     if (!$recurId) {
       return FALSE;
     }
@@ -282,7 +286,7 @@ SELECT r.payment_processor_id
         }
         $activityParams = array(
           'source_contact_id' => $dao->contact_id,
-          'source_record_id' => CRM_Utils_Array::value('source_record_id', $activityParams),
+          'source_record_id' => $dao->recur_id,
           'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Cancel Recurring Contribution'),
           'subject' => CRM_Utils_Array::value('subject', $activityParams, ts('Recurring contribution cancelled')),
           'details' => $details,
@@ -299,16 +303,8 @@ SELECT r.payment_processor_id
         CRM_Activity_BAO_Activity::create($activityParams);
       }
 
-      // if there are associated objects, cancel them as well
-      if (!$objects) {
-        $transaction->commit();
-        return TRUE;
-      }
-      else {
-        // @todo - this is bad! Get the function out of the ipn.
-        $baseIPN = new CRM_Core_Payment_BaseIPN();
-        return $baseIPN->cancelled($objects, $transaction);
-      }
+      $transaction->commit();
+      return TRUE;
     }
     else {
       // if already cancelled, return true
@@ -938,4 +934,14 @@ INNER JOIN civicrm_contribution       con ON ( con.id = mp.contribution_id )
     return $lineSets;
   }
 
+  /**
+   * Returns array with statuses that are considered to make a recurring
+   * contribution inacteve.
+   *
+   * @return array
+   */
+  public static function getInactiveStatuses() {
+    return self::$inactiveStatuses;
+  }
+
 }
diff --git a/civicrm/CRM/Contribute/Exception/FutureContributionPageException.php b/civicrm/CRM/Contribute/Exception/FutureContributionPageException.php
new file mode 100644
index 0000000000..5deeb178f4
--- /dev/null
+++ b/civicrm/CRM/Contribute/Exception/FutureContributionPageException.php
@@ -0,0 +1,25 @@
+<?php
+
+class CRM_Contribute_Exception_FutureContributionPageException extends Exception {
+  private $id;
+
+  /**
+   * @param string $message
+   * @param int $id
+   */
+  public function __construct($message, $id) {
+    parent::__construct(ts($message));
+    $this->id = $id;
+    CRM_Core_Error::debug_log_message('Access to contribution page with start date in future attempted - page number ' . $id);
+  }
+
+  /**
+   * Get Contribution page ID.
+   *
+   * @return int
+   */
+  public function getID() {
+    return $this->id;
+  }
+
+}
diff --git a/civicrm/CRM/Contribute/Exception/PastContributionPageException.php b/civicrm/CRM/Contribute/Exception/PastContributionPageException.php
new file mode 100644
index 0000000000..fc7c8b3182
--- /dev/null
+++ b/civicrm/CRM/Contribute/Exception/PastContributionPageException.php
@@ -0,0 +1,25 @@
+<?php
+
+class CRM_Contribute_Exception_PastContributionPageException extends Exception {
+  private $id;
+
+  /**
+   * @param string $message
+   * @param int $id
+   */
+  public function __construct($message, $id) {
+    parent::__construct(ts($message));
+    $this->id = $id;
+    CRM_Core_Error::debug_log_message('Access to contribution page with past end date attempted - page number ' . $id);
+  }
+
+  /**
+   * Get Contribution page ID.
+   *
+   * @return int
+   */
+  public function getID() {
+    return $this->id;
+  }
+
+}
diff --git a/civicrm/CRM/Contribute/Form/CancelSubscription.php b/civicrm/CRM/Contribute/Form/CancelSubscription.php
index 06941d9035..ec4aabd93b 100644
--- a/civicrm/CRM/Contribute/Form/CancelSubscription.php
+++ b/civicrm/CRM/Contribute/Form/CancelSubscription.php
@@ -224,7 +224,6 @@ class CRM_Contribute_Form_CancelSubscription extends CRM_Core_Form {
         );
       $cancelStatus = CRM_Contribute_BAO_ContributionRecur::cancelRecurContribution(
         $this->_subscriptionDetails->recur_id,
-        NULL,
         $activityParams
       );
 
diff --git a/civicrm/CRM/Contribute/Form/Contribution/Confirm.php b/civicrm/CRM/Contribute/Form/Contribution/Confirm.php
index 17a0bd2f92..a8b19f13e0 100644
--- a/civicrm/CRM/Contribute/Form/Contribution/Confirm.php
+++ b/civicrm/CRM/Contribute/Form/Contribution/Confirm.php
@@ -740,21 +740,18 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
    *
    * Comments from previous refactor indicate doubt as to what was going on.
    *
-   * @param int $contributionTypeId
+   * @param int $financialTypeID
    *
    * @return null|string
    */
-  public function wrangleFinancialTypeID($contributionTypeId) {
-    if (isset($paymentParams['financial_type'])) {
-      $contributionTypeId = $paymentParams['financial_type'];
-    }
-    elseif (!empty($this->_values['pledge_id'])) {
-      $contributionTypeId = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge',
+  public function wrangleFinancialTypeID($financialTypeID) {
+    if (empty($financialTypeID) && !empty($this->_values['pledge_id'])) {
+      $financialTypeID = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge',
         $this->_values['pledge_id'],
         'financial_type_id'
       );
     }
-    return $contributionTypeId;
+    return $financialTypeID;
   }
 
   /**
@@ -1997,6 +1994,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
       }
     }
     $form->set('memberPriceFieldIDS', $membershipPriceFieldIDs);
+    $form->setRecurringMembershipParams();
     $form->processFormSubmission(CRM_Utils_Array::value('contact_id', $params));
   }
 
@@ -2395,26 +2393,19 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
     $priceFieldIds = $this->get('memberPriceFieldIDS');
 
     if (!empty($priceFieldIds)) {
-      $financialTypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceFieldIds['id'], 'financial_type_id');
+      $membershipParams['financial_type_id'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceFieldIds['id'], 'financial_type_id');
       unset($priceFieldIds['id']);
       $membershipTypeIds = array();
       $membershipTypeTerms = array();
       foreach ($priceFieldIds as $priceFieldId) {
-        if ($id = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_type_id')) {
-          $membershipTypeIds[] = $id;
-          //@todo the value for $term is immediately overwritten. It is unclear from the code whether it was intentional to
-          // do this or a double = was intended (this ambiguity is the reason many IDEs complain about 'assignment in condition'
-          $term = 1;
-          if ($term = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_num_terms')) {
-            $membershipTypeTerms[$id] = ($term > 1) ? $term : 1;
-          }
-          else {
-            $membershipTypeTerms[$id] = 1;
-          }
+        $membershipTypeId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_type_id');
+        if ($membershipTypeId) {
+          $membershipTypeIds[] = $membershipTypeId;
+          $term = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_num_terms') ?: 1;
+          $membershipTypeTerms[$membershipTypeId] = ($term > 1) ? $term : 1;
         }
       }
       $membershipParams['selectMembership'] = $membershipTypeIds;
-      $membershipParams['financial_type_id'] = $financialTypeID;
       $membershipParams['types_terms'] = $membershipTypeTerms;
     }
     if (!empty($membershipParams['selectMembership'])) {
diff --git a/civicrm/CRM/Contribute/Form/Contribution/Main.php b/civicrm/CRM/Contribute/Form/Contribution/Main.php
index 9d7ed7c59b..89b92fbf3e 100644
--- a/civicrm/CRM/Contribute/Form/Contribution/Main.php
+++ b/civicrm/CRM/Contribute/Form/Contribution/Main.php
@@ -1300,6 +1300,9 @@ class CRM_Contribute_Form_Contribution_Main extends CRM_Contribute_Form_Contribu
     if (empty($this->_ccid)) {
       return;
     }
+    if (!$this->getContactID()) {
+      CRM_Core_Error::statusBounce(ts("Returning since there is no contact attached to this contribution id."));
+    }
 
     $payment = CRM_Contribute_BAO_Contribution::getPaymentInfo($this->_ccid, 'contribution');
     //bounce if the contribution is not pending.
diff --git a/civicrm/CRM/Contribute/Form/ContributionBase.php b/civicrm/CRM/Contribute/Form/ContributionBase.php
index 97a06e145c..561adfe00d 100644
--- a/civicrm/CRM/Contribute/Form/ContributionBase.php
+++ b/civicrm/CRM/Contribute/Form/ContributionBase.php
@@ -313,6 +313,17 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
         throw new CRM_Contribute_Exception_InactiveContributionPageException(ts('The page you requested is currently unavailable.'), $this->_id);
       }
 
+      $endDate = CRM_Utils_Date::processDate(CRM_Utils_Array::value('end_date', $this->_values));
+      $now = date('YmdHis');
+      if ($endDate && $endDate < $now) {
+        throw new CRM_Contribute_Exception_PastContributionPageException(ts('The page you requested has past its end date on ' . CRM_Utils_Date::customFormat($endDate)), $this->_id);
+      }
+
+      $startDate = CRM_Utils_Date::processDate(CRM_Utils_Array::value('start_date', $this->_values));
+      if ($startDate && $startDate > $now) {
+        throw new CRM_Contribute_Exception_FutureContributionPageException(ts('The page you requested will be active from ' . CRM_Utils_Date::customFormat($startDate)), $this->_id);
+      }
+
       $this->assignBillingType();
 
       // check for is_monetary status
@@ -603,11 +614,7 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
           $this->assign($paymentField, $this->_params[$paymentField]);
         }
       }
-      $paymentFieldsetLabel = ts('%1 Information', array($paymentProcessorObject->getPaymentTypeLabel()));
-      if (empty($paymentFields)) {
-        $paymentFieldsetLabel = '';
-      }
-      $this->assign('paymentFieldsetLabel', $paymentFieldsetLabel);
+      $this->assign('paymentFieldsetLabel', CRM_Core_Payment_Form::getPaymentLabel($paymentProcessorObject));
       $this->assign('paymentFields', $paymentFields);
 
     }
diff --git a/civicrm/CRM/Contribute/Form/ContributionPage.php b/civicrm/CRM/Contribute/Form/ContributionPage.php
index 238353ca07..bc3f23d03e 100644
--- a/civicrm/CRM/Contribute/Form/ContributionPage.php
+++ b/civicrm/CRM/Contribute/Form/ContributionPage.php
@@ -332,18 +332,13 @@ class CRM_Contribute_Form_ContributionPage extends CRM_Core_Form {
       if ($this->_priceSetID) {
         $defaults['price_set_id'] = $this->_priceSetID;
       }
-
-      if (!empty($defaults['end_date'])) {
-        list($defaults['end_date'], $defaults['end_date_time']) = CRM_Utils_Date::setDateDefaults($defaults['end_date']);
-      }
-
-      if (!empty($defaults['start_date'])) {
-        list($defaults['start_date'], $defaults['start_date_time']) = CRM_Utils_Date::setDateDefaults($defaults['start_date']);
-      }
     }
     else {
       $defaults['is_active'] = 1;
       // set current date as start date
+      // @todo look to change to $defaults['start_date'] = date('Ymd His');
+      // main settings form overrides this to implement above but this is left here
+      // 'in case' another extending form uses start_date - for now
       list($defaults['start_date'], $defaults['start_date_time']) = CRM_Utils_Date::setDateDefaults();
     }
 
@@ -353,7 +348,7 @@ class CRM_Contribute_Form_ContributionPage extends CRM_Core_Form {
       ), '1');
     }
     else {
-      # CRM 10860
+      // CRM-10860
       $defaults['recur_frequency_unit'] = array('month' => 1);
     }
 
diff --git a/civicrm/CRM/Contribute/Form/ContributionPage/Settings.php b/civicrm/CRM/Contribute/Form/ContributionPage/Settings.php
index 49e595bfec..7e94089ef4 100644
--- a/civicrm/CRM/Contribute/Form/ContributionPage/Settings.php
+++ b/civicrm/CRM/Contribute/Form/ContributionPage/Settings.php
@@ -44,6 +44,11 @@ class CRM_Contribute_Form_ContributionPage_Settings extends CRM_Contribute_Form_
    */
   public function setDefaultValues() {
     $defaults = parent::setDefaultValues();
+    // @todo handle properly on parent.
+    if (!$this->_id) {
+      $defaults['start_date'] = date('Y-m-d H:i:s');
+      unset($defaults['start_time']);
+    }
     $soft_credit_types = CRM_Core_OptionGroup::values('soft_credit_type', TRUE, FALSE, FALSE, NULL, 'name');
 
     if ($this->_id) {
@@ -223,8 +228,8 @@ class CRM_Contribute_Form_ContributionPage_Settings extends CRM_Contribute_Form_
     }
 
     // add optional start and end dates
-    $this->addDateTime('start_date', ts('Start Date'));
-    $this->addDateTime('end_date', ts('End Date'));
+    $this->add('datepicker', 'start_date', ts('Start Date'));
+    $this->add('datepicker', 'end_date', ts('End Date'));
 
     $this->addFormRule(array('CRM_Contribute_Form_ContributionPage_Settings', 'formRule'), $this);
 
@@ -334,10 +339,6 @@ class CRM_Contribute_Form_ContributionPage_Settings extends CRM_Contribute_Form_
     $params['is_credit_card_only'] = CRM_Utils_Array::value('is_credit_card_only', $params, FALSE);
     $params['honor_block_is_active'] = CRM_Utils_Array::value('honor_block_is_active', $params, FALSE);
     $params['is_for_organization'] = !empty($params['is_organization']) ? CRM_Utils_Array::value('is_for_organization', $params, FALSE) : 0;
-
-    $params['start_date'] = CRM_Utils_Date::processDate($params['start_date'], $params['start_date_time'], TRUE);
-    $params['end_date'] = CRM_Utils_Date::processDate($params['end_date'], $params['end_date_time'], TRUE);
-
     $params['goal_amount'] = CRM_Utils_Rule::cleanMoney($params['goal_amount']);
 
     if (!$params['honor_block_is_active']) {
diff --git a/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php b/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php
index df8905af0a..9d8a621354 100644
--- a/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php
+++ b/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php
@@ -85,55 +85,55 @@ class CRM_Contribute_Form_ContributionPage_Widget extends CRM_Contribute_Form_Co
     $this->_colorFields = array(
       'color_title' => array(
         ts('Title Text Color'),
-        'text',
+        'color',
         FALSE,
         '#2786C2',
       ),
       'color_bar' => array(
         ts('Progress Bar Color'),
-        'text',
+        'color',
         FALSE,
         '#2786C2',
       ),
       'color_main_text' => array(
         ts('Additional Text Color'),
-        'text',
+        'color',
         FALSE,
         '#FFFFFF',
       ),
       'color_main' => array(
         ts('Background Color'),
-        'text',
+        'color',
         FALSE,
         '#96C0E7',
       ),
       'color_main_bg' => array(
         ts('Background Color Top Area'),
-        'text',
+        'color',
         FALSE,
         '#B7E2FF',
       ),
       'color_bg' => array(
         ts('Border Color'),
-        'text',
+        'color',
         FALSE,
         '#96C0E7',
       ),
       'color_about_link' => array(
         ts('Button Text Color'),
-        'text',
+        'color',
         FALSE,
         '#556C82',
       ),
       'color_button' => array(
         ts('Button Background Color'),
-        'text',
+        'color',
         FALSE,
         '#FFFFFF',
       ),
       'color_homepage_link' => array(
         ts('Homepage Link Color'),
-        'text',
+        'color',
         FALSE,
         '#FFFFFF',
       ),
diff --git a/civicrm/CRM/Contribute/Form/Task.php b/civicrm/CRM/Contribute/Form/Task.php
index 2964016264..f089448db4 100644
--- a/civicrm/CRM/Contribute/Form/Task.php
+++ b/civicrm/CRM/Contribute/Form/Task.php
@@ -32,30 +32,10 @@
  */
 
 /**
- * This class generates form components for relationship.
+ * Class for contribute form task actions.
+ * FIXME: This needs refactoring to properly inherit from CRM_Core_Form_Task and share more functions.
  */
-class CRM_Contribute_Form_Task extends CRM_Core_Form {
-
-  /**
-   * The task being performed.
-   *
-   * @var int
-   */
-  protected $_task;
-
-  /**
-   * The additional clause that we restrict the search with.
-   *
-   * @var string
-   */
-  protected $_componentClause = NULL;
-
-  /**
-   * The array that holds all the component ids.
-   *
-   * @var array
-   */
-  protected $_componentIds;
+class CRM_Contribute_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * The array that holds all the contribution ids.
@@ -64,13 +44,6 @@ class CRM_Contribute_Form_Task extends CRM_Core_Form {
    */
   protected $_contributionIds;
 
-  /**
-   * The array that holds all the contact ids.
-   *
-   * @var array
-   */
-  public $_contactIds;
-
   /**
    * The array that holds all the mapping contribution and contact ids.
    *
@@ -94,9 +67,8 @@ class CRM_Contribute_Form_Task extends CRM_Core_Form {
 
   /**
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_contributionIds = array();
 
     $values = $form->controller->exportValues($form->get('searchFormName'));
@@ -217,7 +189,7 @@ class CRM_Contribute_Form_Task extends CRM_Core_Form {
    */
   public function setContactIDs() {
     if (!$this->_includesSoftCredits) {
-      $this->_contactIds = &CRM_Core_DAO::getContactIDsFromComponent(
+      $this->_contactIds = CRM_Core_DAO::getContactIDsFromComponent(
         $this->_contributionIds,
         'civicrm_contribution'
       );
diff --git a/civicrm/CRM/Contribute/Page/Tab.php b/civicrm/CRM/Contribute/Page/Tab.php
index c8260fad81..b5c20a9e0c 100644
--- a/civicrm/CRM/Contribute/Page/Tab.php
+++ b/civicrm/CRM/Contribute/Page/Tab.php
@@ -166,59 +166,106 @@ class CRM_Contribute_Page_Tab extends CRM_Core_Page {
    * Get all the recurring contribution information and assign to the template
    */
   private function addRecurringContributionsBlock() {
+    $activeContributions = $this->getActiveRecurringContributions();
+    $inactiveRecurringContributions = $this->getInactiveRecurringContributions();
+
+    if (!empty($activeContributions) || !empty($inactiveRecurringContributions)) {
+      // assign vars to templates
+      $this->assign('action', $this->_action);
+      $this->assign('activeRecurRows', $activeContributions);
+      $this->assign('inactiveRecurRows', $inactiveRecurringContributions);
+      $this->assign('recur', TRUE);
+    }
+  }
+
+  /**
+   * Loads active recurring contributions for the current contact and formats
+   * them to be used on the form.
+   *
+   * @return array;
+   */
+  private function getActiveRecurringContributions() {
     try {
       $contributionRecurResult = civicrm_api3('ContributionRecur', 'get', array(
         'contact_id' => $this->_contactId,
+        'contribution_status_id' => array('NOT IN' => CRM_Contribute_BAO_ContributionRecur::getInactiveStatuses()),
         'options' => array('limit' => 0, 'sort' => 'start_date ASC'),
       ));
       $recurContributions = CRM_Utils_Array::value('values', $contributionRecurResult);
     }
     catch (Exception $e) {
-      $recurContributions = NULL;
+      $recurContributions = array();
     }
 
-    if (!empty($recurContributions)) {
-      foreach ($recurContributions as $recurId => $recurDetail) {
-        $action = array_sum(array_keys($this->recurLinks($recurId)));
-        // no action allowed if it's not active
-        $recurContributions[$recurId]['is_active'] = (!CRM_Contribute_BAO_Contribution::isContributionStatusNegative($recurDetail['contribution_status_id']));
+    return $this->buildRecurringContributionsArray($recurContributions);
+  }
 
-        // Get the name of the payment processor
-        if (!empty($recurDetail['payment_processor_id'])) {
-          $recurContributions[$recurId]['payment_processor'] = CRM_Financial_BAO_PaymentProcessor::getPaymentProcessorName($recurDetail['payment_processor_id']);
-        }
-        // Get the label for the contribution status
-        if (!empty($recurDetail['contribution_status_id'])) {
-          $recurContributions[$recurId]['contribution_status'] = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', $recurDetail['contribution_status_id']);
-        }
+  /**
+   * Loads inactive recurring contributions for the current contact and formats
+   * them to be used on the form.
+   *
+   * @return array;
+   */
+  private function getInactiveRecurringContributions() {
+    try {
+      $contributionRecurResult = civicrm_api3('ContributionRecur', 'get', array(
+        'contact_id' => $this->_contactId,
+        'contribution_status_id' => array('IN' => CRM_Contribute_BAO_ContributionRecur::getInactiveStatuses()),
+        'options' => array('limit' => 0, 'sort' => 'start_date ASC'),
+      ));
+      $recurContributions = CRM_Utils_Array::value('values', $contributionRecurResult);
+    }
+    catch (Exception $e) {
+      $recurContributions = NULL;
+    }
 
-        if ($recurContributions[$recurId]['is_active']) {
-          $details = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($recurContributions[$recurId]['id'], 'recur');
-          $hideUpdate = $details->membership_id & $details->auto_renew;
+    return $this->buildRecurringContributionsArray($recurContributions);
+  }
 
-          if ($hideUpdate) {
-            $action -= CRM_Core_Action::UPDATE;
-          }
+  /**
+   * @param $recurContributions
+   *
+   * @return mixed
+   */
+  private function buildRecurringContributionsArray($recurContributions) {
+    foreach ($recurContributions as $recurId => $recurDetail) {
+      $action = array_sum(array_keys($this->recurLinks($recurId)));
+      // no action allowed if it's not active
+      $recurContributions[$recurId]['is_active'] = (!CRM_Contribute_BAO_Contribution::isContributionStatusNegative($recurDetail['contribution_status_id']));
+
+      // Get the name of the payment processor
+      if (!empty($recurDetail['payment_processor_id'])) {
+        $recurContributions[$recurId]['payment_processor'] = CRM_Financial_BAO_PaymentProcessor::getPaymentProcessorName($recurDetail['payment_processor_id']);
+      }
+      // Get the label for the contribution status
+      if (!empty($recurDetail['contribution_status_id'])) {
+        $recurContributions[$recurId]['contribution_status'] = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', $recurDetail['contribution_status_id']);
+      }
 
-          $recurContributions[$recurId]['action'] = CRM_Core_Action::formLink(self::recurLinks($recurId), $action,
-            array(
-              'cid' => $this->_contactId,
-              'crid' => $recurId,
-              'cxt' => 'contribution',
-            ),
-            ts('more'),
-            FALSE,
-            'contribution.selector.recurring',
-            'Contribution',
-            $recurId
-          );
+      if ($recurContributions[$recurId]['is_active']) {
+        $details = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($recurContributions[$recurId]['id'], 'recur');
+        $hideUpdate = $details->membership_id & $details->auto_renew;
+
+        if ($hideUpdate) {
+          $action -= CRM_Core_Action::UPDATE;
         }
+
+        $recurContributions[$recurId]['action'] = CRM_Core_Action::formLink(self::recurLinks($recurId), $action,
+          array(
+            'cid' => $this->_contactId,
+            'crid' => $recurId,
+            'cxt' => 'contribution',
+          ),
+          ts('more'),
+          FALSE,
+          'contribution.selector.recurring',
+          'Contribution',
+          $recurId
+        );
       }
-      // assign vars to templates
-      $this->assign('action', $this->_action);
-      $this->assign('recurRows', $recurContributions);
-      $this->assign('recur', TRUE);
     }
+
+    return $recurContributions;
   }
 
   /**
diff --git a/civicrm/CRM/Core/BAO/Cache.php b/civicrm/CRM/Core/BAO/Cache.php
index 0e619143e3..ce1d63ede3 100644
--- a/civicrm/CRM/Core/BAO/Cache.php
+++ b/civicrm/CRM/Core/BAO/Cache.php
@@ -40,6 +40,13 @@
  */
 class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
 
+  /**
+   * When store session/form state, how long should the data be retained?
+   *
+   * @var int, number of second
+   */
+  const DEFAULT_SESSION_TTL = 172800; // Two days: 2*24*60*60
+
   /**
    * @var array ($cacheKey => $cacheValue)
    */
@@ -66,15 +73,15 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
     $argString = "CRM_CT_{$group}_{$path}_{$componentID}";
     if (!array_key_exists($argString, self::$_cache)) {
       $cache = CRM_Utils_Cache::singleton();
-      self::$_cache[$argString] = $cache->get($argString);
+      self::$_cache[$argString] = $cache->get(self::cleanKey($argString));
       if (!self::$_cache[$argString]) {
         $table = self::getTableName();
         $where = self::whereCache($group, $path, $componentID);
         $rawData = CRM_Core_DAO::singleValueQuery("SELECT data FROM $table WHERE $where");
-        $data = $rawData ? unserialize($rawData) : NULL;
+        $data = $rawData ? self::decode($rawData) : NULL;
 
         self::$_cache[$argString] = $data;
-        $cache->set($argString, self::$_cache[$argString]);
+        $cache->set(self::cleanKey($argString), self::$_cache[$argString]);
       }
     }
     return self::$_cache[$argString];
@@ -99,7 +106,7 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
     $argString = "CRM_CT_CI_{$group}_{$componentID}";
     if (!array_key_exists($argString, self::$_cache)) {
       $cache = CRM_Utils_Cache::singleton();
-      self::$_cache[$argString] = $cache->get($argString);
+      self::$_cache[$argString] = $cache->get(self::cleanKey($argString));
       if (!self::$_cache[$argString]) {
         $table = self::getTableName();
         $where = self::whereCache($group, NULL, $componentID);
@@ -107,12 +114,12 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
 
         $result = array();
         while ($dao->fetch()) {
-          $result[$dao->path] = unserialize($dao->data);
+          $result[$dao->path] = self::decode($dao->data);
         }
         $dao->free();
 
         self::$_cache[$argString] = $result;
-        $cache->set($argString, self::$_cache[$argString]);
+        $cache->set(self::cleanKey($argString), self::$_cache[$argString]);
       }
     }
 
@@ -148,7 +155,7 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
     $where = self::whereCache($group, $path, $componentID);
     $dataExists = CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM $table WHERE {$where}");
     $now = date('Y-m-d H:i:s'); // FIXME - Use SQL NOW() or CRM_Utils_Time?
-    $dataSerialized = serialize($data);
+    $dataSerialized = self::encode($data);
 
     // This table has a wonky index, so we cannot use REPLACE or
     // "INSERT ... ON DUPE". Instead, use SELECT+(INSERT|UPDATE).
@@ -180,13 +187,13 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
 
     $argString = "CRM_CT_{$group}_{$path}_{$componentID}";
     $cache = CRM_Utils_Cache::singleton();
-    $data = unserialize($dataSerialized);
+    $data = self::decode($dataSerialized);
     self::$_cache[$argString] = $data;
-    $cache->set($argString, $data);
+    $cache->set(self::cleanKey($argString), $data);
 
     $argString = "CRM_CT_CI_{$group}_{$componentID}";
     unset(self::$_cache[$argString]);
-    $cache->delete($argString);
+    $cache->delete(self::cleanKey($argString));
   }
 
   /**
@@ -237,7 +244,8 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
         if (!empty($_SESSION[$sessionName[0]][$sessionName[1]])) {
           $value = $_SESSION[$sessionName[0]][$sessionName[1]];
         }
-        self::setItem($value, 'CiviCRM Session', "{$sessionName[0]}_{$sessionName[1]}");
+        $key = "{$sessionName[0]}_{$sessionName[1]}";
+        Civi::cache('session')->set($key, $value, self::pickSessionTtl($key));
         if ($resetSession) {
           $_SESSION[$sessionName[0]][$sessionName[1]] = NULL;
           unset($_SESSION[$sessionName[0]][$sessionName[1]]);
@@ -248,7 +256,7 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
         if (!empty($_SESSION[$sessionName])) {
           $value = $_SESSION[$sessionName];
         }
-        self::setItem($value, 'CiviCRM Session', $sessionName);
+        Civi::cache('session')->set($sessionName, $value, self::pickSessionTtl($sessionName));
         if ($resetSession) {
           $_SESSION[$sessionName] = NULL;
           unset($_SESSION[$sessionName]);
@@ -275,17 +283,13 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
   public static function restoreSessionFromCache($names) {
     foreach ($names as $key => $sessionName) {
       if (is_array($sessionName)) {
-        $value = self::getItem('CiviCRM Session',
-          "{$sessionName[0]}_{$sessionName[1]}"
-        );
+        $value = Civi::cache('session')->get("{$sessionName[0]}_{$sessionName[1]}");
         if ($value) {
           $_SESSION[$sessionName[0]][$sessionName[1]] = $value;
         }
       }
       else {
-        $value = self::getItem('CiviCRM Session',
-          $sessionName
-        );
+        $value = Civi::cache('session')->get($sessionName);
         if ($value) {
           $_SESSION[$sessionName] = $value;
         }
@@ -293,6 +297,32 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
     }
   }
 
+  /**
+   * Determine how long session-state should be retained.
+   *
+   * @param string $sessionKey
+   *   Ex: '_CRM_Admin_Form_Preferences_Display_f1a5f232e3d850a29a7a4d4079d7c37b_4654_container'
+   *   Ex: 'CiviCRM_CRM_Admin_Form_Preferences_Display_f1a5f232e3d850a29a7a4d4079d7c37b_4654'
+   * @return int
+   *   Number of seconds.
+   */
+  protected static function pickSessionTtl($sessionKey) {
+    $secureSessionTimeoutMinutes = (int) Civi::settings()->get('secure_cache_timeout_minutes');
+    if ($secureSessionTimeoutMinutes) {
+      $transactionPages = array(
+        'CRM_Contribute_Controller_Contribution',
+        'CRM_Event_Controller_Registration',
+      );
+      foreach ($transactionPages as $transactionPage) {
+        if (strpos($sessionKey, $transactionPage) !== FALSE) {
+          return $secureSessionTimeoutMinutes * 60;
+        }
+      }
+    }
+
+    return self::DEFAULT_SESSION_TTL;
+  }
+
   /**
    * Do periodic cleanup of the CiviCRM session table.
    *
@@ -304,33 +334,7 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
    * @param bool $table
    * @param bool $prevNext
    */
-  public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FALSE) {
-    // first delete all sessions more than 20 minutes old which are related to any potential transaction
-    $timeIntervalMins = (int) Civi::settings()->get('secure_cache_timeout_minutes');
-    if ($timeIntervalMins && $session) {
-      $transactionPages = array(
-        'CRM_Contribute_Controller_Contribution',
-        'CRM_Event_Controller_Registration',
-      );
-
-      $params = array(
-        1 => array(
-          date('Y-m-d H:i:s', time() - $timeIntervalMins * 60),
-          'String',
-        ),
-      );
-      foreach ($transactionPages as $trPage) {
-        $params[] = array("%${trPage}%", 'String');
-        $where[] = 'path LIKE %' . count($params);
-      }
-
-      $sql = "
-DELETE FROM civicrm_cache
-WHERE       group_name = 'CiviCRM Session'
-AND         created_date <= %1
-AND         (" . implode(' OR ', $where) . ")";
-      CRM_Core_DAO::executeQuery($sql, $params);
-    }
+  public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FALSE, $expired = FALSE) {
     // clean up the session cache every $cacheCleanUpNumber probabilistically
     $cleanUpNumber = 757;
 
@@ -338,10 +342,10 @@ AND         (" . implode(' OR ', $where) . ")";
     $timeIntervalDays = 2;
 
     if (mt_rand(1, 100000) % $cleanUpNumber == 0) {
-      $session = $table = $prevNext = TRUE;
+      $expired = $session = $table = $prevNext = TRUE;
     }
 
-    if (!$session && !$table && !$prevNext) {
+    if (!$session && !$table && !$prevNext && !$expired) {
       return;
     }
 
@@ -355,13 +359,43 @@ AND         (" . implode(' OR ', $where) . ")";
     }
 
     if ($session) {
+      // Session caches are just regular caches, so they expire naturally per TTL.
+      $expired = TRUE;
+    }
 
-      $sql = "
-DELETE FROM civicrm_cache
-WHERE       group_name = 'CiviCRM Session'
-AND         created_date < date_sub( NOW( ), INTERVAL $timeIntervalDays DAY )
-";
-      CRM_Core_DAO::executeQuery($sql);
+    if ($expired) {
+      $sql = "DELETE FROM civicrm_cache WHERE expired_date < %1";
+      $params = [
+        1 => [date(CRM_Utils_Cache_SqlGroup::TS_FMT, CRM_Utils_Time::getTimeRaw()), 'String'],
+      ];
+      CRM_Core_DAO::executeQuery($sql, $params);
+    }
+  }
+
+  /**
+   * (Quasi-private) Encode an object/array/string/int as a string.
+   *
+   * @param $mixed
+   * @return string
+   */
+  public static function encode($mixed) {
+    return base64_encode(serialize($mixed));
+  }
+
+  /**
+   * (Quasi-private) Decode an object/array/string/int from a string.
+   *
+   * @param $string
+   * @return mixed
+   */
+  public static function decode($string) {
+    // Upgrade support -- old records (serialize) always have this punctuation,
+    // and new records (base64) never do.
+    if (strpos($string, ':') !== FALSE || strpos($string, ';') !== FALSE) {
+      return unserialize($string);
+    }
+    else {
+      return unserialize(base64_decode($string));
     }
   }
 
@@ -390,4 +424,35 @@ AND         created_date < date_sub( NOW( ), INTERVAL $timeIntervalDays DAY )
     return $clauses ? implode(' AND ', $clauses) : '(1)';
   }
 
+  /**
+   * Normalize a cache key.
+   *
+   * This bridges an impedance mismatch between our traditional caching
+   * and PSR-16 -- PSR-16 accepts a narrower range of cache keys.
+   *
+   * @param string $key
+   *   Ex: 'ab/cd:ef'
+   * @return string
+   *   Ex: '_abcd1234abcd1234' or 'ab_xx/cd_xxef'.
+   *   A similar key, but suitable for use with PSR-16-compliant cache providers.
+   */
+  public static function cleanKey($key) {
+    if (!is_string($key) && !is_int($key)) {
+      throw new \RuntimeException("Malformed cache key");
+    }
+
+    $maxLen = 64;
+    $escape = '-';
+
+    if (strlen($key) >= $maxLen) {
+      return $escape . md5($key);
+    }
+
+    $r = preg_replace_callback(';[^A-Za-z0-9_\. ];', function($m) use ($escape) {
+      return $escape . dechex(ord($m[0]));
+    }, $key);
+
+    return strlen($r) >= $maxLen ? $escape . md5($key) : $r;
+  }
+
 }
diff --git a/civicrm/CRM/Core/BAO/ConfigSetting.php b/civicrm/CRM/Core/BAO/ConfigSetting.php
index 8cc7653286..bb5340bbec 100644
--- a/civicrm/CRM/Core/BAO/ConfigSetting.php
+++ b/civicrm/CRM/Core/BAO/ConfigSetting.php
@@ -260,6 +260,7 @@ class CRM_Core_BAO_ConfigSetting {
 
     // clear all caches
     CRM_Core_Config::clearDBCache();
+    Civi::cache('session')->clear();
     $moveStatus .= ts('Database cache tables cleared.') . '<br />';
 
     $resetSessionTable = CRM_Utils_Request::retrieve('resetSessionTable',
diff --git a/civicrm/CRM/Core/BAO/CustomField.php b/civicrm/CRM/Core/BAO/CustomField.php
index 41a7164319..554eb94dc9 100644
--- a/civicrm/CRM/Core/BAO/CustomField.php
+++ b/civicrm/CRM/Core/BAO/CustomField.php
@@ -83,6 +83,30 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
     return self::$_dataType;
   }
 
+  /**
+   * Build the map of custom field's data types and there respective Util type
+   *
+   * @return array
+   *   Data data-type => CRM_Utils_Type
+   */
+  public static function dataToType() {
+    return [
+      'String' => CRM_Utils_Type::T_STRING,
+      'Int' => CRM_Utils_Type::T_INT,
+      'Money' => CRM_Utils_Type::T_MONEY,
+      'Memo' => CRM_Utils_Type::T_LONGTEXT,
+      'Float' => CRM_Utils_Type::T_FLOAT,
+      'Date' => CRM_Utils_Type::T_DATE,
+      'DateTime' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
+      'Boolean' => CRM_Utils_Type::T_BOOLEAN,
+      'StateProvince' => CRM_Utils_Type::T_INT,
+      'File' => CRM_Utils_Type::T_STRING,
+      'Link' => CRM_Utils_Type::T_STRING,
+      'ContactReference' => CRM_Utils_Type::T_INT,
+      'Country' => CRM_Utils_Type::T_INT,
+    ];
+  }
+
   /**
    * Get data to html array.
    *
@@ -691,6 +715,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
       $regexp = preg_replace('/[.,;:!?]/', '', CRM_Utils_Array::value(0, $values));
       $importableFields[$key] = array(
         'name' => $key,
+        'type' => CRM_Utils_Array::value(CRM_Utils_Array::value('data_type', $values), self::dataToType()),
         'title' => CRM_Utils_Array::value('label', $values),
         'headerPattern' => '/' . preg_quote($regexp, '/') . '/',
         'import' => 1,
@@ -2189,46 +2214,45 @@ ORDER BY html_type";
   }
 
   /**
+   * Get custom field ID from field/group name/title.
    *
-   */
-
-  /**
-   * Get custom field ID.
-   *
-   * @param string $fieldLabel
-   * @param null $groupTitle
+   * @param string $fieldName Field name or label
+   * @param string|null $groupTitle (Optional) Group name or label
+   * @param bool $fullString Whether to return "custom_123" or "123"
    *
-   * @return int|null
+   * @return string|int|null
+   * @throws \CiviCRM_API3_Exception
    */
-  public static function getCustomFieldID($fieldLabel, $groupTitle = NULL) {
-    $params = array(1 => array($fieldLabel, 'String'));
-    if ($groupTitle) {
-      $params[2] = array($groupTitle, 'String');
-      $sql = "
-SELECT     f.id
-FROM       civicrm_custom_field f
-INNER JOIN civicrm_custom_group g ON f.custom_group_id = g.id
-WHERE      ( f.label = %1 OR f.name  = %1 )
-AND        ( g.title = %2 OR g.name = %2 )
-";
-    }
-    else {
-      $sql = "
-SELECT     f.id
-FROM       civicrm_custom_field f
-WHERE      ( f.label = %1 OR f.name = %1 )
-";
-    }
+  public static function getCustomFieldID($fieldName, $groupTitle = NULL, $fullString = FALSE) {
+    if (!isset(Civi::$statics['CRM_Core_BAO_CustomField'][$fieldName])) {
+      $customFieldParams = [
+        'name' => $fieldName,
+        'label' => $fieldName,
+        'options' => ['or' => [["name", "label"]]],
+      ];
+
+      if ($groupTitle) {
+        $customFieldParams['custom_group_id.name'] = $groupTitle;
+        $customFieldParams['custom_group_id.title'] = $groupTitle;
+        $customFieldParams['options'] = ['or' => [["name", "label"], ["custom_group_id.name", "custom_group_id.title"]]];
+      }
 
-    $dao = CRM_Core_DAO::executeQuery($sql, $params);
-    if ($dao->fetch() &&
-      $dao->N == 1
-    ) {
-      return $dao->id;
+      $field = civicrm_api3('CustomField', 'get', $customFieldParams);
+
+      if (empty($field['id'])) {
+        Civi::$statics['CRM_Core_BAO_CustomField'][$fieldName]['id'] = NULL;
+        Civi::$statics['CRM_Core_BAO_CustomField'][$fieldName]['string'] = NULL;
+      }
+      else {
+        Civi::$statics['CRM_Core_BAO_CustomField'][$fieldName]['id'] = $field['id'];
+        Civi::$statics['CRM_Core_BAO_CustomField'][$fieldName]['string'] = 'custom_' . $field['id'];
+      }
     }
-    else {
-      return NULL;
+
+    if ($fullString) {
+      return Civi::$statics['CRM_Core_BAO_CustomField'][$fieldName]['string'];
     }
+    return Civi::$statics['CRM_Core_BAO_CustomField'][$fieldName]['id'];
   }
 
   /**
diff --git a/civicrm/CRM/Core/BAO/CustomQuery.php b/civicrm/CRM/Core/BAO/CustomQuery.php
index bc082255a3..b71acb1081 100644
--- a/civicrm/CRM/Core/BAO/CustomQuery.php
+++ b/civicrm/CRM/Core/BAO/CustomQuery.php
@@ -433,7 +433,7 @@ SELECT f.id, f.label, f.data_type,
             break;
 
           case 'Date':
-            $this->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($fieldName, $op, $value, 'String');
+            $this->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($fieldName, $op, $value, 'Date');
             list($qillOp, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue(NULL, $field['label'], $value, $op, array(), CRM_Utils_Type::T_DATE);
             $this->_qill[$grouping][] = "{$field['label']} $qillOp '$qillVal'";
             break;
diff --git a/civicrm/CRM/Core/BAO/CustomValue.php b/civicrm/CRM/Core/BAO/CustomValue.php
index 516eb9fe6d..ce72448de2 100644
--- a/civicrm/CRM/Core/BAO/CustomValue.php
+++ b/civicrm/CRM/Core/BAO/CustomValue.php
@@ -207,13 +207,18 @@ class CRM_Core_BAO_CustomValue extends CRM_Core_DAO {
     // first we need to find custom value table, from custom group ID
     $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupID, 'table_name');
 
+    // Retrieve the $entityId so we can pass that to the hook.
+    $entityID = CRM_Core_DAO::singleValueQuery("SELECT entity_id FROM {$tableName} WHERE id = %1", array(
+      1 => array($customValueID, 'Integer'),
+    ));
+
     // delete custom value from corresponding custom value table
     $sql = "DELETE FROM {$tableName} WHERE id = {$customValueID}";
     CRM_Core_DAO::executeQuery($sql);
 
     CRM_Utils_Hook::custom('delete',
       $customGroupID,
-      NULL,
+      $entityID,
       $customValueID
     );
   }
diff --git a/civicrm/CRM/Core/BAO/Domain.php b/civicrm/CRM/Core/BAO/Domain.php
index c3a9094d6f..58a06e16d1 100644
--- a/civicrm/CRM/Core/BAO/Domain.php
+++ b/civicrm/CRM/Core/BAO/Domain.php
@@ -319,4 +319,12 @@ class CRM_Core_BAO_Domain extends CRM_Core_DAO_Domain {
     return array($userName, $userEmail);
   }
 
+  /**
+   * Get address to be used for system from addresses when a reply is not expected.
+   */
+  public static function getNoReplyEmailAddress() {
+    $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
+    return "do-not-reply@$emailDomain";
+  }
+
 }
diff --git a/civicrm/CRM/Core/BAO/Job.php b/civicrm/CRM/Core/BAO/Job.php
index fcb749a2c7..a67ffaa5b0 100644
--- a/civicrm/CRM/Core/BAO/Job.php
+++ b/civicrm/CRM/Core/BAO/Job.php
@@ -146,4 +146,25 @@ class CRM_Core_BAO_Job extends CRM_Core_DAO_Job {
     CRM_Core_DAO::executeQuery($query);
   }
 
+  /**
+   * Make a copy of a Job.
+   *
+   * @param int $id The job id to copy.
+   *
+   * @return CRM_Core_DAO
+   */
+  public static function copy($id, $params = array()) {
+    $fieldsFix = array(
+      'suffix' => array(
+        'name' => ' - ' . ts('Copy'),
+      ),
+      'replace' => $params,
+    );
+    $copy = &CRM_Core_DAO::copyGeneric('CRM_Core_DAO_Job', array('id' => $id), NULL, $fieldsFix);
+    $copy->save();
+    CRM_Utils_Hook::copy('Job', $copy);
+
+    return $copy;
+  }
+
 }
diff --git a/civicrm/CRM/Core/BAO/OptionGroup.php b/civicrm/CRM/Core/BAO/OptionGroup.php
index 85b2e5203d..fdc415b5ca 100644
--- a/civicrm/CRM/Core/BAO/OptionGroup.php
+++ b/civicrm/CRM/Core/BAO/OptionGroup.php
@@ -93,17 +93,11 @@ class CRM_Core_BAO_OptionGroup extends CRM_Core_DAO_OptionGroup {
     }
 
     $params['is_active'] = CRM_Utils_Array::value('is_active', $params, FALSE);
-    $params['is_default'] = CRM_Utils_Array::value('is_default', $params, FALSE);
 
     // action is taken depending upon the mode
     $optionGroup = new CRM_Core_DAO_OptionGroup();
     $optionGroup->copyValues($params);;
 
-    if ($params['is_default']) {
-      $query = "UPDATE civicrm_option_group SET is_default = 0";
-      CRM_Core_DAO::executeQuery($query);
-    }
-
     $optionGroup->save();
     return $optionGroup;
   }
diff --git a/civicrm/CRM/Core/BAO/OptionValue.php b/civicrm/CRM/Core/BAO/OptionValue.php
index 2e621cf073..cfb4b45dce 100644
--- a/civicrm/CRM/Core/BAO/OptionValue.php
+++ b/civicrm/CRM/Core/BAO/OptionValue.php
@@ -547,16 +547,23 @@ class CRM_Core_BAO_OptionValue extends CRM_Core_DAO_OptionValue {
    * that an option value exists, without hitting an error if it already exists.
    *
    * This is sympathetic to sites who might pre-add it.
+   *
+   * @param array $params the option value attributes.
+   * @return array the option value attributes.
    */
   public static function ensureOptionValueExists($params) {
-    $existingValues = civicrm_api3('OptionValue', 'get', array(
+    $result = civicrm_api3('OptionValue', 'get', array(
       'option_group_id' => $params['option_group_id'],
       'name' => $params['name'],
-      'return' => 'id',
+      'return' => ['id', 'value'],
+      'sequential' => 1,
     ));
-    if (!$existingValues['count']) {
-      civicrm_api3('OptionValue', 'create', $params);
+
+    if (!$result['count']) {
+      $result = civicrm_api3('OptionValue', 'create', $params);
     }
+
+    return CRM_Utils_Array::first($result['values']);
   }
 
 }
diff --git a/civicrm/CRM/Core/BAO/RecurringEntity.php b/civicrm/CRM/Core/BAO/RecurringEntity.php
index fb73a78cf2..a5ab757b7b 100644
--- a/civicrm/CRM/Core/BAO/RecurringEntity.php
+++ b/civicrm/CRM/Core/BAO/RecurringEntity.php
@@ -660,7 +660,6 @@ class CRM_Core_BAO_RecurringEntity extends CRM_Core_DAO_RecurringEntity {
         }
 
         $updateDAO = CRM_Core_DAO::cascadeUpdate($daoName, $obj->id, $entityID, $skipData);
-        CRM_Core_DAO::freeResult();
       }
       else {
         CRM_Core_Error::fatal("DAO Mapper missing for $entityTable.");
diff --git a/civicrm/CRM/Core/Config.php b/civicrm/CRM/Core/Config.php
index 349c672b83..265532c67d 100644
--- a/civicrm/CRM/Core/Config.php
+++ b/civicrm/CRM/Core/Config.php
@@ -295,6 +295,7 @@ class CRM_Core_Config extends CRM_Core_Config_MagicMerge {
 
     // clear all caches
     self::clearDBCache();
+    Civi::cache('session')->clear();
     CRM_Utils_System::flushCache();
 
     if ($sessionReset) {
@@ -356,7 +357,6 @@ class CRM_Core_Config extends CRM_Core_Config_MagicMerge {
       'TRUNCATE TABLE civicrm_group_contact_cache',
       'TRUNCATE TABLE civicrm_menu',
       'UPDATE civicrm_setting SET value = NULL WHERE name="navigation" AND contact_id IS NOT NULL',
-      'DELETE FROM civicrm_setting WHERE name="modulePaths"', // CRM-10543
     );
 
     foreach ($queries as $query) {
@@ -389,11 +389,12 @@ class CRM_Core_Config extends CRM_Core_Config_MagicMerge {
       WHERE  TABLE_SCHEMA = %1
       AND (
         TABLE_NAME LIKE 'civicrm_import_job_%'
-        OR TABLE_NAME LIKE 'civicrm_export_temp%'
-        OR TABLE_NAME LIKE 'civicrm_task_action_temp%'
         OR TABLE_NAME LIKE 'civicrm_report_temp%'
+        OR TABLE_NAME LIKE 'civicrm_tmp_d%'
         )
     ";
+    // NOTE: Cannot find use-cases where "civicrm_report_temp" would be durable. Could probably remove.
+
     if ($timeInterval) {
       $query .= " AND CREATE_TIME < DATE_SUB(NOW(), INTERVAL {$timeInterval})";
     }
diff --git a/civicrm/CRM/Core/DAO.php b/civicrm/CRM/Core/DAO.php
index 6df94eb19a..b4d07e75ad 100644
--- a/civicrm/CRM/Core/DAO.php
+++ b/civicrm/CRM/Core/DAO.php
@@ -48,6 +48,13 @@ require_once 'CRM/Core/I18n.php';
  */
 class CRM_Core_DAO extends DB_DataObject {
 
+  /**
+   * How many times has this instance been cloned.
+   *
+   * @var int
+   */
+  protected $resultCopies = 0;
+
   /**
    * @var null
    * @deprecated
@@ -119,6 +126,22 @@ class CRM_Core_DAO extends DB_DataObject {
     $this->__table = $this->getTableName();
   }
 
+  public function __clone() {
+    if (!empty($this->_DB_resultid)) {
+      $this->resultCopies++;
+    }
+  }
+
+  /**
+   * Class destructor.
+   */
+  public function __destruct() {
+    if ($this->resultCopies === 0) {
+      $this->free();
+    }
+    $this->resultCopies--;
+  }
+
   /**
    * Empty definition for virtual function.
    */
@@ -1995,6 +2018,8 @@ SELECT contact_id
    * @param null $string
    *
    * @return string
+   * @deprecated
+   * @see CRM_Utils_SQL_TempTable
    */
   public static function createTempTableName($prefix = 'civicrm', $addRandomString = TRUE, $string = NULL) {
     $tableName = $prefix . "_temp";
@@ -2243,6 +2268,54 @@ SELECT contact_id
     return $refsFound;
   }
 
+  /**
+   * Get all references to contact table.
+   *
+   * This includes core tables, custom group tables, tables added by the merge
+   * hook and  the entity_tag table.
+   *
+   * Refer to CRM-17454 for information on the danger of querying the information
+   * schema to derive this.
+   */
+  public static function getReferencesToContactTable() {
+    if (isset(\Civi::$statics[__CLASS__]) && isset(\Civi::$statics[__CLASS__]['contact_references'])) {
+      return \Civi::$statics[__CLASS__]['contact_references'];
+    }
+    $contactReferences = [];
+    $coreReferences = CRM_Core_DAO::getReferencesToTable('civicrm_contact');
+    foreach ($coreReferences as $coreReference) {
+      if (!is_a($coreReference, 'CRM_Core_Reference_Dynamic')) {
+        $contactReferences[$coreReference->getReferenceTable()][] = $coreReference->getReferenceKey();
+      }
+    }
+    self::appendCustomTablesExtendingContacts($contactReferences);
+
+    // FixME for time being adding below line statically as no Foreign key constraint defined for table 'civicrm_entity_tag'
+    $contactReferences['civicrm_entity_tag'][] = 'entity_id';
+    \Civi::$statics[__CLASS__]['contact_references'] = $contactReferences;
+    return \Civi::$statics[__CLASS__]['contact_references'];
+  }
+
+  /**
+   * Add custom tables that extend contacts to the list of contact references.
+   *
+   * CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity seems like a safe-ish
+   * function to be sure all are retrieved & we don't miss subtypes or inactive or multiples
+   * - the down side is it is not cached.
+   *
+   * Further changes should be include tests in the CRM_Core_MergerTest class
+   * to ensure that disabled, subtype, multiple etc groups are still captured.
+   *
+   * @param array $cidRefs
+   */
+  public static function appendCustomTablesExtendingContacts(&$cidRefs) {
+    $customValueTables = CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity('Contact');
+    $customValueTables->find();
+    while ($customValueTables->fetch()) {
+      $cidRefs[$customValueTables->table_name] = array('entity_id');
+    }
+  }
+
   /**
    * Lookup the value of a MySQL global configuration variable.
    *
diff --git a/civicrm/CRM/Core/DAO/CustomField.php b/civicrm/CRM/Core/DAO/CustomField.php
index 1f48b8858f..24b86133fe 100644
--- a/civicrm/CRM/Core/DAO/CustomField.php
+++ b/civicrm/CRM/Core/DAO/CustomField.php
@@ -6,7 +6,7 @@
  *
  * Generated from xml/schema/CRM/Core/CustomField.xml
  * DO NOT EDIT.  Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:dcb494bf3990ce63b66ef13ee47a2d15)
+ * (GenCodeChecksum:7f096c92af68ef9564675e3d708fbbe1)
  */
 
 /**
@@ -594,6 +594,11 @@ class CRM_Core_DAO_CustomField extends CRM_Core_DAO {
           'entity' => 'CustomField',
           'bao' => 'CRM_Core_BAO_CustomField',
           'localizable' => 0,
+          'pseudoconstant' => [
+            'table' => 'civicrm_option_group',
+            'keyColumn' => 'id',
+            'labelColumn' => 'title',
+          ]
         ],
         'filter' => [
           'name' => 'filter',
diff --git a/civicrm/CRM/Core/DAO/UFGroup.php b/civicrm/CRM/Core/DAO/UFGroup.php
index c0b5d43b47..53e1c18927 100644
--- a/civicrm/CRM/Core/DAO/UFGroup.php
+++ b/civicrm/CRM/Core/DAO/UFGroup.php
@@ -6,7 +6,7 @@
  *
  * Generated from xml/schema/CRM/Core/UFGroup.xml
  * DO NOT EDIT.  Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:a6776694df1be240b3f7be798792175d)
+ * (GenCodeChecksum:a48f9522d0bd2e1d485064ebfc66f9a2)
  */
 
 /**
@@ -208,6 +208,13 @@ class CRM_Core_DAO_UFGroup extends CRM_Core_DAO {
    */
   public $submit_button_text;
 
+  /**
+   * Should a Cancel button be included in this Profile form.
+   *
+   * @var boolean
+   */
+  public $add_cancel_button;
+
   /**
    * Class constructor.
    */
@@ -545,6 +552,17 @@ class CRM_Core_DAO_UFGroup extends CRM_Core_DAO {
           'bao' => 'CRM_Core_BAO_UFGroup',
           'localizable' => 1,
         ],
+        'add_cancel_button' => [
+          'name' => 'add_cancel_button',
+          'type' => CRM_Utils_Type::T_BOOLEAN,
+          'title' => ts('Include Cancel Button'),
+          'description' => 'Should a Cancel button be included in this Profile form.',
+          'default' => '1',
+          'table_name' => 'civicrm_uf_group',
+          'entity' => 'UFGroup',
+          'bao' => 'CRM_Core_BAO_UFGroup',
+          'localizable' => 0,
+        ],
       ];
       CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
     }
diff --git a/civicrm/CRM/Core/Form.php b/civicrm/CRM/Core/Form.php
index 113411d7ff..abfbcdfcd4 100644
--- a/civicrm/CRM/Core/Form.php
+++ b/civicrm/CRM/Core/Form.php
@@ -179,6 +179,29 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
    */
   public $urlPath = array();
 
+  /**
+   * Context of the form being loaded.
+   *
+   * 'event' or null
+   *
+   * @var string
+   */
+  protected $context;
+
+  /**
+   * @return string
+   */
+  public function getContext() {
+    return $this->context;
+  }
+
+  /**
+   * Set context variable.
+   */
+  public function setContext() {
+    $this->context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this);
+  }
+
   /**
    * @var CRM_Core_Controller
    */
@@ -1845,7 +1868,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
     $setDefaultCurrency = TRUE
   ) {
     $currencies = CRM_Core_OptionGroup::values('currencies_enabled');
-    if (!array_key_exists($defaultCurrency, $currencies)) {
+    if (!empty($defaultCurrency) && !array_key_exists($defaultCurrency, $currencies)) {
       Civi::log()->warning('addCurrency: Currency ' . $defaultCurrency . ' is disabled but still in use!');
       $currencies[$defaultCurrency] = $defaultCurrency;
     }
diff --git a/civicrm/CRM/Core/Form/Task.php b/civicrm/CRM/Core/Form/Task.php
index 2a487ba70f..7eea0555c5 100644
--- a/civicrm/CRM/Core/Form/Task.php
+++ b/civicrm/CRM/Core/Form/Task.php
@@ -56,6 +56,11 @@ abstract class CRM_Core_Form_Task extends CRM_Core_Form {
    */
   protected $_componentIds;
 
+  /**
+   * @var int
+   */
+  protected $queryMode;
+
   /**
    * The array that holds all the case ids
    *
@@ -70,9 +75,18 @@ abstract class CRM_Core_Form_Task extends CRM_Core_Form {
    */
   public $_contactIds;
 
-  // Must be set to entity table name (eg. civicrm_participant) by child class
+  /**
+   * Must be set to entity table name (eg. civicrm_participant) by child class
+   *
+   * @var string
+   */
   static $tableName = NULL;
-  // Must be set to entity shortname (eg. event)
+
+  /**
+   * Must be set to entity shortname (eg. event)
+   *
+   * @var string
+   */
   static $entityShortname = NULL;
 
   /**
@@ -87,26 +101,25 @@ abstract class CRM_Core_Form_Task extends CRM_Core_Form {
   /**
    * Common pre-processing function.
    *
-   * @param CRM_Core_Form $form
-   * @param bool $useTable FIXME This parameter could probably be deprecated as it's not used here
+   * @param CRM_Core_Form_Task $form
    *
    * @throws \CRM_Core_Exception
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_entityIds = array();
 
-    $values = $form->controller->exportValues($form->get('searchFormName'));
+    $searchFormValues = $form->controller->exportValues($form->get('searchFormName'));
 
-    $form->_task = $values['task'];
+    $form->_task = $searchFormValues['task'];
     $className = 'CRM_' . ucfirst($form::$entityShortname) . '_Task';
     $entityTasks = $className::tasks();
     $form->assign('taskName', $entityTasks[$form->_task]);
 
-    $ids = array();
-    if ($values['radio_ts'] == 'ts_sel') {
-      foreach ($values as $name => $value) {
+    $entityIds = array();
+    if ($searchFormValues['radio_ts'] == 'ts_sel') {
+      foreach ($searchFormValues as $name => $value) {
         if (substr($name, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) {
-          $ids[] = substr($name, CRM_Core_Form::CB_PREFIX_LEN);
+          $entityIds[] = substr($name, CRM_Core_Form::CB_PREFIX_LEN);
         }
       }
     }
@@ -117,24 +130,22 @@ abstract class CRM_Core_Form_Task extends CRM_Core_Form {
         $sortOrder = $form->get(CRM_Utils_Sort::SORT_ORDER);
       }
 
-      $query = new CRM_Contact_BAO_Query($queryParams, NULL, NULL, FALSE, FALSE,
-        CRM_Contact_BAO_Query::MODE_CASE
-      );
+      $query = new CRM_Contact_BAO_Query($queryParams, NULL, NULL, FALSE, FALSE, $form->getQueryMode());
       $query->_distinctComponentClause = " ( " . $form::$tableName . ".id )";
       $query->_groupByComponentClause = " GROUP BY " . $form::$tableName . ".id ";
       $result = $query->searchQuery(0, 0, $sortOrder);
       $selector = $form::$entityShortname . '_id';
       while ($result->fetch()) {
-        $ids[] = $result->$selector;
+        $entityIds[] = $result->$selector;
       }
     }
 
-    if (!empty($ids)) {
-      $form->_componentClause = ' ' . $form::$tableName . '.id IN ( ' . implode(',', $ids) . ' ) ';
-      $form->assign('totalSelected' . ucfirst($form::$entityShortname) . 's', count($ids));
+    if (!empty($entityIds)) {
+      $form->_componentClause = ' ' . $form::$tableName . '.id IN ( ' . implode(',', $entityIds) . ' ) ';
+      $form->assign('totalSelected' . ucfirst($form::$entityShortname) . 's', count($entityIds));
     }
 
-    $form->_entityIds = $form->_componentIds = $ids;
+    $form->_entityIds = $form->_componentIds = $entityIds;
 
     // Some functions (eg. PDF letter tokens) rely on Ids being in specific fields rather than the generic $form->_entityIds
     // So we set that specific field here (eg. for cases $form->_caseIds = $form->_entityIds).
@@ -197,4 +208,14 @@ abstract class CRM_Core_Form_Task extends CRM_Core_Form {
     );
   }
 
+  /**
+   * Get the query mode (eg. CRM_Core_BAO_Query::MODE_CASE)
+   * Should be overridden by child classes in most cases
+   *
+   * @return int
+   */
+  public function getQueryMode() {
+    return $this->queryMode ?: CRM_Contact_BAO_Query::MODE_CONTACTS;
+  }
+
 }
diff --git a/civicrm/CRM/Core/Invoke.php b/civicrm/CRM/Core/Invoke.php
index e5edcd82c9..eb2bef51af 100644
--- a/civicrm/CRM/Core/Invoke.php
+++ b/civicrm/CRM/Core/Invoke.php
@@ -350,7 +350,7 @@ class CRM_Core_Invoke {
       return;
     }
     // always use cached results - they will be refreshed by the session timer
-    $status = Civi::settings()->get('systemStatusCheckResult');
+    $status = Civi::cache('checks')->get('systemStatusCheckResult');
     $template->assign('footer_status_severity', $status);
     $template->assign('footer_status_message', CRM_Utils_Check::toStatusLabel($status));
   }
diff --git a/civicrm/CRM/Core/JobManager.php b/civicrm/CRM/Core/JobManager.php
index e7008ea645..fccfc18b8c 100644
--- a/civicrm/CRM/Core/JobManager.php
+++ b/civicrm/CRM/Core/JobManager.php
@@ -141,12 +141,15 @@ class CRM_Core_JobManager {
       $params = $job->apiParams;
     }
 
+    CRM_Utils_Hook::preJob($job, $params);
     try {
       $result = civicrm_api($job->api_entity, $job->api_action, $params);
     }
     catch (Exception$e) {
       $this->logEntry('Error while executing ' . $job->name . ': ' . $e->getMessage());
+      $result = $e;
     }
+    CRM_Utils_Hook::postJob($job, $params, $result);
     $this->logEntry('Finished execution of ' . $job->name . ' with result: ' . $this->_apiResultToMessage($result));
     $this->currentJob = FALSE;
 
diff --git a/civicrm/CRM/Core/OptionGroup.php b/civicrm/CRM/Core/OptionGroup.php
index bc1f7ad2ac..c7e07e67d6 100644
--- a/civicrm/CRM/Core/OptionGroup.php
+++ b/civicrm/CRM/Core/OptionGroup.php
@@ -200,8 +200,8 @@ WHERE  v.option_group_id = g.id
   /**
    * @return string
    */
-  protected static function createCacheKey() {
-    $cacheKey = "CRM_OG_" . serialize(func_get_args());
+  protected static function createCacheKey($id) {
+    $cacheKey = "CRM_OG_" . preg_replace('/[^a-zA-Z0-9]/', '', $id) . '_' . md5(serialize(func_get_args()));
     return $cacheKey;
   }
 
diff --git a/civicrm/CRM/Core/Payment.php b/civicrm/CRM/Core/Payment.php
index 8626cd790d..b686cd8501 100644
--- a/civicrm/CRM/Core/Payment.php
+++ b/civicrm/CRM/Core/Payment.php
@@ -682,7 +682,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'credit_card_number',
         'title' => ts('Card Number'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 20,
           'maxlength' => 20,
@@ -695,7 +694,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'cvv2',
         'title' => ts('Security Code'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 5,
           'maxlength' => 10,
@@ -714,7 +712,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'date',
         'name' => 'credit_card_exp_date',
         'title' => ts('Expiration Date'),
-        'cc_field' => TRUE,
         'attributes' => CRM_Core_SelectValues::date('creditCard'),
         'is_required' => TRUE,
         'rules' => array(
@@ -729,7 +726,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'select',
         'name' => 'credit_card_type',
         'title' => ts('Card Type'),
-        'cc_field' => TRUE,
         'attributes' => $creditCardType,
         'is_required' => FALSE,
       ),
@@ -737,7 +733,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'account_holder',
         'title' => ts('Account Holder'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 20,
           'maxlength' => 34,
@@ -750,7 +745,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'bank_account_number',
         'title' => ts('Bank Account Number'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 20,
           'maxlength' => 34,
@@ -770,7 +764,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'bank_identification_number',
         'title' => ts('Bank Identification Number'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 20,
           'maxlength' => 11,
@@ -789,7 +782,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'bank_name',
         'title' => ts('Bank Name'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 20,
           'maxlength' => 64,
@@ -803,7 +795,6 @@ abstract class CRM_Core_Payment {
         'name' => 'check_number',
         'title' => ts('Check Number'),
         'is_required' => FALSE,
-        'cc_field' => TRUE,
         'attributes' => NULL,
       ),
       'pan_truncation' => array(
@@ -811,7 +802,6 @@ abstract class CRM_Core_Payment {
         'name' => 'pan_truncation',
         'title' => ts('Last 4 digits of the card'),
         'is_required' => FALSE,
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 4,
           'maxlength' => 4,
@@ -826,6 +816,13 @@ abstract class CRM_Core_Payment {
           ),
         ),
       ),
+      'payment_token' => array(
+        'htmlType' => 'hidden',
+        'name' => 'payment_token',
+        'title' => ts('Authorization token'),
+        'is_required' => FALSE,
+        'attributes' => ['size' => 10, 'autocomplete' => 'off', 'id' => 'payment_token'],
+      ),
     );
   }
 
diff --git a/civicrm/CRM/Core/Payment/AuthorizeNetIPN.php b/civicrm/CRM/Core/Payment/AuthorizeNetIPN.php
index 2696e2a1ab..e350195886 100644
--- a/civicrm/CRM/Core/Payment/AuthorizeNetIPN.php
+++ b/civicrm/CRM/Core/Payment/AuthorizeNetIPN.php
@@ -77,7 +77,7 @@ class CRM_Core_Payment_AuthorizeNetIPN extends CRM_Core_Payment_BaseIPN {
         // processor id & the handleNotification function (which should call the completetransaction api & by-pass this
         // entirely). The only thing the IPN class should really do is extract data from the request, validate it
         // & call completetransaction or call fail? (which may not exist yet).
-        Civi::log()->warning('Unreliable method used for AuthNet IPN - this will cause problems if you have more than one instance');
+        Civi::log()->warning('Unreliable method used to get payment_processor_id for AuthNet IPN - this will cause problems if you have more than one instance');
         $paymentProcessorTypeID = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessorType',
           'AuthNet', 'id', 'name'
         );
diff --git a/civicrm/CRM/Core/Payment/Form.php b/civicrm/CRM/Core/Payment/Form.php
index 05de148e82..60e946d7dd 100644
--- a/civicrm/CRM/Core/Payment/Form.php
+++ b/civicrm/CRM/Core/Payment/Form.php
@@ -68,8 +68,7 @@ class CRM_Core_Payment_Form {
     $processor['object']->setPaymentInstrumentID($paymentInstrumentID);
     $paymentTypeName = self::getPaymentTypeName($processor);
     $form->assign('paymentTypeName', $paymentTypeName);
-    $paymentTypeLabel = self::getPaymentTypeLabel($processor);
-    $form->assign('paymentTypeLabel', $paymentTypeLabel);
+    $form->assign('paymentTypeLabel', self::getPaymentLabel($processor['object']));
     $form->assign('isBackOffice', $isBackOffice);
     $form->_paymentFields = $form->billingFieldSets[$paymentTypeName]['fields'] = self::getPaymentFieldMetadata($processor);
     $form->_paymentFields = array_merge($form->_paymentFields, self::getBillingAddressMetadata($processor, $form->_bltID));
@@ -116,24 +115,22 @@ class CRM_Core_Payment_Form {
   protected static function addCommonFields(&$form, $paymentFields) {
     $requiredPaymentFields = array();
     foreach ($paymentFields as $name => $field) {
-      // @todo - remove the cc_field check - no longer useful.
-      if (!empty($field['cc_field'])) {
-        if ($field['htmlType'] == 'chainSelect') {
-          $form->addChainSelect($field['name'], array('required' => FALSE));
-        }
-        else {
-          $form->add($field['htmlType'],
-            $field['name'],
-            $field['title'],
-            $field['attributes'],
-            FALSE
-          );
-        }
+      if ($field['htmlType'] == 'chainSelect') {
+        $form->addChainSelect($field['name'], array('required' => FALSE));
+      }
+      else {
+        $form->add($field['htmlType'],
+          $field['name'],
+          $field['title'],
+          $field['attributes'],
+          FALSE
+        );
       }
       // This will cause the fields to be marked as required - but it is up to the payment processor to
       // validate it.
       $requiredPaymentFields[$field['name']] = $field['is_required'];
     }
+
     $form->assign('requiredPaymentFields', $requiredPaymentFields);
   }
 
@@ -207,7 +204,7 @@ class CRM_Core_Payment_Form {
    * @return string
    */
   public static function getPaymentTypeLabel($paymentProcessor) {
-    return ts(($paymentProcessor['object']->getPaymentTypeLabel()) . ' Information');
+    return ts('%1 Information', [$paymentProcessor->getPaymentTypeLabel()]);
   }
 
   /**
@@ -426,4 +423,25 @@ class CRM_Core_Payment_Form {
     return CRM_Utils_Array::value('Y', $src['credit_card_exp_date']);
   }
 
+  /**
+   * Get the label for the processor.
+   *
+   * We do not use a label if there are no enterable fields.
+   *
+   * @param \CRM_Core_Payment $processor
+   *
+   * @return string
+   */
+  public static function getPaymentLabel($processor) {
+    $isVisible = FALSE;
+    $paymentTypeLabel = self::getPaymentTypeLabel($processor);
+    foreach (self::getPaymentFieldMetadata(['object' => $processor]) as $paymentField) {
+      if ($paymentField['htmlType'] !== 'hidden') {
+        $isVisible = TRUE;
+      }
+    }
+    return $isVisible ? $paymentTypeLabel : '';
+
+  }
+
 }
diff --git a/civicrm/CRM/Core/Payment/PayPalIPN.php b/civicrm/CRM/Core/Payment/PayPalIPN.php
index 0100ca27d5..b058eb63f6 100644
--- a/civicrm/CRM/Core/Payment/PayPalIPN.php
+++ b/civicrm/CRM/Core/Payment/PayPalIPN.php
@@ -29,7 +29,6 @@
  *
  * @package CRM
  * @copyright CiviCRM LLC (c) 2004-2018
- * $Id$
  *
  */
 class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
@@ -52,7 +51,7 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
    * @throws CRM_Core_Exception
    */
   public function __construct($inputData) {
-    //CRM-19676
+    // CRM-19676
     $params = (!empty($inputData['custom'])) ?
       array_merge($inputData, json_decode($inputData['custom'], TRUE)) :
       $inputData;
@@ -62,47 +61,46 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
 
   /**
    * @param string $name
-   * @param $type
+   * @param string $type
    * @param bool $abort
    *
    * @return mixed
+   * @throws \CRM_Core_Exception
    */
   public function retrieve($name, $type, $abort = TRUE) {
-    static $store = NULL;
-    $value = CRM_Utils_Type::validate(
-      CRM_Utils_Array::value($name, $this->_inputParameters),
-      $type,
-      FALSE
-    );
+    $value = CRM_Utils_Type::validate(CRM_Utils_Array::value($name, $this->_inputParameters), $type, FALSE);
     if ($abort && $value === NULL) {
-      CRM_Core_Error::debug_log_message("Could not find an entry for $name");
+      Civi::log()->debug("PayPalIPN: Could not find an entry for $name");
       echo "Failure: Missing Parameter<p>" . CRM_Utils_Type::escape($name, 'String');
-      exit();
+      throw new CRM_Core_Exception("PayPalIPN: Could not find an entry for $name");
     }
     return $value;
   }
 
   /**
-   * @param $input
-   * @param $ids
-   * @param $objects
-   * @param $first
+   * @param array $input
+   * @param array $ids
+   * @param array $objects
+   * @param bool $first
+   *
+   * @return void
    *
-   * @return bool
+   * @throws \CRM_Core_Exception
+   * @throws \CiviCRM_API3_Exception
    */
   public function recur(&$input, &$ids, &$objects, $first) {
     if (!isset($input['txnType'])) {
-      CRM_Core_Error::debug_log_message("Could not find txn_type in input request");
+      Civi::log()->debug('PayPalIPN: Could not find txn_type in input request');
       echo "Failure: Invalid parameters<p>";
-      return FALSE;
+      return;
     }
 
     if ($input['txnType'] == 'subscr_payment' &&
       $input['paymentStatus'] != 'Completed'
     ) {
-      CRM_Core_Error::debug_log_message("Ignore all IPN payments that are not completed");
+      Civi::log()->debug('PayPalIPN: Ignore all IPN payments that are not completed');
       echo "Failure: Invalid parameters<p>";
-      return FALSE;
+      return;
     }
 
     $recur = &$objects['contributionRecur'];
@@ -110,9 +108,9 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
     // make sure the invoice ids match
     // make sure the invoice is valid and matches what we have in the contribution record
     if ($recur->invoice_id != $input['invoice']) {
-      CRM_Core_Error::debug_log_message("Invoice values dont match between database and IPN request");
+      Civi::log()->debug('PayPalIPN: Invoice values dont match between database and IPN request (RecurID: ' . $recur->id . ').');
       echo "Failure: Invoice values dont match between database and IPN request<p>";
-      return FALSE;
+      return;
     }
 
     $now = date('YmdHis');
@@ -127,18 +125,19 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
     }
     $sendNotification = FALSE;
     $subscriptionPaymentStatus = NULL;
-    //set transaction type
+    // set transaction type
     $txnType = $this->retrieve('txn_type', 'String');
+    $contributionStatuses = array_flip(CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id', 'validate'));
     switch ($txnType) {
       case 'subscr_signup':
         $recur->create_date = $now;
-        //some times subscr_signup response come after the
-        //subscr_payment and set to pending mode.
+        // sometimes subscr_signup response come after the subscr_payment and set to pending mode.
+
         $statusID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionRecur',
           $recur->id, 'contribution_status_id'
         );
-        if ($statusID != 5) {
-          $recur->contribution_status_id = 2;
+        if ($statusID != $contributionStatuses['In Progress']) {
+          $recur->contribution_status_id = $contributionStatuses['Pending'];
         }
         $recur->processor_id = $this->retrieve('subscr_id', 'String');
         $recur->trxn_id = $recur->processor_id;
@@ -147,8 +146,8 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
         break;
 
       case 'subscr_eot':
-        if ($recur->contribution_status_id != 3) {
-          $recur->contribution_status_id = 1;
+        if ($recur->contribution_status_id != $contributionStatuses['Cancelled']) {
+          $recur->contribution_status_id = $contributionStatuses['Completed'];
         }
         $recur->end_date = $now;
         $sendNotification = TRUE;
@@ -156,19 +155,19 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
         break;
 
       case 'subscr_cancel':
-        $recur->contribution_status_id = 3;
+        $recur->contribution_status_id = $contributionStatuses['Cancelled'];
         $recur->cancel_date = $now;
         break;
 
       case 'subscr_failed':
-        $recur->contribution_status_id = 4;
+        $recur->contribution_status_id = $contributionStatuses['Failed'];
         $recur->modified_date = $now;
         break;
 
       case 'subscr_modify':
-        CRM_Core_Error::debug_log_message("We do not handle modifications to subscriptions right now");
+        Civi::log()->debug('PayPalIPN: We do not handle modifications to subscriptions right now  (RecurID: ' . $recur->id . ').');
         echo "Failure: We do not handle modifications to subscriptions right now<p>";
-        return FALSE;
+        return;
 
       case 'subscr_payment':
         if ($first) {
@@ -180,8 +179,8 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
 
         // make sure the contribution status is not done
         // since order of ipn's is unknown
-        if ($recur->contribution_status_id != 1) {
-          $recur->contribution_status_id = 5;
+        if ($recur->contribution_status_id != $contributionStatuses['Completed']) {
+          $recur->contribution_status_id = $contributionStatuses['In Progress'];
         }
         break;
     }
@@ -189,7 +188,6 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
     $recur->save();
 
     if ($sendNotification) {
-
       $autoRenewMembership = FALSE;
       if ($recur->id &&
         isset($ids['membership']) && $ids['membership']
@@ -211,14 +209,18 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
     }
 
     if (!$first) {
-      //check if this contribution transaction is already processed
-      //if not create a contribution and then get it processed
+      // check if this contribution transaction is already processed
+      // if not create a contribution and then get it processed
       $contribution = new CRM_Contribute_BAO_Contribution();
       $contribution->trxn_id = $input['trxn_id'];
       if ($contribution->trxn_id && $contribution->find()) {
-        CRM_Core_Error::debug_log_message("returning since contribution has already been handled");
+        Civi::log()->debug('PayPalIPN: Returning since contribution has already been handled (trxn_id: ' . $contribution->trxn_id . ')');
         echo "Success: Contribution has already been handled<p>";
-        return TRUE;
+        return;
+      }
+
+      if ($input['paymentStatus'] != 'Completed') {
+        throw new CRM_Core_Exception("Ignore all IPN payments that are not completed");
       }
 
       $contribution->contact_id = $ids['contact'];
@@ -240,27 +242,23 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
   }
 
   /**
-   * @param $input
-   * @param $ids
-   * @param $objects
+   * @param array $input
+   * @param array $ids
+   * @param array $objects
    * @param bool $recur
    * @param bool $first
    *
-   * @return bool
+   * @return void
    */
-  public function single(
-    &$input, &$ids, &$objects,
-    $recur = FALSE,
-    $first = FALSE
-  ) {
+  public function single(&$input, &$ids, &$objects, $recur = FALSE, $first = FALSE) {
     $contribution = &$objects['contribution'];
 
     // make sure the invoice is valid and matches what we have in the contribution record
     if ((!$recur) || ($recur && $first)) {
       if ($contribution->invoice_id != $input['invoice']) {
-        CRM_Core_Error::debug_log_message("Invoice values dont match between database and IPN request");
+        Civi::log()->debug('PayPalIPN: Invoice values dont match between database and IPN request. (ID: ' . $contribution->id . ').');
         echo "Failure: Invoice values dont match between database and IPN request<p>";
-        return FALSE;
+        return;
       }
     }
     else {
@@ -269,9 +267,9 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
 
     if (!$recur) {
       if ($contribution->total_amount != $input['amount']) {
-        CRM_Core_Error::debug_log_message("Amount values dont match between database and IPN request");
+        Civi::log()->debug('PayPalIPN: Amount values dont match between database and IPN request. (ID: ' . $contribution->id . ').');
         echo "Failure: Amount values dont match between database and IPN request<p>";
-        return FALSE;
+        return;
       }
     }
     else {
@@ -280,9 +278,6 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
 
     $transaction = new CRM_Core_Transaction();
 
-    $participant = &$objects['participant'];
-    $membership = &$objects['membership'];
-
     $status = $input['paymentStatus'];
     if ($status == 'Denied' || $status == 'Failed' || $status == 'Voided') {
       return $this->failed($objects, $transaction);
@@ -298,11 +293,12 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
     }
 
     // check if contribution is already completed, if so we ignore this ipn
-    if ($contribution->contribution_status_id == 1) {
+    $completedStatusId = CRM_Core_Pseudoconstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
+    if ($contribution->contribution_status_id == $completedStatusId) {
       $transaction->commit();
-      CRM_Core_Error::debug_log_message("returning since contribution has already been handled");
+      Civi::log()->debug('PayPalIPN: Returning since contribution has already been handled. (ID: ' . $contribution->id . ').');
       echo "Success: Contribution has already been handled<p>";
-      return TRUE;
+      return;
     }
 
     $this->completeTransaction($input, $ids, $objects, $transaction, $recur);
@@ -311,10 +307,10 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
   /**
    * Main function.
    *
-   * @return bool
+   * @throws \CRM_Core_Exception
+   * @throws \CiviCRM_API3_Exception
    */
   public function main() {
-
     $objects = $ids = $input = array();
     $component = $this->retrieve('module', 'String');
     $input['component'] = $component;
@@ -361,29 +357,26 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
       if ($ids['contributionRecur']) {
         // check if first contribution is completed, else complete first contribution
         $first = TRUE;
-        if ($objects['contribution']->contribution_status_id == 1) {
+        $completedStatusId = CRM_Core_Pseudoconstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
+        if ($objects['contribution']->contribution_status_id == $completedStatusId) {
           $first = FALSE;
         }
-        return $this->recur($input, $ids, $objects, $first);
-      }
-      else {
-        return $this->single($input, $ids, $objects, FALSE, FALSE);
+        $this->recur($input, $ids, $objects, $first);
+        return;
       }
     }
-    else {
-      return $this->single($input, $ids, $objects, FALSE, FALSE);
-    }
+    $this->single($input, $ids, $objects, FALSE, FALSE);
   }
 
   /**
-   * @param $input
-   * @param $ids
+   * @param array $input
+   * @param array $ids
    *
-   * @return bool
+   * @throws \CRM_Core_Exception
    */
   public function getInput(&$input, &$ids) {
     if (!$this->getBillingID($ids)) {
-      return FALSE;
+      return;
     }
 
     $input['txnType'] = $this->retrieve('txn_type', 'String', FALSE);
diff --git a/civicrm/CRM/Core/Payment/PayPalImpl.php b/civicrm/CRM/Core/Payment/PayPalImpl.php
index 3fa0066892..8ab1bbec3a 100644
--- a/civicrm/CRM/Core/Payment/PayPalImpl.php
+++ b/civicrm/CRM/Core/Payment/PayPalImpl.php
@@ -412,7 +412,6 @@ class CRM_Core_Payment_PayPalImpl extends CRM_Core_Payment {
    */
   public function createRecurringPayments(&$params) {
     $args = array();
-    // @todo this function is riddled with enotices - perhaps use $this->mapPaypalParamsToCivicrmParams($fieldMap, $result)
     $this->initialize($args, 'CreateRecurringPaymentsProfile');
 
     $start_time = strtotime(date('m/d/Y'));
@@ -424,15 +423,12 @@ class CRM_Core_Payment_PayPalImpl extends CRM_Core_Payment {
     $args['currencyCode'] = $params['currencyID'];
     $args['payerID'] = $params['payer_id'];
     $args['invnum'] = $params['invoiceID'];
-    $args['returnURL'] = $params['returnURL'];
-    $args['cancelURL'] = $params['cancelURL'];
     $args['profilestartdate'] = $start_date;
     $args['method'] = 'CreateRecurringPaymentsProfile';
     $args['billingfrequency'] = $params['frequency_interval'];
     $args['billingperiod'] = ucwords($params['frequency_unit']);
     $args['desc'] = $params['amount'] . " Per " . $params['frequency_interval'] . " " . $params['frequency_unit'];
-    //$args['desc']           = 'Recurring Contribution';
-    $args['totalbillingcycles'] = $params['installments'];
+    $args['totalbillingcycles'] = CRM_Utils_Array::value('installments', $params);
     $args['version'] = '56.0';
     $args['profilereference'] = "i={$params['invoiceID']}" .
       "&m=" .
@@ -450,16 +446,18 @@ class CRM_Core_Payment_PayPalImpl extends CRM_Core_Payment {
       return $result;
     }
 
-    /* Success */
-    $params['trxn_id'] = $result['transactionid'];
-    $params['gross_amount'] = $result['amt'];
-    $params['fee_amount'] = $result['feeamt'];
-    $params['net_amount'] = $result['settleamt'];
-    if ($params['net_amount'] == 0 && $params['fee_amount'] != 0) {
-      $params['net_amount'] = number_format(($params['gross_amount'] - $params['fee_amount']), 2);
-    }
-    $params['payment_status'] = $result['paymentstatus'];
-    $params['pending_reason'] = $result['pendingreason'];
+    /* Success - result looks like"
+     * array (
+     * 'profileid' => 'I-CP1U0PLG91R2',
+     * 'profilestatus' => 'ActiveProfile',
+     * 'timestamp' => '2018-05-07T03:55:52Z',
+     * 'correlationid' => 'e717999e9bf62',
+     * 'ack' => 'Success',
+     * 'version' => '56.0',
+     * 'build' => '39949200',)
+     */
+    $params['trxn_id'] = $result['profileid'];
+    $params['payment_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending');
 
     return $params;
   }
@@ -721,7 +719,7 @@ class CRM_Core_Payment_PayPalImpl extends CRM_Core_Payment {
    * @throws \Civi\Payment\Exception\PaymentProcessorException
    */
   public function cancelSubscription(&$message = '', $params = array()) {
-    if ($this->isPayPalType($this::PAYPAL_PRO)) {
+    if ($this->isPayPalType($this::PAYPAL_PRO) || $this->isPayPalType($this::PAYPAL_EXPRESS)) {
       $args = array();
       $this->initialize($args, 'ManageRecurringPaymentsProfileStatus');
 
diff --git a/civicrm/CRM/Core/Payment/PayPalProIPN.php b/civicrm/CRM/Core/Payment/PayPalProIPN.php
index d230762395..f16c7ceae8 100644
--- a/civicrm/CRM/Core/Payment/PayPalProIPN.php
+++ b/civicrm/CRM/Core/Payment/PayPalProIPN.php
@@ -161,13 +161,13 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
    * @param array $ids
    * @param array $objects
    * @param bool $first
-   * @return bool
+   * @return void
    */
   public function recur(&$input, &$ids, &$objects, $first) {
     if (!isset($input['txnType'])) {
-      CRM_Core_Error::debug_log_message("Could not find txn_type in input request");
+      Civi::log()->debug('PayPalProIPN: Could not find txn_type in input request.');
       echo "Failure: Invalid parameters<p>";
-      return FALSE;
+      return;
     }
 
     $recur = &$objects['contributionRecur'];
@@ -176,9 +176,9 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
     // make sure the invoice is valid and matches what we have in
     // the contribution record
     if ($recur->invoice_id != $input['invoice']) {
-      CRM_Core_Error::debug_log_message("Invoice values dont match between database and IPN request recur is " . $recur->invoice_id . " input is " . $input['invoice']);
+      Civi::log()->debug('PayPalProIPN: Invoice values dont match between database and IPN request recur is ' . $recur->invoice_id . ' input is ' . $input['invoice']);
       echo "Failure: Invoice values dont match between database and IPN request recur is " . $recur->invoice_id . " input is " . $input['invoice'];
-      return FALSE;
+      return;
     }
 
     $now = date('YmdHis');
@@ -211,21 +211,20 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
     //set transaction type
     $txnType = $this->retrieve('txn_type', 'String');
     //Changes for paypal pro recurring payment
-    $contributionStatuses = civicrm_api3('contribution', 'getoptions', array('field' => 'contribution_status_id'));
-    $contributionStatuses = $contributionStatuses['values'];
+    $contributionStatuses = array_flip(CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id', 'validate'));
     switch ($txnType) {
       case 'recurring_payment_profile_created':
         if (in_array($recur->contribution_status_id, array(
-              array_search('Pending', $contributionStatuses),
-              array_search('In Progress', $contributionStatuses),
+              $contributionStatuses['Pending'],
+              $contributionStatuses['In Progress'],
             ))
           && !empty($recur->processor_id)
         ) {
           echo "already handled";
-          return FALSE;
+          return;
         }
         $recur->create_date = $now;
-        $recur->contribution_status_id = 2;
+        $recur->contribution_status_id = $contributionStatuses['Pending'];
         $recur->processor_id = $this->retrieve('recurring_payment_id', 'String');
         $recur->trxn_id = $recur->processor_id;
         $subscriptionPaymentStatus = CRM_Core_Payment::RECURRING_PAYMENT_START;
@@ -256,9 +255,9 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
         if ($this->retrieve('profile_status', 'String') == 'Expired') {
           if (!empty($recur->end_date)) {
             echo "already handled";
-            return FALSE;
+            return;
           }
-          $recur->contribution_status_id = 1;
+          $recur->contribution_status_id = $contributionStatuses['Completed'];
           $recur->end_date = $now;
           $sendNotification = TRUE;
           $subscriptionPaymentStatus = CRM_Core_Payment::RECURRING_PAYMENT_END;
@@ -266,8 +265,8 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
 
         // make sure the contribution status is not done
         // since order of ipn's is unknown
-        if ($recur->contribution_status_id != 1) {
-          $recur->contribution_status_id = 5;
+        if ($recur->contribution_status_id != $contributionStatuses['Completed']) {
+          $recur->contribution_status_id = $contributionStatuses['In Progress'];
         }
         break;
     }
@@ -291,7 +290,7 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
     }
 
     if ($txnType != 'recurring_payment') {
-      return TRUE;
+      return;
     }
 
     if (!$first) {
@@ -300,9 +299,9 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
       $contribution = new CRM_Contribute_BAO_Contribution();
       $contribution->trxn_id = $input['trxn_id'];
       if ($contribution->trxn_id && $contribution->find()) {
-        CRM_Core_Error::debug_log_message("returning since contribution has already been handled");
+        Civi::log()->debug('PayPalProIPN: Returning since contribution has already been handled.');
         echo "Success: Contribution has already been handled<p>";
-        return TRUE;
+        return;
       }
 
       $contribution->contact_id = $recur->contact_id;
@@ -319,19 +318,17 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
     // CRM-13737 - am not aware of any reason why payment_date would not be set - this if is a belt & braces
     $objects['contribution']->receive_date = !empty($input['payment_date']) ? date('YmdHis', strtotime($input['payment_date'])) : $now;
 
-    $this->single($input, $ids, $objects,
-      TRUE, $first
-    );
+    $this->single($input, $ids, $objects, TRUE, $first);
   }
 
   /**
-   * @param $input
-   * @param $ids
-   * @param $objects
+   * @param array $input
+   * @param array $ids
+   * @param array $objects
    * @param bool $recur
    * @param bool $first
    *
-   * @return bool
+   * @return void
    */
   public function single(&$input, &$ids, &$objects, $recur = FALSE, $first = FALSE) {
     $contribution = &$objects['contribution'];
@@ -339,9 +336,9 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
     // make sure the invoice is valid and matches what we have in the contribution record
     if ((!$recur) || ($recur && $first)) {
       if ($contribution->invoice_id != $input['invoice']) {
-        CRM_Core_Error::debug_log_message("Invoice values dont match between database and IPN request");
+        Civi::log()->debug('PayPalProIPN: Invoice values dont match between database and IPN request.');
         echo "Failure: Invoice values dont match between database and IPN request<p>contribution is" . $contribution->invoice_id . " and input is " . $input['invoice'];
-        return FALSE;
+        return;
       }
     }
     else {
@@ -350,9 +347,9 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
 
     if (!$recur) {
       if ($contribution->total_amount != $input['amount']) {
-        CRM_Core_Error::debug_log_message("Amount values dont match between database and IPN request");
+        Civi::log()->debug('PayPalProIPN: Amount values dont match between database and IPN request.');
         echo "Failure: Amount values dont match between database and IPN request<p>";
-        return FALSE;
+        return;
       }
     }
     else {
@@ -363,24 +360,29 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
 
     $status = $input['paymentStatus'];
     if ($status == 'Denied' || $status == 'Failed' || $status == 'Voided') {
-      return $this->failed($objects, $transaction);
+      $this->failed($objects, $transaction);
+      return;
     }
     elseif ($status == 'Pending') {
-      return $this->pending($objects, $transaction);
+      $this->pending($objects, $transaction);
+      return;
     }
     elseif ($status == 'Refunded' || $status == 'Reversed') {
-      return $this->cancelled($objects, $transaction);
+      $this->cancelled($objects, $transaction);
+      return;
     }
     elseif ($status != 'Completed') {
-      return $this->unhandled($objects, $transaction);
+      $this->unhandled($objects, $transaction);
+      return;
     }
 
     // check if contribution is already completed, if so we ignore this ipn
-    if ($contribution->contribution_status_id == 1) {
+    $completedStatusId = CRM_Core_Pseudoconstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
+    if ($contribution->contribution_status_id == $completedStatusId) {
       $transaction->commit();
-      CRM_Core_Error::debug_log_message("returning since contribution has already been handled");
+      Civi::log()->debug('PayPalProIPN: Returning since contribution has already been handled.');
       echo "Success: Contribution has already been handled<p>";
-      return TRUE;
+      return;
     }
 
     $this->completeTransaction($input, $ids, $objects, $transaction, $recur);
@@ -397,6 +399,9 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
     // processor id & the handleNotification function (which should call the completetransaction api & by-pass this
     // entirely). The only thing the IPN class should really do is extract data from the request, validate it
     // & call completetransaction or call fail? (which may not exist yet).
+
+    Civi::log()->warning('Unreliable method used to get payment_processor_id for PayPal Pro IPN - this will cause problems if you have more than one instance');
+
     $paymentProcessorTypeID = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessorType',
       'PayPal', 'id', 'name'
     );
@@ -414,7 +419,7 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
    * (with the input parameters) & call this & all will be done
    *
    * @todo the references to POST throughout this class need to be removed
-   * @return bool
+   * @return void
    */
   public function main() {
     CRM_Core_Error::debug_var('GET', $_GET, TRUE, TRUE);
@@ -464,16 +469,10 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr
       }
     }
 
-    // This is an unreliable method as there could be more than one instance.
-    // Recommended approach is to use the civicrm/payment/ipn/xx url where xx is the payment
-    // processor id & the handleNotification function (which should call the completetransaction api & by-pass this
-    // entirely). The only thing the IPN class should really do is extract data from the request, validate it
-    // & call completetransaction or call fail? (which may not exist yet).
-
     $paymentProcessorID = self::getPayPalPaymentProcessorID();
 
     if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) {
-      return FALSE;
+      return;
     }
 
     self::$_paymentProcessor = &$objects['paymentProcessor'];
@@ -484,31 +483,27 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr
       if ($ids['contributionRecur']) {
         // check if first contribution is completed, else complete first contribution
         $first = TRUE;
-        if ($objects['contribution']->contribution_status_id == 1) {
+        $completedStatusId = CRM_Core_Pseudoconstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
+        if ($objects['contribution']->contribution_status_id == $completedStatusId) {
           $first = FALSE;
         }
-        return $this->recur($input, $ids, $objects, $first);
-      }
-      else {
-        return $this->single($input, $ids, $objects, FALSE, FALSE);
+        $this->recur($input, $ids, $objects, $first);
+        return;
       }
     }
-    else {
-      return $this->single($input, $ids, $objects, FALSE, FALSE);
-    }
+    $this->single($input, $ids, $objects, FALSE, FALSE);
   }
 
   /**
-   * @param $input
-   * @param $ids
+   * @param array $input
+   * @param array $ids
    *
-   * @return bool
+   * @return void
    * @throws CRM_Core_Exception
    */
   public function getInput(&$input, &$ids) {
-
     if (!$this->getBillingID($ids)) {
-      return FALSE;
+      return;
     }
 
     $input['txnType'] = self::retrieve('txn_type', 'String', 'POST', FALSE);
@@ -542,6 +537,7 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr
 
   /**
    * Handle payment express IPNs.
+   *
    * For one off IPNS no actual response is required
    * Recurring is more difficult as we have limited confirmation material
    * lets look up invoice id in recur_contribution & rely on the unique transaction id to ensure no
@@ -560,27 +556,30 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr
     // as membership id etc can be derived by the load objects fn
     $objects = $ids = $input = array();
     $isFirst = FALSE;
+    $input['invoice'] = self::getValue('i', FALSE);
     $input['txnType'] = $this->retrieve('txn_type', 'String');
-    if ($input['txnType'] != 'recurring_payment') {
+    $contributionRecur = civicrm_api3('contribution_recur', 'getsingle', array(
+      'return' => 'contact_id, id, payment_processor_id',
+      'invoice_id' => $input['invoice'],
+    ));
+
+    if ($input['txnType'] !== 'recurring_payment' && $input['txnType'] !== 'recurring_payment_profile_created') {
       throw new CRM_Core_Exception('Paypal IPNS not handled other than recurring_payments');
     }
-    $input['invoice'] = self::getValue('i', FALSE);
+
     $this->getInput($input, $ids);
-    if ($this->transactionExists($input['trxn_id'])) {
+    if ($input['txnType'] === 'recurring_payment' && $this->transactionExists($input['trxn_id'])) {
       throw new CRM_Core_Exception('This transaction has already been processed');
     }
 
-    $contributionRecur = civicrm_api3('contribution_recur', 'getsingle', array(
-        'return' => 'contact_id, id',
-        'invoice_id' => $input['invoice'],
-      ));
     $ids['contact'] = $contributionRecur['contact_id'];
     $ids['contributionRecur'] = $contributionRecur['id'];
-    $result = civicrm_api3('contribution', 'getsingle', array('invoice_id' => $input['invoice']));
+    $result = civicrm_api3('contribution', 'getsingle', ['invoice_id' => $input['invoice'], 'contribution_test' => '']);
 
     $ids['contribution'] = $result['id'];
-    //@todo hard - coding 'pending' for now
-    if ($result['contribution_status_id'] == 2) {
+    //@todo hardcoding 'pending' for now
+    $pendingStatusId = CRM_Core_Pseudoconstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending');
+    if ($result['contribution_status_id'] == $pendingStatusId) {
       $isFirst = TRUE;
     }
     // arg api won't get this - fix it
@@ -595,12 +594,12 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr
     // membership would be an easy add - but not relevant to my customer...
     $this->_component = $input['component'] = 'contribute';
     $input['trxn_date'] = date('Y-m-d-H-i-s', strtotime(self::retrieve('time_created', 'String')));
-    $paymentProcessorID = self::getPayPalPaymentProcessorID();
+    $paymentProcessorID = $contributionRecur['payment_processor_id'];
 
     if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) {
       throw new CRM_Core_Exception('Data did not validate');
     }
-    return $this->recur($input, $ids, $objects, $isFirst);
+    $this->recur($input, $ids, $objects, $isFirst);
   }
 
   /**
diff --git a/civicrm/CRM/Core/Payment/PayflowPro.php b/civicrm/CRM/Core/Payment/PayflowPro.php
index 2fcdf72408..2020b2a8cd 100644
--- a/civicrm/CRM/Core/Payment/PayflowPro.php
+++ b/civicrm/CRM/Core/Payment/PayflowPro.php
@@ -567,85 +567,4 @@ class CRM_Core_Payment_PayflowPro extends CRM_Core_Payment {
     return $responseData;
   }
 
-  /**
-   * @param int $recurringProfileID
-   * @param int $processorID
-   *
-   * @throws Exception
-   */
-  public function getRecurringTransactionStatus($recurringProfileID, $processorID) {
-    if (!defined('CURLOPT_SSLCERT')) {
-      CRM_Core_Error::fatal(ts('Payflow Pro requires curl with SSL support'));
-    }
-
-    /*
-     * define variables for connecting with the gateway
-     */
-
-    //if you have not set up a separate user account the vendor name is used as the username
-    if (!$this->_paymentProcessor['subject']) {
-      $user = $this->_paymentProcessor['user_name'];
-    }
-    else {
-      $user = $this->_paymentProcessor['subject'];
-    }
-    //$recurringProfileID = "RT0000000001";
-    //     c  $trythis =        $this->getRecurringTransactionStatus($recurringProfileID,17);
-
-    /*
-     *Create the array of variables to be sent to the processor from the $params array
-     * passed into this function
-     *
-     */
-
-    $payflow_query_array = array(
-      'USER' => $user,
-      'VENDOR' => $this->_paymentProcessor['user_name'],
-      'PARTNER' => $this->_paymentProcessor['signature'],
-      'PWD' => $this->_paymentProcessor['password'],
-      // C - Direct Payment using credit card
-      'TENDER' => 'C',
-      // A - Authorization, S - Sale
-      'TRXTYPE' => 'R',
-      'ACTION' => 'I',
-      //A for add recurring
-      //(M-modify,C-cancel,R-reactivate,
-      //I-inquiry,P-payment
-      'ORIGPROFILEID' => $recurringProfileID,
-      'PAYMENTHISTORY' => 'Y',
-    );
-
-    $payflow_query = $this->convert_to_nvp($payflow_query_array);
-    echo $payflow_query;
-    $submiturl = $this->_paymentProcessor['url_site'];
-    //ie. url at payment processor to submit to.
-    $responseData = self::submit_transaction($submiturl, $payflow_query);
-    /*
-     * Payment successfully sent to gateway - process the response now
-     */
-
-    $result = strstr($responseData, "RESULT");
-    $nvpArray = array();
-    while (strlen($result)) {
-      // name
-      $keypos = strpos($result, '=');
-      $keyval = substr($result, 0, $keypos);
-      // value
-      $valuepos = strpos($result, '&') ? strpos($result, '&') : strlen($result);
-      $valval = substr($result, $keypos + 1, $valuepos - $keypos - 1);
-      // decoding the respose
-      $nvpArray[$keyval] = $valval;
-      $result = substr($result, $valuepos + 1, strlen($result));
-    }
-
-    // @TODO Function is named getRecurringTransactionStatus() which
-    // suggests it returns a result. It sets a $result_code but doesn't return
-    // it, printing output instead?
-    $result_code = $nvpArray['RESULT'];
-    print_r($responseData);
-
-    //RESPMSG=Invalid Profile ID: Invalid recurring profile ID
-    //RT0000000001
-  }
-
 }
diff --git a/civicrm/CRM/Core/Permission.php b/civicrm/CRM/Core/Permission.php
index ee7b49f69d..ff732084aa 100644
--- a/civicrm/CRM/Core/Permission.php
+++ b/civicrm/CRM/Core/Permission.php
@@ -1470,6 +1470,9 @@ class CRM_Core_Permission {
       'create' => array('edit message templates', 'edit user-driven message templates', 'edit system workflow message templates'),
       'update' => array('edit message templates', 'edit user-driven message templates', 'edit system workflow message templates'),
     );
+
+    $permissions['report_template']['update'] = 'save Report Criteria';
+    $permissions['report_template']['create'] = 'save Report Criteria';
     return $permissions;
   }
 
diff --git a/civicrm/CRM/Core/PseudoConstant.php b/civicrm/CRM/Core/PseudoConstant.php
index 81bdb6f232..3392a8e371 100644
--- a/civicrm/CRM/Core/PseudoConstant.php
+++ b/civicrm/CRM/Core/PseudoConstant.php
@@ -536,7 +536,7 @@ class CRM_Core_PseudoConstant {
     $key = 'id',
     $force = NULL
   ) {
-    $cacheKey = "CRM_PC_{$name}_{$all}_{$key}_{$retrieve}_{$filter}_{$condition}_{$orderby}";
+    $cacheKey = CRM_Core_BAO_Cache::cleanKey("CRM_PC_{$name}_{$all}_{$key}_{$retrieve}_{$filter}_{$condition}_{$orderby}");
     $cache = CRM_Utils_Cache::singleton();
     $var = $cache->get($cacheKey);
     if ($var && empty($force)) {
diff --git a/civicrm/CRM/Core/Session.php b/civicrm/CRM/Core/Session.php
index 7c1c1a42a1..632a9e14ab 100644
--- a/civicrm/CRM/Core/Session.php
+++ b/civicrm/CRM/Core/Session.php
@@ -286,7 +286,7 @@ class CRM_Core_Session {
       $values = &$this->_session[$this->_key];
     }
     else {
-      $values = CRM_Core_BAO_Cache::getItem('CiviCRM Session', "CiviCRM_{$prefix}");
+      $values = Civi::cache('session')->get("CiviCRM_{$prefix}");
     }
 
     if ($values) {
diff --git a/civicrm/CRM/Cxn/CiviCxnHttp.php b/civicrm/CRM/Cxn/CiviCxnHttp.php
index f2b2c6357c..0d6024812c 100644
--- a/civicrm/CRM/Cxn/CiviCxnHttp.php
+++ b/civicrm/CRM/Cxn/CiviCxnHttp.php
@@ -54,7 +54,7 @@ class CRM_Cxn_CiviCxnHttp extends \Civi\Cxn\Rpc\Http\PhpHttp {
     $lowVerb = strtolower($verb);
 
     if ($lowVerb === 'get' && $this->cache) {
-      $cachePath = 'get/' . md5($url);
+      $cachePath = 'get_' . md5($url);
       $cacheLine = $this->cache->get($cachePath);
       if ($cacheLine && $cacheLine['expires'] > CRM_Utils_Time::getTimeRaw()) {
         return $cacheLine['data'];
@@ -66,7 +66,7 @@ class CRM_Cxn_CiviCxnHttp extends \Civi\Cxn\Rpc\Http\PhpHttp {
     if ($lowVerb === 'get' && $this->cache) {
       $expires = CRM_Utils_Http::parseExpiration($result[0]);
       if ($expires !== NULL) {
-        $cachePath = 'get/' . md5($url);
+        $cachePath = 'get_' . md5($url);
         $cacheLine = array(
           'url' => $url,
           'expires' => $expires,
@@ -106,4 +106,11 @@ class CRM_Cxn_CiviCxnHttp extends \Civi\Cxn\Rpc\Http\PhpHttp {
     return $result;
   }
 
+  /**
+   * @return \CRM_Utils_Cache_Interface|null
+   */
+  public function getCache() {
+    return $this->cache;
+  }
+
 }
diff --git a/civicrm/CRM/Dedupe/Merger.php b/civicrm/CRM/Dedupe/Merger.php
index a2fe7e7187..afb53df5f5 100644
--- a/civicrm/CRM/Dedupe/Merger.php
+++ b/civicrm/CRM/Dedupe/Merger.php
@@ -203,35 +203,23 @@ class CRM_Dedupe_Merger {
   /**
    * Get array tables and fields that reference civicrm_contact.id.
    *
-   * This includes core tables, custom group tables, tables added by the merge
-   * hook and (somewhat randomly) the entity_tag table.
+   * This function calls the merge hook and only exists to wrap the DAO function to support that deprecated call.
+   * The entityTypes hook is the recommended way to add tables to this result.
    *
-   * Refer to CRM-17454 for information on the danger of querying the information
-   * schema to derive this.
-   *
-   * This function calls the merge hook but the entityTypes hook is the recommended
-   * way to add tables to this result.
+   * I thought about adding another hook to alter tableReferences but decided it was unclear if there
+   * are use cases not covered by entityTables and instead we should wait & see.
    */
   public static function cidRefs() {
     if (isset(\Civi::$statics[__CLASS__]) && isset(\Civi::$statics[__CLASS__]['contact_references'])) {
       return \Civi::$statics[__CLASS__]['contact_references'];
     }
-    $contactReferences = array();
-    $coreReferences = CRM_Core_DAO::getReferencesToTable('civicrm_contact');
-    foreach ($coreReferences as $coreReference) {
-      if (!is_a($coreReference, 'CRM_Core_Reference_Dynamic')) {
-        $contactReferences[$coreReference->getReferenceTable()][] = $coreReference->getReferenceKey();
-      }
-    }
-    self::addCustomTablesExtendingContactsToCidRefs($contactReferences);
 
-    // FixME for time being adding below line statically as no Foreign key constraint defined for table 'civicrm_entity_tag'
-    $contactReferences['civicrm_entity_tag'][] = 'entity_id';
+    $contactReferences = $coreReferences = CRM_Core_DAO::getReferencesToContactTable();
 
-    // Allow hook_civicrm_merge() to adjust $cidRefs.
-    // Note that if entities are registered using the entityTypes hook there
-    // is no need to use this hook.
     CRM_Utils_Hook::merge('cidRefs', $contactReferences);
+    if ($contactReferences !== $coreReferences) {
+      Civi::log()->warning("Deprecated hook ::merge in context of 'cidRefs. Use entityTypes instead.", array('civi.tag' => 'deprecated'));
+    }
     \Civi::$statics[__CLASS__]['contact_references'] = $contactReferences;
     return \Civi::$statics[__CLASS__]['contact_references'];
   }
@@ -486,7 +474,8 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
     // getting all custom tables
     $customTables = array();
     if ($customTableToCopyFrom !== NULL) {
-      self::addCustomTablesExtendingContactsToCidRefs($customTables);
+      // @todo this duplicates cidRefs?
+      CRM_Core_DAO::appendCustomTablesExtendingContacts($customTables);
       $customTables = array_keys($customTables);
     }
 
@@ -1457,7 +1446,6 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
     $otherTree = CRM_Core_BAO_CustomGroup::getTree($main['contact_type'], NULL, $otherId, -1,
       CRM_Utils_Array::value('contact_sub_type', $other), NULL, TRUE, NULL, TRUE, $checkPermissions
     );
-    CRM_Core_DAO::freeResult();
 
     foreach ($otherTree as $gid => $group) {
       $foundField = FALSE;
@@ -1926,26 +1914,6 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
     }
   }
 
-  /**
-   * Add custom tables that extend contacts to the list of contact references.
-   *
-   * CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity seems like a safe-ish
-   * function to be sure all are retrieved & we don't miss subtypes or inactive or multiples
-   * - the down side is it is not cached.
-   *
-   * Further changes should be include tests in the CRM_Core_MergerTest class
-   * to ensure that disabled, subtype, multiple etc groups are still captured.
-   *
-   * @param array $cidRefs
-   */
-  public static function addCustomTablesExtendingContactsToCidRefs(&$cidRefs) {
-    $customValueTables = CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity('Contact');
-    $customValueTables->find();
-    while ($customValueTables->fetch()) {
-      $cidRefs[$customValueTables->table_name] = array('entity_id');
-    }
-  }
-
   /**
    * Create activities tracking the merge on affected contacts.
    *
@@ -2329,8 +2297,6 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
       // pair may have been flipped, so make sure we delete using both orders
       CRM_Core_BAO_PrevNextCache::deletePair($mainId, $otherId, $cacheKeyString, TRUE);
     }
-
-    CRM_Core_DAO::freeResult();
   }
 
 }
diff --git a/civicrm/CRM/Event/Form/Registration/Confirm.php b/civicrm/CRM/Event/Form/Registration/Confirm.php
index da1a8ee522..29b5c5a279 100644
--- a/civicrm/CRM/Event/Form/Registration/Confirm.php
+++ b/civicrm/CRM/Event/Form/Registration/Confirm.php
@@ -208,7 +208,8 @@ class CRM_Event_Form_Registration_Confirm extends CRM_Event_Form_Registration {
     $this->assignToTemplate();
 
     if ($this->_values['event']['is_monetary'] &&
-      ($this->_params[0]['amount'] || $this->_params[0]['amount'] == 0)
+      ($this->_params[0]['amount'] || $this->_params[0]['amount'] == 0) &&
+      !$this->_requireApproval
     ) {
       $this->_amount = array();
 
diff --git a/civicrm/CRM/Event/Form/Task.php b/civicrm/CRM/Event/Form/Task.php
index 263a644cba..cc7df4d31d 100644
--- a/civicrm/CRM/Event/Form/Task.php
+++ b/civicrm/CRM/Event/Form/Task.php
@@ -34,31 +34,10 @@
  */
 
 /**
- * This class generates task actions for CiviEvent
- *
+ * Class for event form task actions.
+ * FIXME: This needs refactoring to properly inherit from CRM_Core_Form_Task and share more functions.
  */
-class CRM_Event_Form_Task extends CRM_Core_Form {
-
-  /**
-   * The task being performed.
-   *
-   * @var int
-   */
-  protected $_task;
-
-  /**
-   * The additional clause that we restrict the search with.
-   *
-   * @var string
-   */
-  protected $_componentClause = NULL;
-
-  /**
-   * The array that holds all the component ids.
-   *
-   * @var array
-   */
-  protected $_componentIds;
+class CRM_Event_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * The array that holds all the participant ids.
@@ -80,9 +59,8 @@ class CRM_Event_Form_Task extends CRM_Core_Form {
 
   /**
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_participantIds = array();
 
     $values = $form->controller->exportValues($form->get('searchFormName'));
diff --git a/civicrm/CRM/Export/BAO/Export.php b/civicrm/CRM/Export/BAO/Export.php
index b86bbe8bd8..dd6fb35db9 100644
--- a/civicrm/CRM/Export/BAO/Export.php
+++ b/civicrm/CRM/Export/BAO/Export.php
@@ -41,6 +41,68 @@ class CRM_Export_BAO_Export {
   // CRM-7675
   const EXPORT_ROW_COUNT = 100000;
 
+  protected static $relationshipReturnProperties = [];
+
+  /**
+   * @param $value
+   * @param $locationTypeFields
+   * @param $relationshipTypes
+   *
+   * @return array
+   */
+  protected static function setRelationshipReturnProperties($value, $locationTypeFields, $relationshipTypes) {
+    $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
+    $relPhoneTypeId = $relIMProviderId = NULL;
+    if (!empty($value[2])) {
+      $relationField = CRM_Utils_Array::value(2, $value);
+      if (trim(CRM_Utils_Array::value(3, $value))) {
+        $relLocTypeId = CRM_Utils_Array::value(3, $value);
+      }
+      else {
+        $relLocTypeId = 'Primary';
+      }
+
+      if ($relationField == 'phone') {
+        $relPhoneTypeId = CRM_Utils_Array::value(4, $value);
+      }
+      elseif ($relationField == 'im') {
+        $relIMProviderId = CRM_Utils_Array::value(4, $value);
+      }
+    }
+    elseif (!empty($value[4])) {
+      $relationField = CRM_Utils_Array::value(4, $value);
+      $relLocTypeId = CRM_Utils_Array::value(5, $value);
+      if ($relationField == 'phone') {
+        $relPhoneTypeId = CRM_Utils_Array::value(6, $value);
+      }
+      elseif ($relationField == 'im') {
+        $relIMProviderId = CRM_Utils_Array::value(6, $value);
+      }
+    }
+    if (in_array($relationField, $locationTypeFields) && is_numeric($relLocTypeId)) {
+      if ($relPhoneTypeId) {
+        self::$relationshipReturnProperties[$relationshipTypes]['location'][$locationTypes[$relLocTypeId]]['phone-' . $relPhoneTypeId] = 1;
+      }
+      elseif ($relIMProviderId) {
+        self::$relationshipReturnProperties[$relationshipTypes]['location'][$locationTypes[$relLocTypeId]]['im-' . $relIMProviderId] = 1;
+      }
+      else {
+        self::$relationshipReturnProperties[$relationshipTypes]['location'][$locationTypes[$relLocTypeId]][$relationField] = 1;
+      }
+    }
+    else {
+      self::$relationshipReturnProperties[$relationshipTypes][$relationField] = 1;
+    }
+    return array($relationField);
+  }
+
+  /**
+   * @return array
+   */
+  public static function getRelationshipReturnProperties() {
+    return self::relationshipReturnProperties;
+  }
+
   /**
    * Get Querymode based on ExportMode
    *
@@ -348,58 +410,15 @@ class CRM_Export_BAO_Export {
       );
 
       foreach ($fields as $key => $value) {
-        $relationField = NULL;
-        $relationshipTypes = $fieldName = CRM_Utils_Array::value(1, $value);
+        $fieldName = CRM_Utils_Array::value(1, $value);
         if (!$fieldName) {
           continue;
         }
 
-        if (array_key_exists($relationshipTypes, $contactRelationshipTypes) && (!empty($value[2]) || !empty($value[4]))) {
-          $relPhoneTypeId = $relIMProviderId = NULL;
-          if (!empty($value[2])) {
-            $relationField = CRM_Utils_Array::value(2, $value);
-            if (trim(CRM_Utils_Array::value(3, $value))) {
-              $relLocTypeId = CRM_Utils_Array::value(3, $value);
-            }
-            else {
-              $relLocTypeId = 'Primary';
-            }
-
-            if ($relationField == 'phone') {
-              $relPhoneTypeId = CRM_Utils_Array::value(4, $value);
-            }
-            elseif ($relationField == 'im') {
-              $relIMProviderId = CRM_Utils_Array::value(4, $value);
-            }
-          }
-          elseif (!empty($value[4])) {
-            $relationField = CRM_Utils_Array::value(4, $value);
-            $relLocTypeId = CRM_Utils_Array::value(5, $value);
-            if ($relationField == 'phone') {
-              $relPhoneTypeId = CRM_Utils_Array::value(6, $value);
-            }
-            elseif ($relationField == 'im') {
-              $relIMProviderId = CRM_Utils_Array::value(6, $value);
-            }
-          }
-          if (in_array($relationField, $locationTypeFields) && is_numeric($relLocTypeId)) {
-            if ($relPhoneTypeId) {
-              $returnProperties[$relationshipTypes]['location'][$locationTypes[$relLocTypeId]]['phone-' . $relPhoneTypeId] = 1;
-            }
-            elseif ($relIMProviderId) {
-              $returnProperties[$relationshipTypes]['location'][$locationTypes[$relLocTypeId]]['im-' . $relIMProviderId] = 1;
-            }
-            else {
-              $returnProperties[$relationshipTypes]['location'][$locationTypes[$relLocTypeId]][$relationField] = 1;
-            }
-          }
-          else {
-            $returnProperties[$relationshipTypes][$relationField] = 1;
-          }
-        }
-
-        if ($relationField) {
-          // already handled.
+        if (array_key_exists($fieldName, $contactRelationshipTypes) && (!empty($value[2]) || !empty($value[4]))) {
+          self::setRelationshipReturnProperties($value, $locationTypeFields, $fieldName);
+          // @todo we can later not add this to this array but maintain a separate array.
+          $returnProperties = array_merge($returnProperties, self::$relationshipReturnProperties);
         }
         elseif (is_numeric(CRM_Utils_Array::value(2, $value))) {
           $locTypeId = $value[2];
@@ -582,81 +601,7 @@ INSERT INTO {$componentTable} SELECT distinct gc.contact_id FROM civicrm_group_c
       unset($returnProperties[$relationKeyHOH]['im_provider']);
     }
 
-    $allRelContactArray = $relationQuery = array();
-
-    foreach ($contactRelationshipTypes as $rel => $dnt) {
-      if ($relationReturnProperties = CRM_Utils_Array::value($rel, $returnProperties)) {
-        $allRelContactArray[$rel] = array();
-        // build Query for each relationship
-        $relationQuery[$rel] = new CRM_Contact_BAO_Query(NULL, $relationReturnProperties,
-          NULL, FALSE, FALSE, $queryMode
-        );
-        list($relationSelect, $relationFrom, $relationWhere, $relationHaving) = $relationQuery[$rel]->query();
-
-        list($id, $direction) = explode('_', $rel, 2);
-        // identify the relationship direction
-        $contactA = 'contact_id_a';
-        $contactB = 'contact_id_b';
-        if ($direction == 'b_a') {
-          $contactA = 'contact_id_b';
-          $contactB = 'contact_id_a';
-        }
-        if ($exportMode == CRM_Export_Form_Select::CONTACT_EXPORT) {
-          $relIDs = $ids;
-        }
-        elseif ($exportMode == CRM_Export_Form_Select::ACTIVITY_EXPORT) {
-          $sourceID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Source');
-          $dao = CRM_Core_DAO::executeQuery("
-            SELECT contact_id FROM civicrm_activity_contact
-            WHERE activity_id IN ( " . implode(',', $ids) . ") AND
-            record_type_id = {$sourceID}
-          ");
-
-          while ($dao->fetch()) {
-            $relIDs[] = $dao->contact_id;
-          }
-        }
-        else {
-          $component = self::exportComponent($exportMode);
-
-          if ($exportMode == CRM_Export_Form_Select::CASE_EXPORT) {
-            $relIDs = CRM_Case_BAO_Case::retrieveContactIdsByCaseId($ids);
-          }
-          else {
-            $relIDs = CRM_Core_DAO::getContactIDsFromComponent($ids, $component);
-          }
-        }
-
-        $relationshipJoin = $relationshipClause = '';
-        if (!$selectAll && $componentTable) {
-          $relationshipJoin = " INNER JOIN {$componentTable} ctTable ON ctTable.contact_id = {$contactA}";
-        }
-        elseif (!empty($relIDs)) {
-          $relID = implode(',', $relIDs);
-          $relationshipClause = " AND crel.{$contactA} IN ( {$relID} )";
-        }
-
-        $relationFrom = " {$relationFrom}
-                INNER JOIN civicrm_relationship crel ON crel.{$contactB} = contact_a.id AND crel.relationship_type_id = {$id}
-                {$relationshipJoin} ";
-
-        //check for active relationship status only
-        $today = date('Ymd');
-        $relationActive = " AND (crel.is_active = 1 AND ( crel.end_date is NULL OR crel.end_date >= {$today} ) )";
-        $relationWhere = " WHERE contact_a.is_deleted = 0 {$relationshipClause} {$relationActive}";
-        $relationGroupBy = CRM_Contact_BAO_Query::getGroupByFromSelectColumns($relationQuery[$rel]->_select, "crel.{$contactA}");
-        $relationSelect = "{$relationSelect}, {$contactA} as refContact ";
-        $relationQueryString = "$relationSelect $relationFrom $relationWhere $relationHaving $relationGroupBy";
-
-        $allRelContactDAO = CRM_Core_DAO::executeQuery($relationQueryString);
-        while ($allRelContactDAO->fetch()) {
-          //FIX Me: Migrate this to table rather than array
-          // build the array of all related contacts
-          $allRelContactArray[$rel][$allRelContactDAO->refContact] = clone($allRelContactDAO);
-        }
-        $allRelContactDAO->free();
-      }
-    }
+    list($relationQuery, $allRelContactArray) = self::buildRelatedContactArray($selectAll, $ids, $exportMode, $componentTable, $contactRelationshipTypes, $returnProperties, $queryMode);
 
     // make sure the groups stuff is included only if specifically specified
     // by the fields param (CRM-1969), else we limit the contacts outputted to only
@@ -1314,7 +1259,7 @@ VALUES $sqlValueString
    */
   public static function createTempTable(&$sqlColumns) {
     //creating a temporary table for the search result that need be exported
-    $exportTempTable = CRM_Core_DAO::createTempTableName('civicrm_export', TRUE);
+    $exportTempTable = CRM_Utils_SQL_TempTable::build()->setDurable()->setCategory('export')->getName();
 
     // also create the sql table
     $sql = "DROP TABLE IF EXISTS {$exportTempTable}";
@@ -2167,4 +2112,105 @@ WHERE  {$whereClause}";
     }
   }
 
+  /**
+   * Get the ids that we want to get related contact details for.
+   *
+   * @param array $ids
+   * @param int $exportMode
+   *
+   * @return array
+   */
+  protected static function getIDsForRelatedContact($ids, $exportMode) {
+    if ($exportMode == CRM_Export_Form_Select::CONTACT_EXPORT) {
+      return $ids;
+    }
+    if ($exportMode == CRM_Export_Form_Select::ACTIVITY_EXPORT) {
+      $relIDs = [];
+      $sourceID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Source');
+      $dao = CRM_Core_DAO::executeQuery("
+            SELECT contact_id FROM civicrm_activity_contact
+            WHERE activity_id IN ( " . implode(',', $ids) . ") AND
+            record_type_id = {$sourceID}
+          ");
+
+      while ($dao->fetch()) {
+        $relIDs[] = $dao->contact_id;
+      }
+      return $relIDs;
+    }
+    $component = self::exportComponent($exportMode);
+
+    if ($exportMode == CRM_Export_Form_Select::CASE_EXPORT) {
+      return CRM_Case_BAO_Case::retrieveContactIdsByCaseId($ids);
+    }
+    else {
+      return CRM_Core_DAO::getContactIDsFromComponent($ids, $component);
+    }
+  }
+
+  /**
+   * @param $selectAll
+   * @param $ids
+   * @param $exportMode
+   * @param $componentTable
+   * @param $contactRelationshipTypes
+   * @param $returnProperties
+   * @param $queryMode
+   * @return array
+   */
+  protected static function buildRelatedContactArray($selectAll, $ids, $exportMode, $componentTable, $contactRelationshipTypes, $returnProperties, $queryMode) {
+    $allRelContactArray = $relationQuery = array();
+
+    foreach ($contactRelationshipTypes as $rel => $dnt) {
+      if ($relationReturnProperties = CRM_Utils_Array::value($rel, $returnProperties)) {
+        $allRelContactArray[$rel] = array();
+        // build Query for each relationship
+        $relationQuery[$rel] = new CRM_Contact_BAO_Query(NULL, $relationReturnProperties,
+          NULL, FALSE, FALSE, $queryMode
+        );
+        list($relationSelect, $relationFrom, $relationWhere, $relationHaving) = $relationQuery[$rel]->query();
+
+        list($id, $direction) = explode('_', $rel, 2);
+        // identify the relationship direction
+        $contactA = 'contact_id_a';
+        $contactB = 'contact_id_b';
+        if ($direction == 'b_a') {
+          $contactA = 'contact_id_b';
+          $contactB = 'contact_id_a';
+        }
+        $relIDs = self::getIDsForRelatedContact($ids, $exportMode);
+
+        $relationshipJoin = $relationshipClause = '';
+        if (!$selectAll && $componentTable) {
+          $relationshipJoin = " INNER JOIN {$componentTable} ctTable ON ctTable.contact_id = {$contactA}";
+        }
+        elseif (!empty($relIDs)) {
+          $relID = implode(',', $relIDs);
+          $relationshipClause = " AND crel.{$contactA} IN ( {$relID} )";
+        }
+
+        $relationFrom = " {$relationFrom}
+                INNER JOIN civicrm_relationship crel ON crel.{$contactB} = contact_a.id AND crel.relationship_type_id = {$id}
+                {$relationshipJoin} ";
+
+        //check for active relationship status only
+        $today = date('Ymd');
+        $relationActive = " AND (crel.is_active = 1 AND ( crel.end_date is NULL OR crel.end_date >= {$today} ) )";
+        $relationWhere = " WHERE contact_a.is_deleted = 0 {$relationshipClause} {$relationActive}";
+        $relationGroupBy = CRM_Contact_BAO_Query::getGroupByFromSelectColumns($relationQuery[$rel]->_select, "crel.{$contactA}");
+        $relationSelect = "{$relationSelect}, {$contactA} as refContact ";
+        $relationQueryString = "$relationSelect $relationFrom $relationWhere $relationHaving $relationGroupBy";
+
+        $allRelContactDAO = CRM_Core_DAO::executeQuery($relationQueryString);
+        while ($allRelContactDAO->fetch()) {
+          //FIX Me: Migrate this to table rather than array
+          // build the array of all related contacts
+          $allRelContactArray[$rel][$allRelContactDAO->refContact] = clone($allRelContactDAO);
+        }
+        $allRelContactDAO->free();
+      }
+    }
+    return array($relationQuery, $allRelContactArray);
+  }
+
 }
diff --git a/civicrm/CRM/Export/Form/Select.php b/civicrm/CRM/Export/Form/Select.php
index 3e36b238ca..af478d4adf 100644
--- a/civicrm/CRM/Export/Form/Select.php
+++ b/civicrm/CRM/Export/Form/Select.php
@@ -36,7 +36,7 @@
 /**
  * This class gets the name of the file to upload
  */
-class CRM_Export_Form_Select extends CRM_Core_Form {
+class CRM_Export_Form_Select extends CRM_Core_Form_Task {
 
   /**
    * Various Contact types.
@@ -70,20 +70,6 @@ class CRM_Export_Form_Select extends CRM_Core_Form {
 
   public $_componentTable;
 
-  /**
-   * Must be set to entity table name (eg. civicrm_participant) by child class
-   *
-   * @var string
-   */
-  static $tableName = NULL;
-
-  /**
-   * Must be set to entity shortname (eg. event)
-   *
-   * @var string
-   */
-  static $entityShortname = NULL;
-
   /**
    * Build all the data structures needed to build the form.
    *
@@ -108,23 +94,14 @@ class CRM_Export_Form_Select extends CRM_Core_Form {
     $this->_componentIds = array();
     $this->_componentClause = NULL;
 
-    $stateMachine = $this->controller->getStateMachine();
-    $formName = CRM_Utils_System::getClassName($stateMachine);
-    $isStandalone = $formName == 'CRM_Export_StateMachine_Standalone';
-
     // we need to determine component export
-    $componentName = explode('_', $formName);
     $components = array('Contact', 'Contribute', 'Member', 'Event', 'Pledge', 'Case', 'Grant', 'Activity');
 
-    if ($isStandalone) {
-      $componentName = array('CRM', $this->controller->get('entity'));
-    }
-
-    $componentMode = $this->controller->get('component_mode');
     // FIXME: This should use a modified version of CRM_Contact_Form_Search::getModeValue but it doesn't have all the contexts
-    switch ($componentMode) {
+    switch ($this->getQueryMode()) {
       case CRM_Contact_BAO_Query::MODE_CONTRIBUTE:
         $entityShortname = 'Contribute';
+        $entityDAOName = $entityShortname;
         break;
 
       case CRM_Contact_BAO_Query::MODE_MEMBER:
@@ -134,33 +111,41 @@ class CRM_Export_Form_Select extends CRM_Core_Form {
 
       case CRM_Contact_BAO_Query::MODE_EVENT:
         $entityShortname = 'Event';
+        $entityDAOName = $entityShortname;
         break;
 
       case CRM_Contact_BAO_Query::MODE_PLEDGE:
         $entityShortname = 'Pledge';
+        $entityDAOName = $entityShortname;
         break;
 
       case CRM_Contact_BAO_Query::MODE_CASE:
         $entityShortname = 'Case';
+        $entityDAOName = $entityShortname;
         break;
 
       case CRM_Contact_BAO_Query::MODE_GRANT:
         $entityShortname = 'Grant';
+        $entityDAOName = $entityShortname;
         break;
 
       case CRM_Contact_BAO_Query::MODE_ACTIVITY:
         $entityShortname = 'Activity';
+        $entityDAOName = $entityShortname;
         break;
 
       default:
+        // FIXME: Code cleanup, we may not need to do this $componentName code here.
+        $formName = CRM_Utils_System::getClassName($this->controller->getStateMachine());
+        $componentName = explode('_', $formName);
+        if ($formName == 'CRM_Export_StateMachine_Standalone') {
+          $componentName = array('CRM', $this->controller->get('entity'));
+        }
         $entityShortname = $componentName[1]; // Contact
+        $entityDAOName = $entityShortname;
         break;
     }
 
-    if (empty($entityDAOName)) {
-      $entityDAOName = $entityShortname;
-    }
-
     if (in_array($entityShortname, $components)) {
       $this->_exportMode = constant('CRM_Export_Form_Select::' . strtoupper($entityShortname) . '_EXPORT');
       $formTaskClassName = "CRM_{$entityShortname}_Form_Task";
@@ -210,7 +195,7 @@ class CRM_Export_Form_Select extends CRM_Core_Form {
       }
     }
 
-    $formTaskClassName::preProcessCommon($this, !$isStandalone);
+    $formTaskClassName::preProcessCommon($this);
 
     // $component is used on CRM/Export/Form/Select.tpl to display extra information for contact export
     ($this->_exportMode == self::CONTACT_EXPORT) ? $component = FALSE : $component = TRUE;
@@ -358,7 +343,7 @@ FROM   {$this->_componentTable}
    * @return bool|array
    *   mixed true or array of errors
    */
-  static public function formRule($params, $files, $self) {
+  public static function formRule($params, $files, $self) {
     $errors = array();
 
     if (CRM_Utils_Array::value('mergeOption', $params) == self::EXPORT_MERGE_SAME_ADDRESS &&
@@ -386,7 +371,7 @@ FROM   {$this->_componentTable}
   /**
    * Process the uploaded file.
    *
-   * @return void
+   * @throws \CRM_Core_Exception
    */
   public function postProcess() {
     $params = $this->controller->exportValues($this->_name);
@@ -535,4 +520,13 @@ FROM   {$this->_componentTable}
     return $options;
   }
 
+  /**
+   * Get the query mode (eg. CRM_Core_BAO_Query::MODE_CASE)
+   *
+   * @return int
+   */
+  public function getQueryMode() {
+    return (int) ($this->queryMode ?: $this->controller->get('component_mode'));
+  }
+
 }
diff --git a/civicrm/CRM/Export/Form/Select/Case.php b/civicrm/CRM/Export/Form/Select/Case.php
new file mode 100644
index 0000000000..c95aba984a
--- /dev/null
+++ b/civicrm/CRM/Export/Form/Select/Case.php
@@ -0,0 +1,53 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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-2018
+ */
+
+/**
+ * This class gets the name of the file to upload
+ */
+class CRM_Export_Form_Select_Case extends CRM_Export_Form_Select {
+
+  /**
+   * @var int
+   */
+  protected $queryMode = CRM_Contact_BAO_Query::MODE_CASE;
+
+  /**
+   * Use the form name to create the tpl file name.
+   *
+   * @return string
+   */
+  public function getTemplateFileName() {
+    return 'CRM/Export/Form/Select.tpl';
+  }
+
+}
diff --git a/civicrm/CRM/Extension/Mapper.php b/civicrm/CRM/Extension/Mapper.php
index 8615513566..38ff7d1951 100644
--- a/civicrm/CRM/Extension/Mapper.php
+++ b/civicrm/CRM/Extension/Mapper.php
@@ -282,7 +282,7 @@ class CRM_Extension_Mapper {
 
     $moduleExtensions = NULL;
     if ($this->cache && !$fresh) {
-      $moduleExtensions = $this->cache->get($this->cacheKey . '/moduleFiles');
+      $moduleExtensions = $this->cache->get($this->cacheKey . '_moduleFiles');
     }
 
     if (!is_array($moduleExtensions)) {
@@ -315,7 +315,7 @@ class CRM_Extension_Mapper {
       }
 
       if ($this->cache) {
-        $this->cache->set($this->cacheKey . '/moduleFiles', $moduleExtensions);
+        $this->cache->set($this->cacheKey . '_moduleFiles', $moduleExtensions);
       }
     }
     return $moduleExtensions;
@@ -461,7 +461,7 @@ class CRM_Extension_Mapper {
     $this->infos = array();
     $this->moduleExtensions = NULL;
     if ($this->cache) {
-      $this->cache->delete($this->cacheKey . '/moduleFiles');
+      $this->cache->delete($this->cacheKey . '_moduleFiles');
     }
     // FIXME: How can code so code wrong be so right?
     CRM_Extension_System::singleton()->getClassLoader()->refresh();
diff --git a/civicrm/CRM/Financial/Page/AJAX.php b/civicrm/CRM/Financial/Page/AJAX.php
index 1c3a9bc266..c5a51b5f99 100644
--- a/civicrm/CRM/Financial/Page/AJAX.php
+++ b/civicrm/CRM/Financial/Page/AJAX.php
@@ -293,6 +293,8 @@ class CRM_Financial_Page_AJAX {
       'civicrm_financial_trxn.currency as currency',
       'civicrm_financial_trxn.status_id as status',
       'civicrm_financial_trxn.check_number as check_number',
+      'civicrm_financial_trxn.card_type_id',
+      'civicrm_financial_trxn.pan_truncation',
     );
 
     $columnHeader = array(
diff --git a/civicrm/CRM/Friend/BAO/Friend.php b/civicrm/CRM/Friend/BAO/Friend.php
index 2d650fd8d7..75e65ba216 100644
--- a/civicrm/CRM/Friend/BAO/Friend.php
+++ b/civicrm/CRM/Friend/BAO/Friend.php
@@ -29,15 +29,22 @@
  *
  * @package CRM
  * @copyright CiviCRM LLC (c) 2004-2018
- * $Id$
  *
  */
 
 /**
- * This class contains the funtions for Friend
+ * This class contains the functions for Friend
  *
  */
 class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
+
+  /**
+   * Tell a friend id in db.
+   *
+   * @var int
+   */
+  public $_friendId;
+
   /**
    */
   public function __construct() {
@@ -74,13 +81,9 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
    */
   public static function retrieve(&$params, &$values) {
     $friend = new CRM_Friend_DAO_Friend();
-
     $friend->copyValues($params);
-
     $friend->find(TRUE);
-
     CRM_Core_DAO::storeValues($friend, $values);
-
     return $values;
   }
 
@@ -88,15 +91,17 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
    * Takes an associative array and creates a friend object.
    *
    * @param array $params
-   *   (reference ) an assoc array of name/value pairs.
+   *   (reference) an assoc array of name/value pairs.
    *
-   * @return void
+   * @throws \CRM_Core_Exception
    */
   public static function create(&$params) {
     $transaction = new CRM_Core_Transaction();
 
     $mailParams = array();
-    //create contact corresponding to each friend
+    $contactParams = array();
+
+    // create contact corresponding to each friend
     foreach ($params['friend'] as $key => $details) {
       if ($details["first_name"]) {
         $contactParams[$key] = array(
@@ -111,14 +116,15 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
       }
     }
 
-    $frndParams = array();
-    $frndParams['entity_id'] = $params['entity_id'];
-    $frndParams['entity_table'] = $params['entity_table'];
-    self::getValues($frndParams);
+    $friendParams = [
+      'entity_id' => $params['entity_id'],
+      'entity_table' => $params['entity_table'],
+    ];
+    self::getValues($friendParams);
 
     $activityTypeId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', 'Tell a Friend', 'value', 'name');
 
-    //create activity
+    // create activity
     $activityParams = array(
       'source_contact_id' => $params['source_contact_id'],
       'source_record_id' => NULL,
@@ -127,20 +133,19 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
       'activity_date_time' => date("YmdHis"),
       'subject' => ts('Tell a Friend') . ": {$params['title']}",
       'details' => $params['suggested_message'],
-      'status_id' => 2,
+      'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'),
       'is_test' => $params['is_test'],
       'campaign_id' => CRM_Utils_Array::value('campaign_id', $params),
     );
 
-    //activity creation
+    // activity creation
     $activity = CRM_Activity_BAO_Activity::create($activityParams);
     $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
     $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
 
-    //friend contacts creation
+    // friend contacts creation
     foreach ($contactParams as $key => $value) {
-
-      //create contact only if it does not exits in db
+      // create contact only if it does not exits in db
       $value['email'] = $value['email-Primary'];
       $contactID = CRM_Contact_BAO_Contact::getFirstDuplicateContact($value, 'Individual', 'Supervised', array(), FALSE);
 
@@ -167,28 +172,28 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
 
     $transaction->commit();
 
-    //process sending of mails
+    // Process sending of mails
     $mailParams['title'] = CRM_Utils_Array::value('title', $params);
-    $mailParams['general_link'] = CRM_Utils_Array::value('general_link', $frndParams);
+    $mailParams['general_link'] = CRM_Utils_Array::value('general_link', $friendParams);
     $mailParams['message'] = CRM_Utils_Array::value('suggested_message', $params);
 
-    // get domain
-    $domainDetails = CRM_Core_BAO_Domain::getNameAndEmail();
-    list($username, $mailParams['domain']) = explode('@', $domainDetails[1]);
+    // Default "from email address" is default domain address.
+    // This is normally overridden by one of the if statements below
+    list($_, $mailParams['email_from']) = CRM_Core_BAO_Domain::getNameAndEmail();
+    list($username, $mailParams['domain']) = explode('@', $mailParams['email_from']);
 
     $default = array();
     $findProperties = array('id' => $params['entity_id']);
 
     if ($params['entity_table'] == 'civicrm_contribution_page') {
-
       $returnProperties = array('receipt_from_email', 'is_email_receipt');
       CRM_Core_DAO::commonRetrieve('CRM_Contribute_DAO_ContributionPage',
         $findProperties,
         $default,
         $returnProperties
       );
-      //if is_email_receipt is set then take receipt_from_email
-      //as from_email
+
+      // if is_email_receipt is set then take receipt_from_email as from_email
       if (!empty($default['is_email_receipt']) && !empty($default['receipt_from_email'])) {
         $mailParams['email_from'] = $default['receipt_from_email'];
       }
@@ -197,7 +202,6 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
       $mailParams['module'] = 'contribute';
     }
     elseif ($params['entity_table'] == 'civicrm_event') {
-
       $returnProperties = array('confirm_from_email', 'is_email_confirm');
       CRM_Core_DAO::commonRetrieve('CRM_Event_DAO_Event',
         $findProperties,
@@ -205,10 +209,7 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
         $returnProperties
       );
 
-      $mailParams['email_from'] = $domainDetails['1'];
-
-      //if is_email_confirm is set then take confirm_from_email
-      //as from_email
+      // if is_email_confirm is set then take confirm_from_email as from_email
       if (!empty($default['is_email_confirm']) && !empty($default['confirm_from_email'])) {
         $mailParams['email_from'] = $default['confirm_from_email'];
       }
@@ -226,14 +227,14 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
 
     $mailParams['page_url'] = CRM_Utils_System::url($urlPath, "reset=1&id={$params['entity_id']}", TRUE, NULL, FALSE, TRUE);
 
-    //send mail
+    // Send the email
     self::sendMail($params['source_contact_id'], $mailParams);
   }
 
   /**
    * Build the form object.
    *
-   * @param CRM_Core_Form $form
+   * @param CRM_Friend_Form $form
    *   Form object.
    *
    * @return void
@@ -263,12 +264,12 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
   }
 
   /**
-   * The function sets the deafult values of the form.
+   * The function sets the default values of the form.
    *
    * @param array $defaults
    *   (reference) the default values.
    *
-   * @return booelan
+   * @return bool
    *   whether anything was found
    */
   public static function getValues(&$defaults) {
@@ -283,7 +284,7 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
   }
 
   /**
-   * Process that send tell a friend e-mails
+   * Process that sends tell a friend e-mails
    *
    * @param int $contactID
    * @param array $values
@@ -302,28 +303,27 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
       $values['email_from'] = $email;
     }
 
+    $templateParams = array(
+      'groupName' => 'msg_tpl_workflow_friend',
+      'valueName' => 'friend',
+      'contactId' => $contactID,
+      'tplParams' => array(
+        $values['module'] => $values['module'],
+        'senderContactName' => $fromName,
+        'title' => $values['title'],
+        'generalLink' => $values['general_link'],
+        'pageURL' => $values['page_url'],
+        'senderMessage' => $values['message'],
+      ),
+      'from' => "$fromName (via {$values['domain']}) <{$values['email_from']}>",
+      'replyTo' => $email,
+    );
+
     foreach ($values['email'] as $displayName => $emailTo) {
       if ($emailTo) {
-        // FIXME: factor the below out of the foreach loop
-        CRM_Core_BAO_MessageTemplate::sendTemplate(
-          array(
-            'groupName' => 'msg_tpl_workflow_friend',
-            'valueName' => 'friend',
-            'contactId' => $contactID,
-            'tplParams' => array(
-              $values['module'] => $values['module'],
-              'senderContactName' => $fromName,
-              'title' => $values['title'],
-              'generalLink' => $values['general_link'],
-              'pageURL' => $values['page_url'],
-              'senderMessage' => $values['message'],
-            ),
-            'from' => "$fromName (via {$values['domain']}) <{$values['email_from']}>",
-            'toName' => $displayName,
-            'toEmail' => $emailTo,
-            'replyTo' => $email,
-          )
-        );
+        $templateParams['toName'] = $displayName;
+        $templateParams['toEmail'] = $emailTo;
+        CRM_Core_BAO_MessageTemplate::sendTemplate($templateParams);
       }
     }
   }
@@ -336,16 +336,14 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
    * pairs
    *
    * @param array $params
-   *   (reference ) an assoc array of name/value pairs.
+   *   (reference) an assoc array of name/value pairs.
    *
-   * @return CRM_Friend_BAO_Friend
+   * @return CRM_Friend_DAO_Friend
    */
   public static function addTellAFriend(&$params) {
     $friendDAO = new CRM_Friend_DAO_Friend();
-
     $friendDAO->copyValues($params);
     $friendDAO->is_active = CRM_Utils_Array::value('is_active', $params, FALSE);
-
     $friendDAO->save();
 
     return $friendDAO;
diff --git a/civicrm/CRM/Friend/Form.php b/civicrm/CRM/Friend/Form.php
index 7f0329ac49..9b2a9c9f76 100644
--- a/civicrm/CRM/Friend/Form.php
+++ b/civicrm/CRM/Friend/Form.php
@@ -45,14 +45,21 @@ class CRM_Friend_Form extends CRM_Core_Form {
   const NUM_OPTION = 3;
 
   /**
-   * The id of the entity that we are proceessing.
+   * The id of the entity that we are processing.
    *
    * @var int
    */
   protected $_entityId;
 
   /**
-   * The table name of the entity that we are proceessing.
+   * Tell a friend id in db.
+   *
+   * @var int
+   */
+  public $_friendId;
+
+  /**
+   * The table name of the entity that we are processing.
    *
    * @var string
    */
diff --git a/civicrm/CRM/Grant/Form/Task.php b/civicrm/CRM/Grant/Form/Task.php
index 51f505428e..58fd8f9acf 100644
--- a/civicrm/CRM/Grant/Form/Task.php
+++ b/civicrm/CRM/Grant/Form/Task.php
@@ -34,31 +34,10 @@
  */
 
 /**
- * This class generates task actions for CiviEvent
- *
+ * Class for grant form task actions.
+ * FIXME: This needs refactoring to properly inherit from CRM_Core_Form_Task and share more functions.
  */
-class CRM_Grant_Form_Task extends CRM_Core_Form {
-
-  /**
-   * The task being performed.
-   *
-   * @var int
-   */
-  protected $_task;
-
-  /**
-   * The additional clause that we restrict the search with.
-   *
-   * @var string
-   */
-  protected $_componentClause = NULL;
-
-  /**
-   * The array that holds all the component ids.
-   *
-   * @var array
-   */
-  protected $_componentIds;
+class CRM_Grant_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * The array that holds all the grant ids.
@@ -80,9 +59,8 @@ class CRM_Grant_Form_Task extends CRM_Core_Form {
 
   /**
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_grantIds = array();
 
     $values = $form->controller->exportValues('Search');
@@ -127,7 +105,7 @@ class CRM_Grant_Form_Task extends CRM_Core_Form {
     $form->_grantIds = $form->_componentIds = $ids;
 
     //set the context for redirection for any task actions
-    $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $this);
+    $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $form);
     $urlParams = 'force=1';
     if (CRM_Utils_Rule::qfKey($qfKey)) {
       $urlParams .= "&qfKey=$qfKey";
diff --git a/civicrm/CRM/Group/Form/Edit.php b/civicrm/CRM/Group/Form/Edit.php
index a4d944e18f..810ca19d1b 100644
--- a/civicrm/CRM/Group/Form/Edit.php
+++ b/civicrm/CRM/Group/Form/Edit.php
@@ -364,7 +364,7 @@ WHERE  title = %1
    * Process the form when submitted.
    */
   public function postProcess() {
-    CRM_Utils_System::flushCache('CRM_Core_DAO_Group');
+    CRM_Utils_System::flushCache();
 
     $updateNestingCache = FALSE;
     if ($this->_action & CRM_Core_Action::DELETE) {
@@ -458,7 +458,7 @@ WHERE  title = %1
       $potentialParentGroupIds = array_keys($groupNames);
     }
 
-    $parentGroupSelectValues = array('' => '- ' . ts('select group') . ' -');
+    $parentGroupSelectValues = array();
     foreach ($potentialParentGroupIds as $potentialParentGroupId) {
       if (array_key_exists($potentialParentGroupId, $groupNames)) {
         $parentGroupSelectValues[$potentialParentGroupId] = $groupNames[$potentialParentGroupId];
@@ -472,7 +472,7 @@ WHERE  title = %1
       else {
         $required = FALSE;
       }
-      $form->add('select', 'parents', ts('Add Parent'), $parentGroupSelectValues, $required, array('class' => 'crm-select2'));
+      $form->add('select', 'parents', ts('Add Parent'), $parentGroupSelectValues, $required, array('class' => 'crm-select2', 'multiple' => TRUE));
     }
 
     return $parentGroups;
diff --git a/civicrm/CRM/Group/Form/Search.php b/civicrm/CRM/Group/Form/Search.php
index f05dfdcb83..a8edc042fd 100644
--- a/civicrm/CRM/Group/Form/Search.php
+++ b/civicrm/CRM/Group/Form/Search.php
@@ -81,6 +81,17 @@ class CRM_Group_Form_Search extends CRM_Core_Form {
       NULL, NULL, NULL, NULL, '&nbsp;&nbsp;&nbsp;'
     );
 
+    $componentModes = CRM_Contact_Form_Search::getModeSelect();
+    if (count($componentModes) > 1) {
+      $this->add('select',
+        'component_mode',
+        ts('View Results As'),
+        $componentModes,
+        FALSE,
+        array('class' => 'crm-select2')
+      );
+    }
+
     $this->addButtons(array(
       array(
         'type' => 'refresh',
@@ -97,7 +108,7 @@ class CRM_Group_Form_Search extends CRM_Core_Form {
     $params = $this->controller->exportValues($this->_name);
     $parent = $this->controller->getParent();
     if (!empty($params)) {
-      $fields = array('title', 'created_by', 'group_type', 'visibility', 'active_status', 'inactive_status');
+      $fields = array('title', 'created_by', 'group_type', 'visibility', 'active_status', 'inactive_status', 'component_mode');
       foreach ($fields as $field) {
         if (isset($params[$field]) &&
           !CRM_Utils_System::isNull($params[$field])
diff --git a/civicrm/CRM/Group/Page/AJAX.php b/civicrm/CRM/Group/Page/AJAX.php
index a1d9211f9f..da2e8f1ea6 100644
--- a/civicrm/CRM/Group/Page/AJAX.php
+++ b/civicrm/CRM/Group/Page/AJAX.php
@@ -54,6 +54,7 @@ class CRM_Group_Page_AJAX {
         'created_by' => 'String',
         'group_type' => 'String',
         'visibility' => 'String',
+        'component_mode' => 'String',
         'status' => 'Integer',
         'parentsOnly' => 'Integer',
         'showOrgInfo' => 'Boolean',
diff --git a/civicrm/CRM/Logging/Schema.php b/civicrm/CRM/Logging/Schema.php
index 996653ce6c..a24906ef91 100644
--- a/civicrm/CRM/Logging/Schema.php
+++ b/civicrm/CRM/Logging/Schema.php
@@ -945,7 +945,7 @@ COLS;
    * but this is the only entity currently available...
    */
   public function getLogTablesForContact() {
-    $tables = array_keys(CRM_Dedupe_Merger::cidRefs());
+    $tables = array_keys(CRM_Core_DAO::getReferencesToContactTable());
     return array_intersect($tables, $this->tables);
   }
 
diff --git a/civicrm/CRM/Mailing/BAO/Mailing.php b/civicrm/CRM/Mailing/BAO/Mailing.php
index 8e34d917e1..27a46fd693 100644
--- a/civicrm/CRM/Mailing/BAO/Mailing.php
+++ b/civicrm/CRM/Mailing/BAO/Mailing.php
@@ -1453,7 +1453,7 @@ ORDER BY   civicrm_email.is_bulkmail DESC
     }
     $mailing->domain_id = CRM_Utils_Array::value('domain_id', $params, CRM_Core_Config::domainID());
 
-    if (!isset($params['replyto_email']) &&
+    if (((!$id && empty($params['replyto_email'])) || !isset($params['replyto_email'])) &&
       isset($params['from_email'])
     ) {
       $params['replyto_email'] = $params['from_email'];
diff --git a/civicrm/CRM/Mailing/BAO/MailingJob.php b/civicrm/CRM/Mailing/BAO/MailingJob.php
index 9b2a80b207..398afeeaa2 100644
--- a/civicrm/CRM/Mailing/BAO/MailingJob.php
+++ b/civicrm/CRM/Mailing/BAO/MailingJob.php
@@ -598,6 +598,7 @@ VALUES (%1, %2, %3, %4, %5, %6, %7)
     $returnProperties = $mailing->getReturnProperties();
     $params = $targetParams = $deliveredParams = array();
     $count = 0;
+    $retryGroup = FALSE;
 
     // CRM-15702: Sending bulk sms to contacts without e-mail address fails.
     // Solution is to skip checking for on hold
@@ -684,9 +685,11 @@ VALUES (%1, %2, %3, %4, %5, %6, %7)
           CRM_Core_Error::debug_log_message("SMTP Socket Error or failed to set sender error. Message: $message, Code: $code");
 
           // these are socket write errors which most likely means smtp connection errors
-          // lets skip them
+          // lets skip them and reconnect.
           $smtpConnectionErrors++;
           if ($smtpConnectionErrors <= 5) {
+            $mailer->disconnect();
+            $retryGroup = TRUE;
             continue;
           }
 
@@ -776,6 +779,10 @@ VALUES (%1, %2, %3, %4, %5, %6, %7)
       $job_date
     );
 
+    if ($retryGroup) {
+      return FALSE;
+    }
+
     return $result;
   }
 
@@ -817,11 +824,54 @@ AND    status IN ( 'Scheduled', 'Running', 'Paused' )
         2 => array(date('YmdHis'), 'Timestamp'),
       );
       CRM_Core_DAO::executeQuery($sql, $params);
-
-      CRM_Core_Session::setStatus(ts('The mailing has been canceled.'), ts('Canceled'), 'success');
     }
   }
 
+  /**
+   * Pause a mailing
+   *
+   * @param int $mailingID
+   *   The id of the mailing to be paused.
+   */
+  public static function pause($mailingID) {
+    $sql = "
+      UPDATE civicrm_mailing_job
+      SET status = 'Paused'
+      WHERE mailing_id = %1
+      AND is_test = 0
+      AND status IN ('Scheduled', 'Running')
+    ";
+    CRM_Core_DAO::executeQuery($sql, array(1 => array($mailingID, 'Integer')));
+  }
+
+  /**
+   * Resume a mailing
+   *
+   * @param int $mailingID
+   *   The id of the mailing to be resumed.
+   */
+  public static function resume($mailingID) {
+    $sql = "
+      UPDATE civicrm_mailing_job
+      SET status = 'Scheduled'
+      WHERE mailing_id = %1
+      AND is_test = 0
+      AND start_date IS NULL
+      AND status = 'Paused'
+    ";
+    CRM_Core_DAO::executeQuery($sql, array(1 => array($mailingID, 'Integer')));
+
+    $sql = "
+      UPDATE civicrm_mailing_job
+      SET status = 'Running'
+      WHERE mailing_id = %1
+      AND is_test = 0
+      AND start_date IS NOT NULL
+      AND status = 'Paused'
+    ";
+    CRM_Core_DAO::executeQuery($sql, array(1 => array($mailingID, 'Integer')));
+  }
+
   /**
    * Return a translated status enum string.
    *
diff --git a/civicrm/CRM/Mailing/Event/BAO/Confirm.php b/civicrm/CRM/Mailing/Event/BAO/Confirm.php
index f01e188d8a..1d3c2d4d18 100644
--- a/civicrm/CRM/Mailing/Event/BAO/Confirm.php
+++ b/civicrm/CRM/Mailing/Event/BAO/Confirm.php
@@ -118,8 +118,6 @@ class CRM_Mailing_Event_BAO_Confirm extends CRM_Mailing_Event_DAO_Confirm {
 
     $component->find(TRUE);
 
-    $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
-
     $html = $component->body_html;
 
     if ($component->body_text) {
@@ -143,11 +141,11 @@ class CRM_Mailing_Event_BAO_Confirm extends CRM_Mailing_Event_DAO_Confirm {
     $mailParams = array(
       'groupName' => 'Mailing Event ' . $component->component_type,
       'subject' => $component->subject,
-      'from' => "\"$domainEmailName\" <do-not-reply@$emailDomain>",
+      'from' => "\"$domainEmailName\" <" . CRM_Core_BAO_Domain::getNoReplyEmailAddress() . '>',
       'toEmail' => $email,
       'toName' => $display_name,
-      'replyTo' => "do-not-reply@$emailDomain",
-      'returnPath' => "do-not-reply@$emailDomain",
+      'replyTo' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
+      'returnPath' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
       'html' => $html,
       'text' => $text,
     );
diff --git a/civicrm/CRM/Mailing/Event/BAO/Reply.php b/civicrm/CRM/Mailing/Event/BAO/Reply.php
index 00d8cd3eac..679822ffe0 100644
--- a/civicrm/CRM/Mailing/Event/BAO/Reply.php
+++ b/civicrm/CRM/Mailing/Event/BAO/Reply.php
@@ -173,8 +173,6 @@ class CRM_Mailing_Event_BAO_Reply extends CRM_Mailing_Event_DAO_Reply {
       }
     }
     else {
-      $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
-
       if (empty($eq->display_name)) {
         $from = $eq->email;
       }
@@ -189,7 +187,7 @@ class CRM_Mailing_Event_BAO_Reply extends CRM_Mailing_Event_DAO_Reply {
         'To' => $mailing->replyto_email,
         'From' => $from,
         'Reply-To' => empty($replyto) ? $eq->email : $replyto,
-        'Return-Path' => "do-not-reply@{$emailDomain}",
+        'Return-Path' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
         // CRM-17754 Include re-sent headers to indicate that we have forwarded on the email
         'Resent-From' => $domainValues['values'][0]['from_email'],
         'Resent-Date' => date('r'),
@@ -253,14 +251,12 @@ class CRM_Mailing_Event_BAO_Reply extends CRM_Mailing_Event_DAO_Reply {
     $domain = CRM_Core_BAO_Domain::getDomain();
     list($domainEmailName, $_) = CRM_Core_BAO_Domain::getNameAndEmail();
 
-    $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
-
     $headers = array(
       'Subject' => $component->subject,
       'To' => $to,
-      'From' => "\"$domainEmailName\" <do-not-reply@$emailDomain>",
-      'Reply-To' => "do-not-reply@$emailDomain",
-      'Return-Path' => "do-not-reply@$emailDomain",
+      'From' => "\"$domainEmailName\" <" . CRM_Core_BAO_Domain::getNoReplyEmailAddress() . '>',
+      'Reply-To' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
+      'Return-Path' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
     );
 
     // TODO: do we need reply tokens?
diff --git a/civicrm/CRM/Mailing/Event/BAO/Resubscribe.php b/civicrm/CRM/Mailing/Event/BAO/Resubscribe.php
index 483f35641d..360bd36bfe 100644
--- a/civicrm/CRM/Mailing/Event/BAO/Resubscribe.php
+++ b/civicrm/CRM/Mailing/Event/BAO/Resubscribe.php
@@ -267,14 +267,12 @@ class CRM_Mailing_Event_BAO_Resubscribe {
       $message->setTxtBody($text);
     }
 
-    $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
-
     $headers = array(
       'Subject' => $component->subject,
-      'From' => "\"$domainEmailName\" <do-not-reply@$emailDomain>",
+      'From' => "\"$domainEmailName\" <" . CRM_Core_BAO_Domain::getNoReplyEmailAddress() . '>',
       'To' => $eq->email,
-      'Reply-To' => "do-not-reply@$emailDomain",
-      'Return-Path' => "do-not-reply@$emailDomain",
+      'Reply-To' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
+      'Return-Path' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
     );
     CRM_Mailing_BAO_Mailing::addMessageIdHeader($headers, 'e', $job, $queue_id, $eq->hash);
     $b = CRM_Utils_Mail::setMimeParams($message);
diff --git a/civicrm/CRM/Mailing/Event/BAO/Subscribe.php b/civicrm/CRM/Mailing/Event/BAO/Subscribe.php
index d3257557b7..d82776413b 100644
--- a/civicrm/CRM/Mailing/Event/BAO/Subscribe.php
+++ b/civicrm/CRM/Mailing/Event/BAO/Subscribe.php
@@ -227,7 +227,7 @@ SELECT     civicrm_email.id as email_id
       'From' => "\"{$domainEmailName}\" <{$domainEmailAddress}>",
       'To' => $email,
       'Reply-To' => $confirm,
-      'Return-Path' => "do-not-reply@$emailDomain",
+      'Return-Path' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
     );
 
     $url = CRM_Utils_System::url('civicrm/mailing/confirm',
diff --git a/civicrm/CRM/Mailing/Event/BAO/Unsubscribe.php b/civicrm/CRM/Mailing/Event/BAO/Unsubscribe.php
index 76f3fef7fb..aa6950cb8c 100644
--- a/civicrm/CRM/Mailing/Event/BAO/Unsubscribe.php
+++ b/civicrm/CRM/Mailing/Event/BAO/Unsubscribe.php
@@ -418,10 +418,10 @@ WHERE  email = %2
 
     $headers = array(
       'Subject' => $component->subject,
-      'From' => "\"$domainEmailName\" <do-not-reply@$emailDomain>",
+      'From' => "\"$domainEmailName\" <" . CRM_Core_BAO_Domain::getNoReplyEmailAddress() . '>',
       'To' => $eq->email,
-      'Reply-To' => "do-not-reply@$emailDomain",
-      'Return-Path' => "do-not-reply@$emailDomain",
+      'Reply-To' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
+      'Return-Path' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
     );
     CRM_Mailing_BAO_Mailing::addMessageIdHeader($headers, 'u', $job, $queue_id, $eq->hash);
 
diff --git a/civicrm/CRM/Mailing/Form/Browse.php b/civicrm/CRM/Mailing/Form/Browse.php
index c5db18c6ae..dad45758b2 100644
--- a/civicrm/CRM/Mailing/Form/Browse.php
+++ b/civicrm/CRM/Mailing/Form/Browse.php
@@ -82,6 +82,7 @@ class CRM_Mailing_Form_Browse extends CRM_Core_Form {
     }
     elseif ($this->_action & CRM_Core_Action::DISABLE) {
       CRM_Mailing_BAO_MailingJob::cancel($this->_mailingId);
+      CRM_Core_Session::setStatus(ts('The mailing has been canceled.'), ts('Canceled'), 'success');
     }
     elseif ($this->_action & CRM_Core_Action::RENEW) {
       //set is_archived to 1
diff --git a/civicrm/CRM/Mailing/Form/Search.php b/civicrm/CRM/Mailing/Form/Search.php
index ce95910100..f64d36dd90 100644
--- a/civicrm/CRM/Mailing/Form/Search.php
+++ b/civicrm/CRM/Mailing/Form/Search.php
@@ -92,7 +92,7 @@ class CRM_Mailing_Form_Search extends CRM_Core_Form {
       $defaults['status_unscheduled'] = 1;
     }
     if ($parent->get('scheduled')) {
-      $statusVals = array('Scheduled', 'Complete', 'Running', 'Canceled');
+      $statusVals = array_keys(CRM_Core_SelectValues::getMailingJobStatus());
       $defaults['is_archived'] = 0;
     }
     if ($parent->get('archived')) {
diff --git a/civicrm/CRM/Mailing/Form/Task.php b/civicrm/CRM/Mailing/Form/Task.php
index 68ff661c56..dc9290be3f 100644
--- a/civicrm/CRM/Mailing/Form/Task.php
+++ b/civicrm/CRM/Mailing/Form/Task.php
@@ -32,30 +32,10 @@
  */
 
 /**
- * This class generates form components for relationship
+ * Class for mailing form task actions.
+ * FIXME: This needs refactoring to properly inherit from CRM_Core_Form_Task and share more functions.
  */
-class CRM_Mailing_Form_Task extends CRM_Core_Form {
-
-  /**
-   * The task being performed.
-   *
-   * @var int
-   */
-  protected $_task;
-
-  /**
-   * The additional clause that we restrict the search with.
-   *
-   * @var string
-   */
-  protected $_componentClause = NULL;
-
-  /**
-   * The array that holds all the component ids.
-   *
-   * @var array
-   */
-  protected $_componentIds;
+class CRM_Mailing_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * Build all the data structures needed to build the form.
@@ -66,9 +46,8 @@ class CRM_Mailing_Form_Task extends CRM_Core_Form {
 
   /**
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $values = $form->controller->exportValues($form->get('searchFormName'));
 
     $form->_task = CRM_Utils_Array::value('task', $values);
diff --git a/civicrm/CRM/Mailing/Page/Browse.php b/civicrm/CRM/Mailing/Page/Browse.php
index d5e2ac1a51..bb801f45fe 100644
--- a/civicrm/CRM/Mailing/Page/Browse.php
+++ b/civicrm/CRM/Mailing/Page/Browse.php
@@ -181,6 +181,7 @@ class CRM_Mailing_Page_Browse extends CRM_Core_Page {
     if ($this->_action & CRM_Core_Action::DISABLE) {
       if (CRM_Utils_Request::retrieve('confirmed', 'Boolean', $this)) {
         CRM_Mailing_BAO_MailingJob::cancel($this->_mailingId);
+        CRM_Core_Session::setStatus(ts('The mailing has been canceled.'), ts('Canceled'), 'success');
         CRM_Utils_System::redirect($context);
       }
       else {
@@ -192,6 +193,22 @@ class CRM_Mailing_Page_Browse extends CRM_Core_Page {
         $controller->run();
       }
     }
+    elseif ($this->_action & CRM_Core_Action::CLOSE) {
+      if (!CRM_Core_Permission::checkActionPermission('CiviMail', CRM_Core_Action::CLOSE)) {
+        CRM_Core_Error::fatal(ts('You do not have permission to access this page.'));
+      }
+      CRM_Mailing_BAO_MailingJob::pause($this->_mailingId);
+      CRM_Core_Session::setStatus(ts('The mailing has been paused. Active message deliveries may continue for a few minutes, but CiviMail will not begin delivery of any more batches.'), ts('Paused'), 'success');
+      CRM_Utils_System::redirect($context);
+    }
+    elseif ($this->_action & CRM_Core_Action::REOPEN) {
+      if (!CRM_Core_Permission::checkActionPermission('CiviMail', CRM_Core_Action::CLOSE)) {
+        CRM_Core_Error::fatal(ts('You do not have permission to access this page.'));
+      }
+      CRM_Mailing_BAO_MailingJob::resume($this->_mailingId);
+      CRM_Core_Session::setStatus(ts('The mailing has been resumed.'), ts('Resumed'), 'success');
+      CRM_Utils_System::redirect($context);
+    }
     elseif ($this->_action & CRM_Core_Action::DELETE) {
       if (CRM_Utils_Request::retrieve('confirmed', 'Boolean', $this)) {
 
diff --git a/civicrm/CRM/Mailing/Selector/Browse.php b/civicrm/CRM/Mailing/Selector/Browse.php
index 4668cf2938..8af4a7f00c 100644
--- a/civicrm/CRM/Mailing/Selector/Browse.php
+++ b/civicrm/CRM/Mailing/Selector/Browse.php
@@ -294,6 +294,18 @@ LEFT JOIN  civicrm_contact scheduledContact ON ( $mailing.scheduled_id = schedul
           'extra' => 'onclick="if (confirm(\'' . $archiveExtra . '\')) this.href+=\'&amp;confirmed=1\'; else return false;"',
           'title' => ts('Archive Mailing'),
         ),
+        CRM_Core_Action::REOPEN => array(
+          'name' => ts('Resume'),
+          'url' => 'civicrm/mailing/browse',
+          'qs' => 'action=reopen&mid=%%mid%%&reset=1',
+          'title' => ts('Resume mailing'),
+        ),
+        CRM_Core_Action::CLOSE => array(
+          'name' => ts('Pause'),
+          'url' => 'civicrm/mailing/browse',
+          'qs' => 'action=close&mid=%%mid%%&reset=1',
+          'title' => ts('Pause mailing'),
+        ),
       );
     }
 
@@ -389,6 +401,12 @@ LEFT JOIN  civicrm_contact scheduledContact ON ( $mailing.scheduled_id = schedul
           ) {
 
             $actionMask |= CRM_Core_Action::DISABLE;
+            if ($row['status'] == "Paused") {
+              $actionMask |= CRM_Core_Action::REOPEN;
+            }
+            else {
+              $actionMask |= CRM_Core_Action::CLOSE;
+            }
           }
           if ($row['status'] == 'Scheduled' &&
             empty($row['approval_status_id'])
@@ -550,7 +568,7 @@ LEFT JOIN  civicrm_contact scheduledContact ON ( $mailing.scheduled_id = schedul
     if (!$isFormSubmitted && $this->_parent->get('scheduled')) {
       // mimic default behavior for scheduled screen
       $isArchived = 0;
-      $mailingStatus = array('Scheduled' => 1, 'Complete' => 1, 'Running' => 1, 'Canceled' => 1);
+      $mailingStatus = array('Scheduled' => 1, 'Complete' => 1, 'Running' => 1, 'Paused' => 1, 'Canceled' => 1);
     }
     if (!$isFormSubmitted && $this->_parent->get('archived')) {
       // mimic default behavior for archived screen
diff --git a/civicrm/CRM/Member/BAO/Membership.php b/civicrm/CRM/Member/BAO/Membership.php
index ce6df5a605..1826901429 100644
--- a/civicrm/CRM/Member/BAO/Membership.php
+++ b/civicrm/CRM/Member/BAO/Membership.php
@@ -134,7 +134,7 @@ class CRM_Member_BAO_Membership extends CRM_Member_DAO_Membership {
       $membershipLog['modified_id'] = $membership->contact_id;
     }
 
-    CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray);
+    CRM_Member_BAO_MembershipLog::add($membershipLog);
 
     // reset the group contact cache since smart groups might be affected due to this
     CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush();
@@ -1233,7 +1233,7 @@ AND civicrm_membership.is_test = %2";
         )
       );
 
-      CRM_Member_BAO_MembershipLog::add($logParams, CRM_Core_DAO::$_nullArray);
+      CRM_Member_BAO_MembershipLog::add($logParams);
     }
   }
 
diff --git a/civicrm/CRM/Member/BAO/MembershipType.php b/civicrm/CRM/Member/BAO/MembershipType.php
index 66c9cd2a8e..796e047a07 100644
--- a/civicrm/CRM/Member/BAO/MembershipType.php
+++ b/civicrm/CRM/Member/BAO/MembershipType.php
@@ -807,41 +807,39 @@ class CRM_Member_BAO_MembershipType extends CRM_Member_DAO_MembershipType {
    * @param array $params
    */
   public static function updateAllPriceFieldValue($membershipTypeId, $params) {
-    $updateFields = array();
-    if (!empty($params['minimum_fee'])) {
-      $amount = $params['minimum_fee'];
-    }
-    else {
-      $amount = 0;
-    }
-
-    $updateValues = array(
-      2 => array('financial_type_id', 'financial_type_id', 'Integer'),
-      3 => array('label', 'name', 'String'),
-      4 => array('amount', 'minimum_fee', 'Float'),
-      5 => array('description', 'description', 'String'),
+    $defaults = array();
+    $fieldsToUpdate = array(
+      'financial_type_id' => 'financial_type_id',
+      'name' => 'label',
+      'minimum_fee' => 'amount',
+      'description' => 'description',
+      'visibility' => 'visibility_id',
     );
-
-    $queryParams = array(1 => array($membershipTypeId, 'Integer'));
-    foreach ($updateValues as $key => $value) {
-      if (array_key_exists($value[1], $params)) {
-        $updateFields[] = "cpfv." . $value[0] . " = %$key";
-        if ($value[1] == 'minimum_fee') {
-          $fieldValue = $amount;
-        }
-        else {
-          $fieldValue = $params[$value[1]];
+    $priceFieldValueBAO = new CRM_Price_BAO_PriceFieldValue();
+    $priceFieldValueBAO->membership_type_id = $membershipTypeId;
+    $priceFieldValueBAO->find();
+    while ($priceFieldValueBAO->fetch()) {
+      $updateParams = array(
+        'id' => $priceFieldValueBAO->id,
+        'price_field_id' => $priceFieldValueBAO->price_field_id,
+      );
+      //Get priceset details.
+      $fieldParams = array('fid' => $priceFieldValueBAO->price_field_id);
+      $setID = CRM_Price_BAO_PriceSet::getSetId($fieldParams);
+      $setParams = array('id' => $setID);
+      $setValues = CRM_Price_BAO_PriceSet::retrieve($setParams, $defaults);
+      if (!empty($setValues->is_quick_config) && $setValues->name != 'default_membership_type_amount') {
+        foreach ($fieldsToUpdate as $key => $value) {
+          if ($value == 'visibility_id' && !empty($params['visibility'])) {
+            $updateParams['visibility_id'] = CRM_Price_BAO_PriceField::getVisibilityOptionID(strtolower($params['visibility']));
+          }
+          else {
+            $updateParams[$value] = CRM_Utils_Array::value($key, $params);
+          }
         }
-        $queryParams[$key] = array($fieldValue, $value[2]);
+        CRM_Price_BAO_PriceFieldValue::add($updateParams);
       }
     }
-
-    $query = "UPDATE `civicrm_price_field_value` cpfv
-INNER JOIN civicrm_price_field cpf on cpf.id = cpfv.price_field_id
-INNER JOIN civicrm_price_set cps on cps.id = cpf.price_set_id
-SET " . implode(' , ', $updateFields) . " WHERE cpfv.membership_type_id = %1
-AND cps.is_quick_config = 1 AND cps.name != 'default_membership_type_amount'";
-    CRM_Core_DAO::executeQuery($query, $queryParams);
   }
 
 }
diff --git a/civicrm/CRM/Member/Form/Task.php b/civicrm/CRM/Member/Form/Task.php
index 009b67f201..e8c6e9bfe1 100644
--- a/civicrm/CRM/Member/Form/Task.php
+++ b/civicrm/CRM/Member/Form/Task.php
@@ -34,38 +34,10 @@
  */
 
 /**
- * Class for civimember task actions
- *
+ * Class for member form task actions.
+ * FIXME: This needs refactoring to properly inherit from CRM_Core_Form_Task and share more functions.
  */
-class CRM_Member_Form_Task extends CRM_Core_Form {
-
-  /**
-   * The task being performed.
-   *
-   * @var int
-   */
-  protected $_task;
-
-  /**
-   * The additional clause that we restrict the search with.
-   *
-   * @var string
-   */
-  protected $_componentClause = NULL;
-
-  /**
-   * The array that holds all the component ids.
-   *
-   * @var array
-   */
-  protected $_componentIds;
-
-  /**
-   * The array that holds all the contact ids.
-   *
-   * @var array
-   */
-  public $_contactIds;
+class CRM_Member_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * The array that holds all the member ids.
@@ -87,9 +59,8 @@ class CRM_Member_Form_Task extends CRM_Core_Form {
 
   /**
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_memberIds = array();
 
     $values = $form->controller->exportValues($form->get('searchFormName'));
diff --git a/civicrm/CRM/Member/Page/RecurringContributions.php b/civicrm/CRM/Member/Page/RecurringContributions.php
index 67ef43d1d2..67b2b93938 100644
--- a/civicrm/CRM/Member/Page/RecurringContributions.php
+++ b/civicrm/CRM/Member/Page/RecurringContributions.php
@@ -112,14 +112,18 @@ class CRM_Member_Page_RecurringContributions extends CRM_Core_Page {
    */
   private function setActionsForRecurringContribution($recurID, &$recurringContribution) {
     $action = array_sum(array_keys($this->recurLinks($recurID)));
+
     // no action allowed if it's not active
     $recurringContribution['is_active'] = ($recurringContribution['contribution_status_id'] != 3);
+
     if ($recurringContribution['is_active']) {
       $details = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($recurringContribution['id'], 'recur');
       $hideUpdate = $details->membership_id & $details->auto_renew;
-      if ($hideUpdate || empty($details->processor_id)) {
+
+      if ($hideUpdate) {
         $action -= CRM_Core_Action::UPDATE;
       }
+
       $recurringContribution['action'] = CRM_Core_Action::formLink(
         $this->recurLinks($recurID),
         $action,
diff --git a/civicrm/CRM/Member/Task.php b/civicrm/CRM/Member/Task.php
index a8843f6e6c..572dbae011 100644
--- a/civicrm/CRM/Member/Task.php
+++ b/civicrm/CRM/Member/Task.php
@@ -102,6 +102,16 @@ class CRM_Member_Task extends CRM_Core_Task {
           'class' => 'CRM_Member_Form_Task_PDFLetter',
           'result' => FALSE,
         ),
+        self::SAVE_SEARCH => array(
+          'title' => ts('Group - create smart group'),
+          'class' => 'CRM_Contact_Form_Task_SaveSearch',
+          'result' => TRUE,
+        ),
+        self::SAVE_SEARCH_UPDATE => array(
+          'title' => ts('Group - update smart group'),
+          'class' => 'CRM_Contact_Form_Task_SaveSearch_Update',
+          'result' => TRUE,
+        ),
       );
 
       //CRM-4418, check for delete
diff --git a/civicrm/CRM/Pledge/Form/Task.php b/civicrm/CRM/Pledge/Form/Task.php
index 092fece3a3..4a8b855614 100644
--- a/civicrm/CRM/Pledge/Form/Task.php
+++ b/civicrm/CRM/Pledge/Form/Task.php
@@ -32,30 +32,10 @@
  */
 
 /**
- * This class generates task actions for CiviEvent.
+ * Class for pledge form task actions.
+ * FIXME: This needs refactoring to properly inherit from CRM_Core_Form_Task and share more functions.
  */
-class CRM_Pledge_Form_Task extends CRM_Core_Form {
-
-  /**
-   * The task being performed.
-   *
-   * @var int
-   */
-  protected $_task;
-
-  /**
-   * The additional clause that we restrict the search with.
-   *
-   * @var string
-   */
-  protected $_componentClause = NULL;
-
-  /**
-   * The array that holds all the component ids.
-   *
-   * @var array
-   */
-  protected $_componentIds;
+class CRM_Pledge_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * The array that holds all the pledge ids.
@@ -75,9 +55,8 @@ class CRM_Pledge_Form_Task extends CRM_Core_Form {
    * Common pre-processing.
    *
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_pledgeIds = array();
 
     $values = $form->controller->exportValues('Search');
diff --git a/civicrm/CRM/Profile/Form/Edit.php b/civicrm/CRM/Profile/Form/Edit.php
index 601283c1e6..ada0354e0a 100644
--- a/civicrm/CRM/Profile/Form/Edit.php
+++ b/civicrm/CRM/Profile/Form/Edit.php
@@ -222,6 +222,7 @@ SELECT module,is_reserved
 
     $cancelButtonValue = !empty($this->_ufGroup['cancel_button_text']) ? $this->_ufGroup['cancel_button_text'] : ts('Cancel');
     $this->assign('cancelButtonText', $cancelButtonValue);
+    $this->assign('includeCancelButton', CRM_Utils_Array::value('add_cancel_button', $this->_ufGroup));
 
     if (($this->_multiRecord & CRM_Core_Action::DELETE) && $this->_recordExists) {
       $this->_deleteButtonName = $this->getButtonName('upload', 'delete');
diff --git a/civicrm/CRM/Report/BAO/ReportInstance.php b/civicrm/CRM/Report/BAO/ReportInstance.php
index 0b254ee73c..a7dc5733cb 100644
--- a/civicrm/CRM/Report/BAO/ReportInstance.php
+++ b/civicrm/CRM/Report/BAO/ReportInstance.php
@@ -364,24 +364,35 @@ class CRM_Report_BAO_ReportInstance extends CRM_Report_DAO_ReportInstance {
    *  - general script-add.
    */
   public static function getActionMetadata() {
-    $actions = array(
-      'report_instance.save' => array('title' => ts('Save')),
-      'report_instance.copy' => array(
+    $actions = array();
+    if (CRM_Core_Permission::check('save Report Criteria')) {
+      $actions['report_instance.save'] = array('title' => ts('Save'));
+      $actions['report_instance.copy'] = array(
         'title' => ts('Save a Copy'),
         'data' => array(
           'is_confirm' => TRUE,
           'confirm_title' => ts('Save a copy...'),
           'confirm_refresh_fields' => json_encode(array(
-            'title' => array('selector' => '.crm-report-instanceForm-form-block-title', 'prepend' => ts('(Copy) ')),
-            'description' => array('selector' => '.crm-report-instanceForm-form-block-description', 'prepend' => ''),
-            'parent_id' => array('selector' => '.crm-report-instanceForm-form-block-parent_id', 'prepend' => ''),
+            'title' => array(
+              'selector' => '.crm-report-instanceForm-form-block-title',
+              'prepend' => ts('(Copy) '),
+            ),
+            'description' => array(
+              'selector' => '.crm-report-instanceForm-form-block-description',
+              'prepend' => '',
+            ),
+            'parent_id' => array(
+              'selector' => '.crm-report-instanceForm-form-block-parent_id',
+              'prepend' => '',
+            ),
           )),
         ),
-      ),
-      'report_instance.print' => array('title' => ts('Print Report')),
-      'report_instance.pdf' => array('title' => ts('Print to PDF')),
-      'report_instance.csv' => array('title' => ts('Export as CSV')),
-    );
+      );
+    }
+    $actions['report_instance.print'] = array('title' => ts('Print Report'));
+    $actions['report_instance.pdf'] = array('title' => ts('Print to PDF'));
+    $actions['report_instance.csv'] = array('title' => ts('Export as CSV'));
+
     if (CRM_Core_Permission::check('administer Reports')) {
       $actions['report_instance.delete'] = array(
         'title' => ts('Delete report'),
diff --git a/civicrm/CRM/Report/Form.php b/civicrm/CRM/Report/Form.php
index 97f2e78d0f..3d1ebc3456 100644
--- a/civicrm/CRM/Report/Form.php
+++ b/civicrm/CRM/Report/Form.php
@@ -488,6 +488,14 @@ class CRM_Report_Form extends CRM_Core_Form {
 
   protected $sqlArray;
 
+  /**
+   * Tables created for the report that need removal afterwards.
+   *
+   * ['civicrm_temp_report_x' => ['temporary' => TRUE, 'name' => 'civicrm_temp_report_x']
+   * @var array
+   */
+  protected $temporaryTables = [];
+
   /**
    * Can this report use the sql mode ONLY_FULL_GROUP_BY.
    * @var bool
@@ -1114,6 +1122,15 @@ class CRM_Report_Form extends CRM_Core_Form {
     return $this->_defaults;
   }
 
+  /**
+   * Remove any temporary tables.
+   */
+  public function cleanUpTemporaryTables() {
+    foreach ($this->temporaryTables as $temporaryTable) {
+      CRM_Core_DAO::executeQuery('DROP ' . ($temporaryTable['temporary'] ? 'TEMPORARY' : '') . ' TABLE IF EXISTS ' . $temporaryTable['name']);
+    }
+  }
+
   /**
    * Add columns to report.
    */
@@ -3672,7 +3689,7 @@ WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
         WHERE smartgroup_contact.group_id IN ({$smartGroups}) ";
     }
 
-    $this->groupTempTable = 'civicrm_report_temp_group_' . date('Ymd_') . uniqid();
+    $this->groupTempTable = CRM_Utils_SQL_TempTable::build()->setCategory('rptgrp')->setId(date('Ymd_') . uniqid())->getName();
     $this->executeReportQuery("
       CREATE TEMPORARY TABLE $this->groupTempTable $this->_databaseAttributes
       $query
@@ -5357,6 +5374,22 @@ LEFT JOIN civicrm_contact {$field['alias']} ON {$field['alias']}.id = {$this->_a
         'title' => $options['prefix_label'] . ts('Nick Name'),
         'is_fields' => TRUE,
       ),
+      $options['prefix'] . 'prefix_id' => array(
+        'name' => 'prefix_id',
+        'title' => $options['prefix_label'] . ts('Prefix'),
+        'options' => CRM_Contact_BAO_Contact::buildOptions('prefix_id'),
+        'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+        'is_fields' => TRUE,
+        'is_filters' => TRUE,
+      ),
+      $options['prefix'] . 'suffix_id' => array(
+        'name' => 'suffix_id',
+        'title' => $options['prefix_label'] . ts('Suffix'),
+        'options' => CRM_Contact_BAO_Contact::buildOptions('suffix_id'),
+        'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+        'is_fields' => TRUE,
+        'is_filters' => TRUE,
+      ),
       $options['prefix'] . 'gender_id' => array(
         'name' => 'gender_id',
         'title' => $options['prefix_label'] . ts('Gender'),
diff --git a/civicrm/CRM/Report/Form/Contribute/Bookkeeping.php b/civicrm/CRM/Report/Form/Contribute/Bookkeeping.php
index 088e2d730e..8e4c25d1e3 100644
--- a/civicrm/CRM/Report/Form/Contribute/Bookkeeping.php
+++ b/civicrm/CRM/Report/Form/Contribute/Bookkeeping.php
@@ -301,6 +301,10 @@ class CRM_Report_Form_Contribute_Bookkeeping extends CRM_Report_Form {
             'title' => ts('Contribution Status'),
             'default' => TRUE,
           ),
+          'contribution_source' => array(
+            'title' => ts('Source'),
+            'name' => 'source',
+          ),
           'id' => array(
             'title' => ts('Contribution ID'),
             'default' => TRUE,
@@ -314,6 +318,11 @@ class CRM_Report_Form_Contribute_Bookkeeping extends CRM_Report_Form {
             'type' => CRM_Utils_Type::T_INT,
           ),
           'receive_date' => array('operatorType' => CRM_Report_Form::OP_DATE),
+          'contribution_source' => array(
+            'title' => ts('Source'),
+            'name' => 'source',
+            'type' => CRM_Utils_Type::T_STRING,
+          ),
           'contribution_status_id' => array(
             'title' => ts('Contribution Status'),
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
diff --git a/civicrm/CRM/Report/Form/Contribute/Detail.php b/civicrm/CRM/Report/Form/Contribute/Detail.php
index 7910fa1453..3619a47409 100644
--- a/civicrm/CRM/Report/Form/Contribute/Detail.php
+++ b/civicrm/CRM/Report/Form/Contribute/Detail.php
@@ -46,6 +46,32 @@ class CRM_Report_Form_Contribute_Detail extends CRM_Report_Form {
 
   protected $groupConcatTested = TRUE;
 
+  protected $isTempTableBuilt = FALSE;
+
+  /**
+   * Query mode.
+   *
+   * This can be 'Main' or 'SoftCredit' to denote which query we are building.
+   *
+   * @var string
+   */
+  protected $queryMode = 'Main';
+
+  /**
+   * Is this report being run on contributions as the base entity.
+   *
+   * The report structure is generally designed around a base entity but
+   * depending on input it can be run in a sort of hybrid way that causes a lot
+   * of complexity.
+   *
+   * If it is in isContributionsOnlyMode we can simplify.
+   *
+   * (arguably there should be 2 separate report templates, not one doing double duty.)
+   *
+   * @var bool
+   */
+  protected $isContributionBaseMode = FALSE;
+
   /**
    * This report has been optimised for group filtering.
    *
@@ -465,34 +491,59 @@ GROUP BY {$this->_aliases['civicrm_contribution']}.currency";
   }
 
   /**
-   * This function appears to have been overrriden for the purposes of facilitating soft credits in the report.
+   * Build the report query.
    *
-   * 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.
+   * @param bool $applyLimit
    *
-   * 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.
+   * @return string
    */
-  public function postProcess() {
-    // @todo in order to make this report testable we need to remove this function override in favour of the
-    // functions called by the reportTemplate.getrows api - this requires a bit of tidy up!
-    // get the acl clauses built before we assemble the query
-    $this->buildACLClause($this->_aliases['civicrm_contact']);
+  public function buildQuery($applyLimit = TRUE) {
+    if ($this->isTempTableBuilt) {
+      return "SELECT * FROM civireport_contribution_detail_temp3 $this->_orderBy";
+    }
+    return parent::buildQuery($applyLimit);
+  }
 
-    $this->beginPostProcess();
+  /**
+   * Shared function for preliminary processing.
+   *
+   * This is called by the api / unit tests and the form layer and is
+   * the right place to do 'initial analysis of input'.
+   */
+  public function beginPostProcessCommon() {
+    // 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') {
+      $this->isContributionBaseMode = TRUE;
+    }
+    if ($this->isContributionBaseMode &&
+      (!empty($this->_params['fields']['soft_credit_type_id'])
+      || !empty($this->_params['soft_credit_type_id_value']))
+    ) {
+      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();
+        CRM_Core_Session::setStatus(ts('Is it not possible to filter on soft contribution type when not including soft credits.'));
+      }
+    }
     // 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->temporaryTables['civireport_contribution_detail_temp1'] = ['name' => 'civireport_contribution_detail_temp1', 'temporary' => TRUE];
+    $this->executeReportQuery($tempQuery);
     $this->setPager();
 
     // 2. customize main contribution query for soft credit, and build temp table 2 with soft credit contributions only
+    $this->queryMode = 'SoftCredit';
+    // Rebuild select with no groupby. Do not let column headers change.
+    $headers = $this->_columnHeaders;
+    $this->select();
+    $this->_columnHeaders = $headers;
     $this->softCreditFrom();
     // also include custom group from if included
     // since this might be included in select
@@ -504,11 +555,13 @@ GROUP BY {$this->_aliases['civicrm_contribution']}.currency";
     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}";
+    // we inner join with temp1 to restrict soft contributions to those in temp1 table.
+    // no group by here as we want to display as many soft credit rows as actually exist.
+    $sql = "{$select} {$this->_from} {$this->_where}";
     $tempQuery = "CREATE TEMPORARY TABLE civireport_contribution_detail_temp2 {$this->_databaseAttributes} AS {$sql}";
-    $this->addToDeveloperTab($tempQuery);
-    CRM_Core_DAO::executeQuery($tempQuery);
+    $this->executeReportQuery($tempQuery);
+    $this->temporaryTables['civireport_contribution_detail_temp2'] = ['name' => 'civireport_contribution_detail_temp2', 'temporary' => TRUE];
+
     if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
       'soft_credits_only'
     ) {
@@ -527,66 +580,41 @@ GROUP BY {$this->_aliases['civicrm_contribution']}.currency";
     $this->customDataFrom();
 
     // 3. Decide where to populate temp3 table from
-    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
-      'contributions_only'
+    if ($this->isContributionBaseMode
     ) {
-      $tempQuery = "(SELECT * FROM civireport_contribution_detail_temp1)";
+      $this->executeReportQuery(
+        "CREATE TEMPORARY TABLE civireport_contribution_detail_temp3 {$this->_databaseAttributes} AS (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)";
+      $this->executeReportQuery(
+        "CREATE TEMPORARY TABLE civireport_contribution_detail_temp3 {$this->_databaseAttributes} AS (SELECT * FROM civireport_contribution_detail_temp2)"
+      );
     }
     else {
-      $tempQuery = "
+      $this->executeReportQuery("CREATE TEMPORARY TABLE civireport_contribution_detail_temp3 {$this->_databaseAttributes}
 (SELECT * FROM civireport_contribution_detail_temp1)
 UNION ALL
-(SELECT * FROM civireport_contribution_detail_temp2)";
+(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);
+    $this->temporaryTables['civireport_contribution_detail_temp3'] = ['name' => 'civireport_contribution_detail_temp3', 'temporary' => TRUE];
+    $this->isTempTableBuilt = TRUE;
   }
 
   /**
-   * Shared function for preliminary processing.
+   * Store group bys into array - so we can check elsewhere what is grouped.
    *
-   * This is called by the api / unit tests and the form layer and is
-   * the right place to do 'initial analysis of input'.
+   * If we are generating a table of soft credits we do not want to be using
+   * group by.
    */
-  public function beginPostProcessCommon() {
-    // 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;
+  protected function storeGroupByArray() {
+    if ($this->queryMode === 'SoftCredit') {
+      $this->_groupByArray = [];
     }
-
-    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
-      'contributions_only' &&
-      (!empty($this->_params['fields']['soft_credit_type_id'])
-      || !empty($this->_params['soft_credit_type_id_value']))
-    ) {
-      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();
-        CRM_Core_Session::setStatus(ts('Is it not possible to filter on soft contribution type when not including soft credits.'));
-      }
+    else {
+      parent::storeGroupByArray();
     }
   }
 
@@ -600,7 +628,6 @@ UNION ALL
    *   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();
@@ -717,7 +744,7 @@ UNION ALL
         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
+SELECT civicrm_contact_id, civicrm_contact_sort_name, civicrm_contribution_total_amount, civicrm_contribution_currency
 FROM   civireport_contribution_detail_temp2
 WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribution_id']}";
         $this->addToDeveloperTab($query);
@@ -729,7 +756,7 @@ WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribu
             $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);
+            CRM_Utils_Money::format($dao->civicrm_contribution_total_amount, $dao->civicrm_contribution_currency);
         }
         $rows[$rowNum]['civicrm_contribution_soft_credits'] = $string;
       }
diff --git a/civicrm/CRM/Report/Form/Contribute/History.php b/civicrm/CRM/Report/Form/Contribute/History.php
index 117ece4836..ac23fd4bfd 100644
--- a/civicrm/CRM/Report/Form/Contribute/History.php
+++ b/civicrm/CRM/Report/Form/Contribute/History.php
@@ -867,6 +867,8 @@ class CRM_Report_Form_Contribute_History extends CRM_Report_Form {
         $entryFound = TRUE;
       }
 
+      $entryFound = $this->alterDisplayAddressFields($row, $rows, $rowNum, NULL, NULL) ? TRUE : $entryFound;
+
     }
   }
 
diff --git a/civicrm/CRM/Report/Form/Contribute/Lybunt.php b/civicrm/CRM/Report/Form/Contribute/Lybunt.php
index 0fb7f093a5..e5aeb0fcba 100644
--- a/civicrm/CRM/Report/Form/Contribute/Lybunt.php
+++ b/civicrm/CRM/Report/Form/Contribute/Lybunt.php
@@ -567,7 +567,7 @@ class CRM_Report_Form_Contribute_Lybunt extends CRM_Report_Form {
     // @todo this acl has no test coverage and is very hard to test manually so could be fragile.
     $this->resetFormSqlAndWhereHavingClauses();
 
-    $this->contactTempTable = 'civicrm_report_temp_lybunt_c_' . date('Ymd_') . uniqid();
+    $this->contactTempTable = CRM_Utils_SQL_TempTable::build()->setCategory('rptlybunt')->setId(date('Ymd_') . uniqid())->getName();
     $this->limit();
     $getContacts = "
       CREATE TEMPORARY TABLE $this->contactTempTable {$this->_databaseAttributes}
diff --git a/civicrm/CRM/Report/Info.php b/civicrm/CRM/Report/Info.php
index 83cc3bbbe5..8d8c429a5d 100644
--- a/civicrm/CRM/Report/Info.php
+++ b/civicrm/CRM/Report/Info.php
@@ -87,6 +87,10 @@ class CRM_Report_Info extends CRM_Core_Component_Info {
         ts('access Report Criteria'),
         ts('Change report search criteria'),
       ),
+      'save Report Criteria' => array(
+        ts('save Report Criteria'),
+        ts('Save report search criteria'),
+      ),
       'administer private reports' => array(
         ts('administer private reports'),
         ts('Edit all private reports'),
diff --git a/civicrm/CRM/SMS/Form/Provider.php b/civicrm/CRM/SMS/Form/Provider.php
index c5496c2b57..f6aace8040 100644
--- a/civicrm/CRM/SMS/Form/Provider.php
+++ b/civicrm/CRM/SMS/Form/Provider.php
@@ -159,7 +159,7 @@ class CRM_SMS_Form_Provider extends CRM_Core_Form {
    */
   public function postProcess() {
 
-    CRM_Utils_System::flushCache('CRM_SMS_DAO_Provider');
+    CRM_Utils_System::flushCache();
 
     if ($this->_action & CRM_Core_Action::DELETE) {
       CRM_SMS_BAO_Provider::del($this->_id);
diff --git a/civicrm/CRM/Tag/Form/Tag.php b/civicrm/CRM/Tag/Form/Tag.php
index ed75c7017c..765c71eb77 100644
--- a/civicrm/CRM/Tag/Form/Tag.php
+++ b/civicrm/CRM/Tag/Form/Tag.php
@@ -113,7 +113,7 @@ class CRM_Tag_Form_Tag extends CRM_Core_Form {
    * @return void
    */
   public function postProcess() {
-    CRM_Utils_System::flushCache('CRM_Core_DAO_Tag');
+    CRM_Utils_System::flushCache();
 
     // array contains the posted values
     // exportvalues is not used because its give value 1 of the checkbox which were checked by default,
diff --git a/civicrm/CRM/UF/Form/AdvanceSetting.php b/civicrm/CRM/UF/Form/AdvanceSetting.php
index 617940127f..01db60e8b9 100644
--- a/civicrm/CRM/UF/Form/AdvanceSetting.php
+++ b/civicrm/CRM/UF/Form/AdvanceSetting.php
@@ -50,6 +50,8 @@ class CRM_UF_Form_AdvanceSetting extends CRM_UF_Form_Group {
     $form->addGroup($options, 'is_update_dupe', ts('What to do upon duplicate match'));
     // we do not have any url checks to allow relative urls
     $form->addElement('text', 'post_URL', ts('Redirect URL'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_UFGroup', 'post_URL'));
+
+    $form->add('advcheckbox', 'add_cancel_button', ts('Include Cancel Button?'));
     $form->addElement('text', 'cancel_URL', ts('Cancel Redirect URL'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_UFGroup', 'cancel_URL'));
     $form->addElement('text', 'cancel_button_text', ts('Cancel Button Text'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_UFGroup', 'cancel_button_text'));
     $form->addElement('text', 'submit_button_text', ts('Submit Button Text'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_UFGroup', 'submit_button_text'));
diff --git a/civicrm/CRM/UF/Form/Group.php b/civicrm/CRM/UF/Form/Group.php
index 9adc248503..9fcb834176 100644
--- a/civicrm/CRM/UF/Form/Group.php
+++ b/civicrm/CRM/UF/Form/Group.php
@@ -270,6 +270,7 @@ class CRM_UF_Form_Group extends CRM_Core_Form {
       }
     }
     else {
+      $defaults['add_cancel_button'] = 1;
       $defaults['is_active'] = 1;
       $defaults['is_map'] = 0;
       $defaults['is_update_dupe'] = 0;
diff --git a/civicrm/CRM/Upgrade/Form.php b/civicrm/CRM/Upgrade/Form.php
index b2b09f08f2..7a6ad203c8 100644
--- a/civicrm/CRM/Upgrade/Form.php
+++ b/civicrm/CRM/Upgrade/Form.php
@@ -769,6 +769,7 @@ SET    version = '$version'
     foreach ($revisions as $rev) {
       if (version_compare($currentVer, $rev) < 0) {
         $versionObject = $this->incrementalPhpObject($rev);
+        CRM_Upgrade_Incremental_General::updateMessageTemplate($preUpgradeMessage, $rev);
         if (is_callable(array($versionObject, 'setPreUpgradeMessage'))) {
           $versionObject->setPreUpgradeMessage($preUpgradeMessage, $rev, $currentVer);
         }
diff --git a/civicrm/CRM/Upgrade/Incremental/Base.php b/civicrm/CRM/Upgrade/Incremental/Base.php
index 1cbf1cc3f5..b180904d70 100644
--- a/civicrm/CRM/Upgrade/Incremental/Base.php
+++ b/civicrm/CRM/Upgrade/Incremental/Base.php
@@ -183,6 +183,18 @@ class CRM_Upgrade_Incremental_Base {
     return TRUE;
   }
 
+  /**
+   * Do any relevant message template updates.
+   *
+   * @param CRM_Queue_TaskContext $ctx
+   * @param string $version
+   */
+  public static function updateMessageTemplates($ctx, $version) {
+    $messageTemplateObject = new CRM_Upgrade_Incremental_MessageTemplates($version);
+    $messageTemplateObject->updateTemplates();
+
+  }
+
   /**
    * Drop a column from a table if it exist.
    *
diff --git a/civicrm/CRM/Upgrade/Incremental/General.php b/civicrm/CRM/Upgrade/Incremental/General.php
index 9f0ebe39c9..3575ce91c9 100644
--- a/civicrm/CRM/Upgrade/Incremental/General.php
+++ b/civicrm/CRM/Upgrade/Incremental/General.php
@@ -117,13 +117,37 @@ class CRM_Upgrade_Incremental_General {
     }
   }
 
+  /**
+   * Perform any message template updates. 5.0+.
+   * @param $message
+   * @param $version
+   */
+  public static function updateMessageTemplate(&$message, $version) {
+    if (version_compare($version, 5.0, '<')) {
+      return;
+    }
+    $messageObj = new CRM_Upgrade_Incremental_MessageTemplates($version);
+    $messages = $messageObj->getUpgradeMessages();
+    if (empty($messages)) {
+      return;
+    }
+    $message .= '<br />' . ts("The default copies of the message templates listed below will be updated to handle new features or correct a problem. Your installation has customized versions of these message templates, and you will need to apply the updates manually after running this upgrade. <a href='%1' style='color:white; text-decoration:underline; font-weight:bold;' target='_blank'>Click here</a> for detailed instructions. %2", array(
+        1 => 'http://wiki.civicrm.org/confluence/display/CRMDOC/Message+Templates#MessageTemplates-UpgradesandCustomizedSystemWorkflowTemplates',
+        2 => '<ul><l>' . implode('</li><li>', $messages) . '</li></ul>',
+      ));
+
+    $messageObj->updateTemplates();
+  }
+
   /**
    * @param $message
    * @param $latestVer
    * @param $currentVer
    */
   public static function checkMessageTemplate(&$message, $latestVer, $currentVer) {
-
+    if (version_compare($currentVer, 5.0, '>')) {
+      return;
+    }
     $sql = "SELECT orig.workflow_id as workflow_id,
              orig.msg_title as title
             FROM civicrm_msg_template diverted JOIN civicrm_msg_template orig ON (
diff --git a/civicrm/CRM/Upgrade/Incremental/MessageTemplates.php b/civicrm/CRM/Upgrade/Incremental/MessageTemplates.php
new file mode 100644
index 0000000000..7b11e199df
--- /dev/null
+++ b/civicrm/CRM/Upgrade/Incremental/MessageTemplates.php
@@ -0,0 +1,155 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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-2018
+ */
+class CRM_Upgrade_Incremental_MessageTemplates {
+
+  /**
+   * Version we are upgrading to.
+   *
+   * @var string
+   */
+  protected $upgradeVersion;
+
+  /**
+   * @return string
+   */
+  public function getUpgradeVersion() {
+    return $this->upgradeVersion;
+  }
+
+  /**
+   * @param string $upgradeVersion
+   */
+  public function setUpgradeVersion($upgradeVersion) {
+    $this->upgradeVersion = $upgradeVersion;
+  }
+
+  /**
+   * CRM_Upgrade_Incremental_MessageTemplates constructor.
+   *
+   * @param string $upgradeVersion
+   */
+  public function __construct($upgradeVersion) {
+    $this->setUpgradeVersion($upgradeVersion);
+  }
+
+  /**
+   * Get any templates that have been updated.
+   *
+   * @return array
+   */
+  protected function getTemplateUpdates() {
+    return [
+      [
+        'version' => '5.4.alpha1',
+        'upgrade_descriptor' => ts('Use email greeting at top where available'),
+        'templates' => [
+          ['name' => 'membership_online_receipt', 'type' => 'text'],
+          ['name' => 'membership_online_receipt', 'type' => 'html'],
+          ['name' => 'contribution_online_receipt', 'type' => 'text'],
+          ['name' => 'contribution_online_receipt', 'type' => 'html'],
+          ['name' => 'event_online_receipt', 'type' => 'text'],
+          ['name' => 'event_online_receipt', 'type' => 'html'],
+          ['name' => 'event_online_receipt', 'type' => 'subject'],
+        ]
+      ],
+    ];
+  }
+
+  /**
+   * Get any required template updates.
+   *
+   * @return array
+   */
+  public function getTemplatesToUpdate() {
+    $templates = $this->getTemplateUpdates();
+    $return = [];
+    foreach ($templates as $templateArray) {
+      if ($templateArray['version'] === $this->getUpgradeVersion()) {
+        foreach ($templateArray['templates'] as $template) {
+          $return[$template['name'] . '_' . $template['type']] = array_merge($template, $templateArray);
+        }
+      }
+    }
+    return $return;
+  }
+
+  /**
+   * Get the upgrade messages.
+   */
+  public function getUpgradeMessages() {
+    $updates = $this->getTemplatesToUpdate();
+    $messages = [];
+    foreach ($updates as $key => $value) {
+      $templateLabel = civicrm_api3('OptionValue', 'getvalue', [
+        'return' => 'label',
+        'name' => $value['name'],
+        'options' => ['limit' => 1],
+      ]);
+      $messages[$templateLabel] = $value['upgrade_descriptor'];
+    }
+    return $messages;
+  }
+
+  /**
+   * Update message templates.
+   */
+  public function updateTemplates() {
+    $templates = $this->getTemplatesToUpdate();
+    foreach ($templates as $template) {
+      $workFlowID = CRM_Core_DAO::singleValueQuery("SELECT MAX(id) as id FROM civicrm_option_value WHERE name = %1", [
+        1 => [$template['name'], 'String'],
+      ]);
+      $content = file_get_contents(\Civi::paths()->getPath('[civicrm.root]/xml/templates/message_templates/' . $template['name'] . '_' . $template['type'] . '.tpl'));
+      $templatesToUpdate = [];
+      $templatesToUpdate[] = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_msg_template WHERE workflow_id = $workFlowID AND is_reserved = 1");
+      $defaultTemplateID = CRM_Core_DAO::singleValueQuery("
+        SELECT default_template.id FROM civicrm_msg_template reserved
+        LEFT JOIN civicrm_msg_template default_template
+          ON reserved.workflow_id = default_template.workflow_id
+        WHERE reserved.workflow_id = $workFlowID
+        AND reserved.is_reserved = 1 AND default_template.is_default = 1 AND reserved.id <> default_template.id
+        AND reserved.msg_{$template['type']} = default_template.msg_{$template['type']}
+      ");
+      if ($defaultTemplateID) {
+        $templatesToUpdate[] = $defaultTemplateID;
+      }
+
+      CRM_Core_DAO::executeQuery("
+        UPDATE civicrm_msg_template SET msg_{$template['type']} = %1 WHERE id IN (" . implode(',', $templatesToUpdate) . ")", [
+          1 => [$content, 'String']
+          ]
+      );
+    }
+  }
+
+}
diff --git a/civicrm/CRM/Upgrade/Incremental/php/FiveFour.php b/civicrm/CRM/Upgrade/Incremental/php/FiveFour.php
new file mode 100644
index 0000000000..9c4082c0d7
--- /dev/null
+++ b/civicrm/CRM/Upgrade/Incremental/php/FiveFour.php
@@ -0,0 +1,137 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | 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.                                       |
+ |                                                                    |
+ | 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 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Upgrade logic for FiveFour */
+class CRM_Upgrade_Incremental_php_FiveFour extends CRM_Upgrade_Incremental_Base {
+
+  /**
+   * Compute any messages which should be displayed beforeupgrade.
+   *
+   * Note: This function is called iteratively for each upcoming
+   * revision to the database.
+   *
+   * @param string $preUpgradeMessage
+   * @param string $rev
+   *   a version number, e.g. '4.4.alpha1', '4.4.beta3', '4.4.0'.
+   * @param null $currentVer
+   */
+  public function setPreUpgradeMessage(&$preUpgradeMessage, $rev, $currentVer = NULL) {
+    // Example: Generate a pre-upgrade message.
+    // if ($rev == '5.12.34') {
+    //   $preUpgradeMessage .= '<p>' . ts('A new permission has been added called %1 This Permission is now used to control access to the Manage Tags screen', array(1 => 'manage tags')) . '</p>';
+    // }
+  }
+
+  /**
+   * Compute any messages which should be displayed after upgrade.
+   *
+   * @param string $postUpgradeMessage
+   *   alterable.
+   * @param string $rev
+   *   an intermediate version; note that setPostUpgradeMessage is called repeatedly with different $revs.
+   */
+  public function setPostUpgradeMessage(&$postUpgradeMessage, $rev) {
+    $postUpgradeMessage .= '<p>' . ts('A new %1 permission has been added. It is not granted by default. If your users create reports, you may wish to review your permissions.', array(1 => 'save Report Criteria')) . '</p>';
+    // Example: Generate a post-upgrade message.
+    // if ($rev == '5.12.34') {
+    //   $postUpgradeMessage .= '<br /><br />' . ts("By default, CiviCRM now disables the ability to import directly from SQL. To use this feature, you must explicitly grant permission 'import SQL datasource'.");
+    // }
+  }
+
+  /**
+   * Upgrade function.
+   *
+   * @param string $rev
+   */
+  public function upgrade_5_4_alpha1($rev) {
+    $this->addTask(ts('Upgrade DB to %1: SQL', array(1 => $rev)), 'runSql', $rev);
+    $this->addTask('Add Cancel Button Setting to the Profile', 'addColumn',
+      'civicrm_uf_group', 'add_cancel_button', "tinyint DEFAULT '1' COMMENT 'Should a Cancel button be included in this Profile form.'");
+    $this->addTask('Add location_id if missing to group_contact table (affects some older installs CRM-20711)', 'addColumn',
+      'civicrm_group_contact', 'location_id', "int(10) unsigned DEFAULT NULL COMMENT 'Optional location to associate with this membership'");
+    $this->addTask('dev/core#107 - Add Activity\'s default assignee options', 'addActivityDefaultAssigneeOptions');
+  }
+
+  /**
+   * This task adds the default assignee option values that can be selected when
+   * creating or editing a new workflow's activity.
+   *
+   * @return bool
+   */
+  public static function addActivityDefaultAssigneeOptions() {
+    // Add option group for activity default assignees:
+    CRM_Core_BAO_OptionGroup::ensureOptionGroupExists(array(
+      'name' => 'activity_default_assignee',
+      'title' => ts('Activity default assignee'),
+      'is_reserved' => 1,
+    ));
+
+    // Add option values for activity default assignees:
+    $options = array(
+      array('name' => 'NONE', 'label' => ts('None'), 'is_default' => 1),
+      array('name' => 'BY_RELATIONSHIP', 'label' => ts('By relationship to case client')),
+      array('name' => 'SPECIFIC_CONTACT', 'label' => ts('Specific contact')),
+      array('name' => 'USER_CREATING_THE_CASE', 'label' => ts('User creating the case')),
+    );
+
+    foreach ($options as $option) {
+      CRM_Core_BAO_OptionValue::ensureOptionValueExists(array(
+        'option_group_id' => 'activity_default_assignee',
+        'name' => $option['name'],
+        'label' => $option['label'],
+        'is_default' => CRM_Utils_Array::value('is_default', $option, 0),
+        'is_active' => TRUE,
+      ));
+    }
+
+    return TRUE;
+  }
+
+  /*
+   * Important! All upgrade functions MUST add a 'runSql' task.
+   * Uncomment and use the following template for a new upgrade version
+   * (change the x in the function name):
+   */
+
+  //  /**
+  //   * Upgrade function.
+  //   *
+  //   * @param string $rev
+  //   */
+  //  public function upgrade_5_0_x($rev) {
+  //    $this->addTask(ts('Upgrade DB to %1: SQL', array(1 => $rev)), 'runSql', $rev);
+  //    $this->addTask('Do the foo change', 'taskFoo', ...);
+  //    // Additional tasks here...
+  //    // Note: do not use ts() in the addTask description because it adds unnecessary strings to transifex.
+  //    // The above is an exception because 'Upgrade DB to %1: SQL' is generic & reusable.
+  //  }
+
+  // public static function taskFoo(CRM_Queue_TaskContext $ctx, ...) {
+  //   return TRUE;
+  // }
+
+}
diff --git a/civicrm/CRM/Upgrade/Incremental/sql/5.3.0.mysql.tpl b/civicrm/CRM/Upgrade/Incremental/sql/5.3.0.mysql.tpl
deleted file mode 100644
index b557c2542b..0000000000
--- a/civicrm/CRM/Upgrade/Incremental/sql/5.3.0.mysql.tpl
+++ /dev/null
@@ -1,18 +0,0 @@
-{* file to handle db changes in 5.3.0 during upgrade *}
-ALTER TABLE civicrm_custom_group ALTER column is_multiple SET DEFAULT 0;
-UPDATE civicrm_custom_group SET is_multiple = 0 WHERE is_multiple IS NULL;
-ALTER TABLE civicrm_custom_group ALTER column is_active SET DEFAULT 1;
-ALTER TABLE civicrm_custom_field ALTER column is_view SET DEFAULT 0;
-UPDATE civicrm_custom_field SET is_view = 0 WHERE is_view IS NULL;
-ALTER TABLE civicrm_custom_field ALTER column is_required SET DEFAULT 0;
-UPDATE civicrm_custom_field SET is_required = 0 WHERE is_required IS NULL;
-ALTER TABLE civicrm_custom_field ALTER column is_searchable SET DEFAULT 0;
-UPDATE civicrm_custom_field SET is_searchable = 0 WHERE is_required IS NULL;
-ALTER TABLE civicrm_custom_field ALTER column is_active SET DEFAULT 1;
-
-SET @UKCountryId = (SELECT id FROM civicrm_country cc WHERE cc.name = 'United Kingdom');
-INSERT INTO civicrm_state_province (country_id, abbreviation, name)
-VALUES (@UKCountryId, 'MON', 'Monmouthshire');
-
-{* dev/core/#152 *}
-UPDATE `civicrm_custom_field` set `html_type` = "Multi-Select" WHERE `html_type` = "AdvMulti-Select";
diff --git a/civicrm/CRM/Upgrade/Incremental/sql/5.3.1.mysql.tpl b/civicrm/CRM/Upgrade/Incremental/sql/5.3.1.mysql.tpl
deleted file mode 100644
index 2e2057d6dd..0000000000
--- a/civicrm/CRM/Upgrade/Incremental/sql/5.3.1.mysql.tpl
+++ /dev/null
@@ -1,19 +0,0 @@
-{* file to handle db changes in 5.3.1 during upgrade *}
-{* Re run upgrades from 5.3.0 Just in case they were missed somehow due to dodgy tarball *}
-ALTER TABLE civicrm_custom_group ALTER column is_multiple SET DEFAULT 0;
-UPDATE civicrm_custom_group SET is_multiple = 0 WHERE is_multiple IS NULL;
-ALTER TABLE civicrm_custom_group ALTER column is_active SET DEFAULT 1;
-ALTER TABLE civicrm_custom_field ALTER column is_view SET DEFAULT 0;
-UPDATE civicrm_custom_field SET is_view = 0 WHERE is_view IS NULL;
-ALTER TABLE civicrm_custom_field ALTER column is_required SET DEFAULT 0;
-UPDATE civicrm_custom_field SET is_required = 0 WHERE is_required IS NULL;
-ALTER TABLE civicrm_custom_field ALTER column is_searchable SET DEFAULT 0;
-UPDATE civicrm_custom_field SET is_searchable = 0 WHERE is_required IS NULL;
-ALTER TABLE civicrm_custom_field ALTER column is_active SET DEFAULT 1;
-
-SET @UKCountryId = (SELECT id FROM civicrm_country cc WHERE cc.name = 'United Kingdom');
-INSERT IGNORE INTO civicrm_state_province (country_id, abbreviation, name)
-VALUES (@UKCountryId, 'MON', 'Monmouthshire');
-
-{* dev/core/#152 *}
-UPDATE `civicrm_custom_field` set `html_type` = "Multi-Select" WHERE `html_type` = "AdvMulti-Select";
diff --git a/civicrm/CRM/Upgrade/Incremental/sql/5.3.2.mysql.tpl b/civicrm/CRM/Upgrade/Incremental/sql/5.3.2.mysql.tpl
deleted file mode 100644
index 3caad28f74..0000000000
--- a/civicrm/CRM/Upgrade/Incremental/sql/5.3.2.mysql.tpl
+++ /dev/null
@@ -1 +0,0 @@
-{* file to handle db changes in 5.3.2 during upgrade *}
diff --git a/civicrm/CRM/Upgrade/Incremental/sql/5.3.alpha1.mysql.tpl b/civicrm/CRM/Upgrade/Incremental/sql/5.3.alpha1.mysql.tpl
index 8a3d52ecc1..7c05f9ecaa 100644
--- a/civicrm/CRM/Upgrade/Incremental/sql/5.3.alpha1.mysql.tpl
+++ b/civicrm/CRM/Upgrade/Incremental/sql/5.3.alpha1.mysql.tpl
@@ -1 +1,18 @@
 {* file to handle db changes in 5.3.alpha1 during upgrade *}
+ALTER TABLE civicrm_custom_group ALTER column is_multiple SET DEFAULT 0;
+UPDATE civicrm_custom_group SET is_multiple = 0 WHERE is_multiple IS NULL;
+ALTER TABLE civicrm_custom_group ALTER column is_active SET DEFAULT 1;
+ALTER TABLE civicrm_custom_field ALTER column is_view SET DEFAULT 0;
+UPDATE civicrm_custom_field SET is_view = 0 WHERE is_view IS NULL;
+ALTER TABLE civicrm_custom_field ALTER column is_required SET DEFAULT 0;
+UPDATE civicrm_custom_field SET is_required = 0 WHERE is_required IS NULL;
+ALTER TABLE civicrm_custom_field ALTER column is_searchable SET DEFAULT 0;
+UPDATE civicrm_custom_field SET is_searchable = 0 WHERE is_required IS NULL;
+ALTER TABLE civicrm_custom_field ALTER column is_active SET DEFAULT 1;
+
+SET @UKCountryId = (SELECT id FROM civicrm_country cc WHERE cc.name = 'United Kingdom');
+INSERT IGNORE INTO civicrm_state_province (country_id, abbreviation, name)
+VALUES (@UKCountryId, 'MON', 'Monmouthshire');
+
+{* dev/core/#152 *}
+UPDATE `civicrm_custom_field` set `html_type` = "Multi-Select" WHERE `html_type` = "AdvMulti-Select";
diff --git a/civicrm/CRM/Upgrade/Incremental/sql/5.4.0.mysql.tpl b/civicrm/CRM/Upgrade/Incremental/sql/5.4.0.mysql.tpl
new file mode 100644
index 0000000000..4d21c517ad
--- /dev/null
+++ b/civicrm/CRM/Upgrade/Incremental/sql/5.4.0.mysql.tpl
@@ -0,0 +1 @@
+{* file to handle db changes in 5.4.0 during upgrade *}
diff --git a/civicrm/CRM/Upgrade/Incremental/sql/5.4.alpha1.mysql.tpl b/civicrm/CRM/Upgrade/Incremental/sql/5.4.alpha1.mysql.tpl
new file mode 100644
index 0000000000..e47d96cf83
--- /dev/null
+++ b/civicrm/CRM/Upgrade/Incremental/sql/5.4.alpha1.mysql.tpl
@@ -0,0 +1,13 @@
+{* file to handle db changes in 5.4.alpha1 during upgrade *}
+
+{*
+v4.7.20 updated these colums so that new installs would default to TIMESTAMP instead of DATETIME.
+Status-checks and DoctorWhen have been encouraging a transition, but it wasn't mandated, and there
+was little urgency... because `expired_date` was ignored, and adhoc TTLs on `created_date` had
+generally long windows. Now that we're using `expired_date` in more important ways for 5.4,
+we want to ensure that these values are handled precisely and consistently.
+*}
+
+ALTER TABLE civicrm_cache
+  CHANGE created_date created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'When was the cache item created',
+  CHANGE expired_date expired_date  TIMESTAMP NULL DEFAULT NULL COMMENT 'When should the cache item expire';
diff --git a/civicrm/CRM/Upgrade/Incremental/sql/5.4.beta1.mysql.tpl b/civicrm/CRM/Upgrade/Incremental/sql/5.4.beta1.mysql.tpl
new file mode 100644
index 0000000000..b486eda69d
--- /dev/null
+++ b/civicrm/CRM/Upgrade/Incremental/sql/5.4.beta1.mysql.tpl
@@ -0,0 +1 @@
+{* file to handle db changes in 5.4.beta1 during upgrade *}
diff --git a/civicrm/CRM/Utils/Cache.php b/civicrm/CRM/Utils/Cache.php
index 00a92888fc..171e4d5437 100644
--- a/civicrm/CRM/Utils/Cache.php
+++ b/civicrm/CRM/Utils/Cache.php
@@ -35,6 +35,9 @@
  * Cache is an empty base object, we'll modify the scheme when we have different caching schemes
  */
 class CRM_Utils_Cache {
+
+  const DELIMITER = '/';
+
   /**
    * (Quasi-Private) Treat this as private. It is marked public to facilitate testing.
    *
@@ -83,6 +86,7 @@ class CRM_Utils_Cache {
       // a generic method for utilizing any of the available db caches.
       $dbCacheClass = 'CRM_Utils_Cache_' . $className;
       $settings = self::getCacheSettings($className);
+      $settings['prefix'] = CRM_Utils_Array::value('prefix', $settings, '') . self::DELIMITER . 'default' . self::DELIMITER;
       self::$_singleton = new $dbCacheClass($settings);
     }
     return self::$_singleton;
@@ -186,7 +190,7 @@ class CRM_Utils_Cache {
           if (defined('CIVICRM_DB_CACHE_CLASS') && in_array(CIVICRM_DB_CACHE_CLASS, array('Memcache', 'Memcached', 'Redis'))) {
             $dbCacheClass = 'CRM_Utils_Cache_' . CIVICRM_DB_CACHE_CLASS;
             $settings = self::getCacheSettings(CIVICRM_DB_CACHE_CLASS);
-            $settings['prefix'] = $settings['prefix'] . '_' . $params['name'];
+            $settings['prefix'] = CRM_Utils_Array::value('prefix', $settings, '') . self::DELIMITER . $params['name'] . self::DELIMITER;
             return new $dbCacheClass($settings);
           }
           break;
@@ -210,4 +214,30 @@ class CRM_Utils_Cache {
     throw new CRM_Core_Exception("Failed to instantiate cache. No supported cache type found. " . print_r($params, 1));
   }
 
+  /**
+   * Assert that a key is well-formed.
+   *
+   * @param string $key
+   * @return string
+   *   Same $key, if it's valid.
+   * @throws \CRM_Utils_Cache_InvalidArgumentException
+   */
+  public static function assertValidKey($key) {
+    $strict = CRM_Utils_Constant::value('CIVICRM_PSR16_STRICT', FALSE) || defined('CIVICRM_TEST');
+
+    if (!is_string($key)) {
+      throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache key: Not a string");
+    }
+
+    if ($strict && !preg_match(';^[A-Za-z0-9_\-\. ]+$;', $key)) {
+      throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache key: Illegal characters");
+    }
+
+    if ($strict && strlen($key) > 255) {
+      throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache key: Too long");
+    }
+
+    return $key;
+  }
+
 }
diff --git a/civicrm/CRM/Utils/Cache/APCcache.php b/civicrm/CRM/Utils/Cache/APCcache.php
index e696aa19c3..26fa86b241 100644
--- a/civicrm/CRM/Utils/Cache/APCcache.php
+++ b/civicrm/CRM/Utils/Cache/APCcache.php
@@ -31,6 +31,10 @@
  * @copyright CiviCRM LLC (c) 2004-2018
  */
 class CRM_Utils_Cache_APCcache implements CRM_Utils_Cache_Interface {
+
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+  use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation
+
   const DEFAULT_TIMEOUT = 3600;
   const DEFAULT_PREFIX = '';
 
@@ -72,11 +76,19 @@ class CRM_Utils_Cache_APCcache implements CRM_Utils_Cache_Interface {
   /**
    * @param $key
    * @param $value
+   * @param null|int|\DateInterval $ttl
    *
    * @return bool
    */
-  public function set($key, &$value) {
-    if (!apc_store($this->_prefix . $key, $value, $this->_timeout)) {
+  public function set($key, $value, $ttl = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    if (is_int($ttl) && $ttl <= 0) {
+      return $this->delete($key);
+    }
+
+    $ttl = CRM_Utils_Date::convertCacheTtl($ttl, $this->_timeout);
+    $expires = time() + $ttl;
+    if (!apc_store($this->_prefix . $key, ['e' => $expires, 'v' => $value], $ttl)) {
       return FALSE;
     }
     return TRUE;
@@ -84,11 +96,17 @@ class CRM_Utils_Cache_APCcache implements CRM_Utils_Cache_Interface {
 
   /**
    * @param $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function get($key) {
-    return apc_fetch($this->_prefix . $key);
+  public function get($key, $default = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    $result = apc_fetch($this->_prefix . $key, $success);
+    if ($success && isset($result['e']) && $result['e'] > time()) {
+      return $this->reobjectify($result['v']);
+    }
+    return $default;
   }
 
   /**
@@ -97,22 +115,33 @@ class CRM_Utils_Cache_APCcache implements CRM_Utils_Cache_Interface {
    * @return bool|string[]
    */
   public function delete($key) {
-    return apc_delete($this->_prefix . $key);
+    CRM_Utils_Cache::assertValidKey($key);
+    apc_delete($this->_prefix . $key);
+    return TRUE;
   }
 
   public function flush() {
     $allinfo = apc_cache_info('user');
     $keys = $allinfo['cache_list'];
-    $prefix = $this->_prefix . "CRM_";  // Our keys follows this pattern: ([A-Za-z0-9_]+)?CRM_[A-Za-z0-9_]+
+    $prefix = $this->_prefix;  // Our keys follows this pattern: ([A-Za-z0-9_]+)?CRM_[A-Za-z0-9_]+
     $lp = strlen($prefix);              // Get prefix length
 
     foreach ($keys as $key) {
       $name = $key['info'];
       if ($prefix == substr($name, 0, $lp)) {
         // Ours?
-        apc_delete($this->_prefix . $name);
+        apc_delete($name);
       }
     }
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
+  }
+
+  private function reobjectify($value) {
+    return is_object($value) ? unserialize(serialize($value)) : $value;
   }
 
 }
diff --git a/civicrm/CRM/Utils/Cache/ArrayCache.php b/civicrm/CRM/Utils/Cache/ArrayCache.php
index b2272fa1eb..287c6a3e5c 100644
--- a/civicrm/CRM/Utils/Cache/ArrayCache.php
+++ b/civicrm/CRM/Utils/Cache/ArrayCache.php
@@ -36,11 +36,18 @@
  */
 class CRM_Utils_Cache_Arraycache implements CRM_Utils_Cache_Interface {
 
+  use CRM_Utils_Cache_NaiveMultipleTrait;
+  use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation
+
+  const DEFAULT_TIMEOUT = 3600;
+
   /**
    * The cache storage container, an in memory array by default
    */
   protected $_cache;
 
+  protected $_expires;
+
   /**
    * Constructor.
    *
@@ -51,35 +58,67 @@ class CRM_Utils_Cache_Arraycache implements CRM_Utils_Cache_Interface {
    */
   public function __construct($config) {
     $this->_cache = array();
+    $this->_expires = array();
   }
 
   /**
    * @param string $key
    * @param mixed $value
+   * @param null|int|\DateInterval $ttl
+   * @return bool
+   * @throws \Psr\SimpleCache\InvalidArgumentException
    */
-  public function set($key, &$value) {
-    $this->_cache[$key] = $value;
+  public function set($key, $value, $ttl = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    $this->_cache[$key] = $this->reobjectify($value);
+    $this->_expires[$key] = CRM_Utils_Date::convertCacheTtlToExpires($ttl, self::DEFAULT_TIMEOUT);
+    return TRUE;
   }
 
   /**
    * @param string $key
+   * @param mixed $default
    *
    * @return mixed
+   * @throws \Psr\SimpleCache\InvalidArgumentException
    */
-  public function get($key) {
-    return CRM_Utils_Array::value($key, $this->_cache);
+  public function get($key, $default = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    if (isset($this->_expires[$key]) && is_numeric($this->_expires[$key]) && $this->_expires[$key] <= time()) {
+      return $default;
+    }
+    if (array_key_exists($key, $this->_cache)) {
+      return $this->reobjectify($this->_cache[$key]);
+    }
+    return $default;
   }
 
   /**
    * @param string $key
+   * @return bool
+   * @throws \Psr\SimpleCache\InvalidArgumentException
    */
   public function delete($key) {
+    CRM_Utils_Cache::assertValidKey($key);
+
     unset($this->_cache[$key]);
+    unset($this->_expires[$key]);
+    return TRUE;
   }
 
   public function flush() {
     unset($this->_cache);
+    unset($this->_expires);
     $this->_cache = array();
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
+  }
+
+  private function reobjectify($value) {
+    return is_object($value) ? unserialize(serialize($value)) : $value;
   }
 
 }
diff --git a/civicrm/CRM/Utils/Cache/CacheException.php b/civicrm/CRM/Utils/Cache/CacheException.php
new file mode 100644
index 0000000000..cdb0821da2
--- /dev/null
+++ b/civicrm/CRM/Utils/Cache/CacheException.php
@@ -0,0 +1,36 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Class CRM_Utils_Cache_CacheException
+ *
+ * NOTE: PSR-16 specifies its exceptions using interfaces. For cache-consumers,
+ * it's better to catch based on the interface. For cache-drivers, we need
+ * a concrete class.
+ */
+class CRM_Utils_Cache_CacheException extends \CRM_Core_Exception implements \Psr\SimpleCache\CacheException {
+}
diff --git a/civicrm/CRM/Utils/Cache/Interface.php b/civicrm/CRM/Utils/Cache/Interface.php
index f11327cb59..9effdd9d3a 100644
--- a/civicrm/CRM/Utils/Cache/Interface.php
+++ b/civicrm/CRM/Utils/Cache/Interface.php
@@ -30,60 +30,78 @@
  * @package CRM
  * @copyright CiviCRM LLC (c) 2004-2018
  *
- * CRM_Utils_Cache_Interface
+ * CRM_Utils_Cache_Interface is a long-standing interface used within CiviCRM
+ * for interacting with a cache service. In style and substance, it is extremely
+ * similar to PHP-FIG's SimpleCache interface (PSR-16). Consequently, beginning
+ * with CiviCRM v5.4, this extends \Psr\SimpleCache\CacheInterface.
  *
- * PHP-FIG has been developing a draft standard for caching,
- * PSR-6. The standard has not been ratified yet. When
- * making changes to this interface, please take care to
- * avoid *conflicst* with PSR-6's CacheItemPoolInterface. At
- * time of writing, they do not conflict. Avoiding conflicts
- * will enable more transition paths where Civi
- * simultaneously supports both interfaces in the same
- * implementation.
- *
- * For example, the current interface defines:
- *
- *   function get($key) => mixed $value
- *
- * and PSR-6 defines:
- *
- *   function getItem($key) => ItemInterface $item
- *
- * These are different styles (e.g. "weak item" vs "strong item"),
- * but the two methods do not *conflict*. They can coexist,
- * and you can trivially write adapters between the two.
- *
- * @see https://github.com/php-fig/fig-standards/blob/master/proposed/cache.md
+ * @see https://www.php-fig.org/psr/psr-16/
  */
-interface CRM_Utils_Cache_Interface {
+interface CRM_Utils_Cache_Interface extends \Psr\SimpleCache\CacheInterface {
 
   /**
    * Set the value in the cache.
    *
    * @param string $key
    * @param mixed $value
+   * @param null|int|\DateInterval $ttl
+   * @return bool
    */
-  public function set($key, &$value);
+  public function set($key, $value, $ttl = NULL);
 
   /**
    * Get a value from the cache.
    *
    * @param string $key
+   * @param mixed $default
    * @return mixed
-   *   NULL if $key has not been previously set
+   *   The previously set value value, or $default (NULL).
    */
-  public function get($key);
+  public function get($key, $default = NULL);
 
   /**
    * Delete a value from the cache.
    *
    * @param string $key
+   * @return bool
    */
   public function delete($key);
 
   /**
    * Delete all values from the cache.
+   *
+   * NOTE: flush() and clear() should be aliases. flush() is specified by
+   * Civi's traditional interface, and clear() is specified by PSR-16.
+   *
+   * @return bool
+   * @see clear
+   * @deprecated
    */
   public function flush();
 
+  /**
+   * Delete all values from the cache.
+   *
+   * NOTE: flush() and clear() should be aliases. flush() is specified by
+   * Civi's traditional interface, and clear() is specified by PSR-16.
+   *
+   * @return bool
+   * @see flush
+   */
+  public function clear();
+
+  /**
+   * Determines whether an item is present in the cache.
+   *
+   * NOTE: It is recommended that has() is only to be used for cache warming type purposes
+   * and not to be used within your live applications operations for get/set, as this method
+   * is subject to a race condition where your has() will return true and immediately after,
+   * another script can remove it making the state of your app out of date.
+   *
+   * @param string $key The cache item key.
+   *
+   * @return bool
+   */
+  public function has($key);
+
 }
diff --git a/civicrm/CRM/Utils/Cache/InvalidArgumentException.php b/civicrm/CRM/Utils/Cache/InvalidArgumentException.php
new file mode 100644
index 0000000000..5d9747d916
--- /dev/null
+++ b/civicrm/CRM/Utils/Cache/InvalidArgumentException.php
@@ -0,0 +1,36 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Class CRM_Utils_Cache_InvalidArgumentException
+ *
+ * NOTE: PSR-16 specifies its exceptions using interfaces. For cache-consumers,
+ * it's better to catch based on the interface. For cache-drivers, we need
+ * a concrete class.
+ */
+class CRM_Utils_Cache_InvalidArgumentException extends \CRM_Core_Exception implements \Psr\SimpleCache\InvalidArgumentException {
+}
diff --git a/civicrm/CRM/Utils/Cache/Memcache.php b/civicrm/CRM/Utils/Cache/Memcache.php
index e7455bd498..5bbdf5e0e6 100644
--- a/civicrm/CRM/Utils/Cache/Memcache.php
+++ b/civicrm/CRM/Utils/Cache/Memcache.php
@@ -31,11 +31,19 @@
  * @copyright CiviCRM LLC (c) 2004-2018
  */
 class CRM_Utils_Cache_Memcache implements CRM_Utils_Cache_Interface {
+
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+
   const DEFAULT_HOST = 'localhost';
   const DEFAULT_PORT = 11211;
   const DEFAULT_TIMEOUT = 3600;
   const DEFAULT_PREFIX = '';
 
+  /**
+   * If another process clears namespace, we'll find out in ~5 sec.
+   */
+  const NS_LOCAL_TTL = 5;
+
   /**
    * The host name of the memcached server.
    *
@@ -75,6 +83,15 @@ class CRM_Utils_Cache_Memcache implements CRM_Utils_Cache_Interface {
    */
   protected $_cache;
 
+  /**
+   * @var NULL|array
+   *
+   * This is the effective prefix. It may be bumped up whenever the dataset is flushed.
+   *
+   * @see https://github.com/memcached/memcached/wiki/ProgrammingTricks#deleting-by-namespace
+   */
+  protected $_truePrefix = NULL;
+
   /**
    * Constructor.
    *
@@ -109,40 +126,82 @@ class CRM_Utils_Cache_Memcache implements CRM_Utils_Cache_Interface {
   /**
    * @param $key
    * @param $value
+   * @param null|int|\DateInterval $ttl
    *
    * @return bool
    */
-  public function set($key, &$value) {
-    if (!$this->_cache->set($this->_prefix . $key, $value, FALSE, $this->_timeout)) {
-      return FALSE;
+  public function set($key, $value, $ttl = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    if (is_int($ttl) && $ttl <= 0) {
+      return $this->delete($key);
     }
-    return TRUE;
+    $expires = CRM_Utils_Date::convertCacheTtlToExpires($ttl, $this->_timeout);
+    return $this->_cache->set($this->getTruePrefix() . $key, serialize($value), FALSE, $expires);
   }
 
   /**
    * @param $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function &get($key) {
-    $result = $this->_cache->get($this->_prefix . $key);
-    return $result;
+  public function get($key, $default = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    $result = $this->_cache->get($this->getTruePrefix() . $key);
+    return ($result === FALSE) ? $default : unserialize($result);
   }
 
+  /**
+   * @param string $key
+   *
+   * @return bool
+   * @throws \Psr\SimpleCache\CacheException
+   */
+  public function has($key) {
+    CRM_Utils_Cache::assertValidKey($key);
+    $result = $this->_cache->get($this->getTruePrefix() . $key);
+    return ($result !== FALSE);
+  }
+
+
   /**
    * @param $key
    *
-   * @return mixed
+   * @return bool
    */
   public function delete($key) {
-    return $this->_cache->delete($this->_prefix . $key);
+    CRM_Utils_Cache::assertValidKey($key);
+    $this->_cache->delete($this->getTruePrefix() . $key);
+    return TRUE;
   }
 
   /**
-   * @return mixed
+   * @return bool
    */
   public function flush() {
-    return $this->_cache->flush();
+    $this->_truePrefix = NULL;
+    $this->_cache->delete($this->_prefix);
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
+  }
+
+  protected function getTruePrefix() {
+    if ($this->_truePrefix === NULL || $this->_truePrefix['expires'] < time()) {
+      $key = $this->_prefix;
+      $value = $this->_cache->get($key);
+      if ($value === FALSE) {
+        $value = uniqid();
+        $this->_cache->set($key, $value, FALSE, 0); // Indefinite.
+      }
+      $this->_truePrefix = [
+        'value' => $value,
+        'expires' => time() + self::NS_LOCAL_TTL,
+      ];
+    }
+    return $this->_prefix . $this->_truePrefix['value'] . '/';
   }
 
 }
diff --git a/civicrm/CRM/Utils/Cache/Memcached.php b/civicrm/CRM/Utils/Cache/Memcached.php
index e80b1af4b3..546c5b933b 100644
--- a/civicrm/CRM/Utils/Cache/Memcached.php
+++ b/civicrm/CRM/Utils/Cache/Memcached.php
@@ -31,11 +31,19 @@
  * @copyright CiviCRM LLC (c) 2004-2018
  */
 class CRM_Utils_Cache_Memcached implements CRM_Utils_Cache_Interface {
+
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+
   const DEFAULT_HOST = 'localhost';
   const DEFAULT_PORT = 11211;
   const DEFAULT_TIMEOUT = 3600;
   const DEFAULT_PREFIX = '';
-  const MAX_KEY_LEN = 62;
+  const MAX_KEY_LEN = 200;
+
+  /**
+   * If another process clears namespace, we'll find out in ~5 sec.
+   */
+  const NS_LOCAL_TTL = 5;
 
   /**
    * The host name of the memcached server
@@ -76,6 +84,15 @@ class CRM_Utils_Cache_Memcached implements CRM_Utils_Cache_Interface {
    */
   protected $_cache;
 
+  /**
+   * @var NULL|array
+   *
+   * This is the effective prefix. It may be bumped up whenever the dataset is flushed.
+   *
+   * @see https://github.com/memcached/memcached/wiki/ProgrammingTricks#deleting-by-namespace
+   */
+  protected $_truePrefix = NULL;
+
   /**
    * Constructor.
    *
@@ -110,29 +127,79 @@ class CRM_Utils_Cache_Memcached implements CRM_Utils_Cache_Interface {
   /**
    * @param $key
    * @param $value
+   * @param null|int|\DateInterval $ttl
    *
    * @return bool
    * @throws Exception
    */
-  public function set($key, &$value) {
+  public function set($key, $value, $ttl = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    if (is_int($ttl) && $ttl <= 0) {
+      return $this->delete($key);
+    }
+    $expires = CRM_Utils_Date::convertCacheTtlToExpires($ttl, $this->_timeout);
+
     $key = $this->cleanKey($key);
-    if (!$this->_cache->set($key, $value, $this->_timeout)) {
-      CRM_Core_Error::debug('Result Code: ', $this->_cache->getResultMessage());
-      CRM_Core_Error::fatal("memcached set failed, wondering why?, $key", $value);
+    if (!$this->_cache->set($key, serialize($value), $expires)) {
+      if (PHP_SAPI === 'cli' || (Civi\Core\Container::isContainerBooted() && CRM_Core_Permission::check('view debug output'))) {
+        throw new CRM_Utils_Cache_CacheException("Memcached::set($key) failed: " . $this->_cache->getResultMessage());
+      }
+      else {
+        Civi::log()->error("Memcached::set($key) failed: " . $this->_cache->getResultMessage());
+        throw new CRM_Utils_Cache_CacheException("Memcached::set($key) failed");
+      }
       return FALSE;
+
     }
     return TRUE;
   }
 
   /**
    * @param $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function &get($key) {
+  public function get($key, $default = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
     $key = $this->cleanKey($key);
     $result = $this->_cache->get($key);
-    return $result;
+    switch ($this->_cache->getResultCode()) {
+      case Memcached::RES_SUCCESS:
+        return unserialize($result);
+
+      case Memcached::RES_NOTFOUND:
+        return $default;
+
+      default:
+        Civi::log()->error("Memcached::get($key) failed: " . $this->_cache->getResultMessage());
+        throw new CRM_Utils_Cache_CacheException("Memcached set ($key) failed");
+    }
+  }
+
+  /**
+   * @param string $key
+   *
+   * @return bool
+   * @throws \Psr\SimpleCache\CacheException
+   */
+  public function has($key) {
+    CRM_Utils_Cache::assertValidKey($key);
+    $key = $this->cleanKey($key);
+    if ($this->_cache->get($key) !== FALSE) {
+      return TRUE;
+    }
+    switch ($this->_cache->getResultCode()) {
+      case Memcached::RES_NOTFOUND:
+        return FALSE;
+
+      case Memcached::RES_SUCCESS:
+        return TRUE;
+
+      default:
+        Civi::log()->error("Memcached::has($key) failed: " . $this->_cache->getResultMessage());
+        throw new CRM_Utils_Cache_CacheException("Memcached set ($key) failed");
+    }
   }
 
   /**
@@ -141,8 +208,13 @@ class CRM_Utils_Cache_Memcached implements CRM_Utils_Cache_Interface {
    * @return mixed
    */
   public function delete($key) {
+    CRM_Utils_Cache::assertValidKey($key);
     $key = $this->cleanKey($key);
-    return $this->_cache->delete($key);
+    if ($this->_cache->delete($key)) {
+      return TRUE;
+    }
+    $code = $this->_cache->getResultCode();
+    return ($code == Memcached::RES_DELETED || $code == Memcached::RES_NOTFOUND);
   }
 
   /**
@@ -151,20 +223,47 @@ class CRM_Utils_Cache_Memcached implements CRM_Utils_Cache_Interface {
    * @return mixed|string
    */
   public function cleanKey($key) {
-    $key = preg_replace('/\s+|\W+/', '_', $this->_prefix . $key);
-    if (strlen($key) > self::MAX_KEY_LEN) {
+    $truePrefix = $this->getTruePrefix();
+    $maxLen = self::MAX_KEY_LEN - strlen($truePrefix);
+    $key = preg_replace('/\s+|\W+/', '_', $key);
+    if (strlen($key) > $maxLen) {
       $md5Key = md5($key);  // this should be 32 characters in length
-      $subKeyLen = self::MAX_KEY_LEN - 1 - strlen($md5Key);
+      $subKeyLen = $maxLen - 1 - strlen($md5Key);
       $key = substr($key, 0, $subKeyLen) . "_" . $md5Key;
     }
-    return $key;
+    return $truePrefix . $key;
   }
 
   /**
-   * @return mixed
+   * @return bool
    */
   public function flush() {
-    return $this->_cache->flush();
+    $this->_truePrefix = NULL;
+    if ($this->_cache->delete($this->_prefix)) {
+      return TRUE;
+    }
+    $code = $this->_cache->getResultCode();
+    return ($code == Memcached::RES_DELETED || $code == Memcached::RES_NOTFOUND);
+  }
+
+  public function clear() {
+    return $this->flush();
+  }
+
+  protected function getTruePrefix() {
+    if ($this->_truePrefix === NULL || $this->_truePrefix['expires'] < time()) {
+      $key = $this->_prefix;
+      $value = $this->_cache->get($key);
+      if ($this->_cache->getResultCode() === Memcached::RES_NOTFOUND) {
+        $value = uniqid();
+        $this->_cache->add($key, $value, 0); // Indefinite.
+      }
+      $this->_truePrefix = [
+        'value' => $value,
+        'expires' => time() + self::NS_LOCAL_TTL,
+      ];
+    }
+    return $this->_prefix . $this->_truePrefix['value'] . '/';
   }
 
 }
diff --git a/civicrm/CRM/Utils/Cache/NaiveHasTrait.php b/civicrm/CRM/Utils/Cache/NaiveHasTrait.php
new file mode 100644
index 0000000000..78ecc67887
--- /dev/null
+++ b/civicrm/CRM/Utils/Cache/NaiveHasTrait.php
@@ -0,0 +1,49 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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-2018
+ *
+ * The traditional CRM_Utils_Cache_Interface did not support has().
+ * To get drop-in compliance with PSR-16, we use a naive adapter.
+ *
+ * Ideally, these should be replaced with more performant/native versions.
+ */
+trait CRM_Utils_Cache_NaiveHasTrait {
+
+  public function has($key) {
+    // This is crazy-talk. If you've got an environment setup where you might
+    // be investigating this, fix your preferred cache driver by
+    // replacing `NaiveHasTrait` with a decent function.
+    $hasDefaultA = ($this->get($key, NULL) === NULL);
+    $hasDefaultB = ($this->get($key, 123) === 123);
+    return !($hasDefaultA && $hasDefaultB);
+  }
+
+}
diff --git a/civicrm/CRM/Utils/Cache/NaiveMultipleTrait.php b/civicrm/CRM/Utils/Cache/NaiveMultipleTrait.php
new file mode 100644
index 0000000000..d7ead9fc94
--- /dev/null
+++ b/civicrm/CRM/Utils/Cache/NaiveMultipleTrait.php
@@ -0,0 +1,121 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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-2018
+ *
+ * The traditional CRM_Utils_Cache_Interface did not support multiple-key
+ * operations. To get drop-in compliance with PSR-16, we use a naive adapter.
+ * An operation like `getMultiple()` just calls `get()` multiple times.
+ *
+ * Ideally, these should be replaced with more performant/native versions.
+ */
+trait CRM_Utils_Cache_NaiveMultipleTrait {
+
+  /**
+   * Obtains multiple cache items by their unique keys.
+   *
+   * @param iterable $keys A list of keys that can obtained in a single operation.
+   * @param mixed $default Default value to return for keys that do not exist.
+   *
+   * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
+   *
+   * @throws \Psr\SimpleCache\InvalidArgumentException
+   *   MUST be thrown if $keys is neither an array nor a Traversable,
+   *   or if any of the $keys are not a legal value.
+   */
+  public function getMultiple($keys, $default = NULL) {
+    $this->assertIterable('getMultiple', $keys);
+
+    $result = [];
+    foreach ($keys as $key) {
+      $result[$key] = $this->get($key, $default);
+    }
+    return $result;
+  }
+
+  /**
+   * Persists a set of key => value pairs in the cache, with an optional TTL.
+   *
+   * @param iterable $values A list of key => value pairs for a multiple-set operation.
+   * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and
+   *                                       the driver supports TTL then the library may set a default value
+   *                                       for it or let the driver take care of that.
+   *
+   * @return bool True on success and false on failure.
+   *
+   * @throws \Psr\SimpleCache\InvalidArgumentException
+   *   MUST be thrown if $values is neither an array nor a Traversable,
+   *   or if any of the $values are not a legal value.
+   */
+  public function setMultiple($values, $ttl = NULL) {
+    $this->assertIterable('setMultiple', $values);
+
+    $result = TRUE;
+    foreach ($values as $key => $value) {
+      if (is_int($key)) {
+        $key = (string) $key;
+      }
+      $result = $this->set($key, $value, $ttl) || $result;
+    }
+    return $result;
+  }
+
+  /**
+   * Deletes multiple cache items in a single operation.
+   *
+   * @param iterable $keys A list of string-based keys to be deleted.
+   *
+   * @return bool True if the items were successfully removed. False if there was an error.
+   *
+   * @throws \Psr\SimpleCache\InvalidArgumentException
+   *   MUST be thrown if $keys is neither an array nor a Traversable,
+   *   or if any of the $keys are not a legal value.
+   */
+  public function deleteMultiple($keys) {
+    $this->assertIterable('deleteMultiple', $keys);
+
+    $result = TRUE;
+    foreach ($keys as $key) {
+      $result = $this->delete($key) || $result;
+    }
+    return $result;
+  }
+
+  /**
+   * @param $keys
+   * @throws \CRM_Utils_Cache_InvalidArgumentException
+   */
+  private function assertIterable($func, $keys) {
+    if (!is_array($keys) && !($keys instanceof Traversable)) {
+      throw new CRM_Utils_Cache_InvalidArgumentException("$func expects iterable input");
+    }
+  }
+
+}
diff --git a/civicrm/CRM/Utils/Cache/NoCache.php b/civicrm/CRM/Utils/Cache/NoCache.php
index 66c6b74cff..3c1d9b9445 100644
--- a/civicrm/CRM/Utils/Cache/NoCache.php
+++ b/civicrm/CRM/Utils/Cache/NoCache.php
@@ -32,6 +32,9 @@
  */
 class CRM_Utils_Cache_NoCache implements CRM_Utils_Cache_Interface {
 
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+  use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation
+
   /**
    * We only need one instance of this object. So we use the singleton
    * pattern and cache the instance in this variable
@@ -54,20 +57,22 @@ class CRM_Utils_Cache_NoCache implements CRM_Utils_Cache_Interface {
   /**
    * @param string $key
    * @param mixed $value
+   * @param null|int|\DateInterval $ttl
    *
    * @return bool
    */
-  public function set($key, &$value) {
+  public function set($key, $value, $ttl = NULL) {
     return FALSE;
   }
 
   /**
    * @param string $key
+   * @param mixed $default
    *
    * @return null
    */
-  public function get($key) {
-    return NULL;
+  public function get($key, $default = NULL) {
+    return $default;
   }
 
   /**
@@ -86,4 +91,8 @@ class CRM_Utils_Cache_NoCache implements CRM_Utils_Cache_Interface {
     return FALSE;
   }
 
+  public function clear() {
+    return $this->flush();
+  }
+
 }
diff --git a/civicrm/CRM/Utils/Cache/Redis.php b/civicrm/CRM/Utils/Cache/Redis.php
index 07279d530d..3e2f2f7f9b 100644
--- a/civicrm/CRM/Utils/Cache/Redis.php
+++ b/civicrm/CRM/Utils/Cache/Redis.php
@@ -33,6 +33,10 @@
  *
  */
 class CRM_Utils_Cache_Redis implements CRM_Utils_Cache_Interface {
+
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+  use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation
+
   const DEFAULT_HOST    = 'localhost';
   const DEFAULT_PORT    = 6379;
   const DEFAULT_TIMEOUT = 3600;
@@ -105,19 +109,33 @@ class CRM_Utils_Cache_Redis implements CRM_Utils_Cache_Interface {
       echo 'Could not connect to redisd server';
       CRM_Utils_System::civiExit();
     }
-    $this->_cache->auth(CIVICRM_DB_CACHE_PASSWORD);
+    if (CRM_Utils_Constant::value('CIVICRM_DB_CACHE_PASSWORD')) {
+      $this->_cache->auth(CIVICRM_DB_CACHE_PASSWORD);
+    }
   }
 
   /**
    * @param $key
    * @param $value
+   * @param null|int|\DateInterval $ttl
    *
    * @return bool
    * @throws Exception
    */
-  public function set($key, &$value) {
-    if (!$this->_cache->set($this->_prefix . $key, serialize($value), $this->_timeout)) {
-      CRM_Core_Error::fatal("Redis set failed, wondering why?, $key", $value);
+  public function set($key, $value, $ttl = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    if (is_int($ttl) && $ttl <= 0) {
+      return $this->delete($key);
+    }
+    $ttl = CRM_Utils_Date::convertCacheTtl($ttl, self::DEFAULT_TIMEOUT);
+    if (!$this->_cache->setex($this->_prefix . $key, $ttl, serialize($value))) {
+      if (PHP_SAPI === 'cli' || (Civi\Core\Container::isContainerBooted() && CRM_Core_Permission::check('view debug output'))) {
+        throw new CRM_Utils_Cache_CacheException("Redis set ($key) failed: " . $this->_cache->getLastError());
+      }
+      else {
+        Civi::log()->error("Redis set ($key) failed: " . $this->_cache->getLastError());
+        throw new CRM_Utils_Cache_CacheException("Redis set ($key) failed");
+      }
       return FALSE;
     }
     return TRUE;
@@ -125,28 +143,42 @@ class CRM_Utils_Cache_Redis implements CRM_Utils_Cache_Interface {
 
   /**
    * @param $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function get($key) {
+  public function get($key, $default = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
     $result = $this->_cache->get($this->_prefix . $key);
-    return unserialize($result);
+    return ($result === FALSE) ? $default : unserialize($result);
   }
 
   /**
    * @param $key
    *
-   * @return mixed
+   * @return bool
    */
   public function delete($key) {
-    return $this->_cache->delete($this->_prefix . $key);
+    CRM_Utils_Cache::assertValidKey($key);
+    $this->_cache->delete($this->_prefix . $key);
+    return TRUE;
   }
 
   /**
-   * @return mixed
+   * @return bool
    */
   public function flush() {
-    return $this->_cache->flushDB();
+    // FIXME: Ideally, we'd map each prefix to a different 'hash' object in Redis,
+    // and this would be simpler. However, that needs to go in tandem with a
+    // more general rethink of cache expiration/TTL.
+
+    $keys = $this->_cache->keys($this->_prefix . '*');
+    $this->_cache->del($keys);
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
   }
 
 }
diff --git a/civicrm/CRM/Utils/Cache/SerializeCache.php b/civicrm/CRM/Utils/Cache/SerializeCache.php
index 6164694c3e..fdf0eb6d47 100644
--- a/civicrm/CRM/Utils/Cache/SerializeCache.php
+++ b/civicrm/CRM/Utils/Cache/SerializeCache.php
@@ -36,6 +36,9 @@
  */
 class CRM_Utils_Cache_SerializeCache implements CRM_Utils_Cache_Interface {
 
+  use CRM_Utils_Cache_NaiveMultipleTrait;
+  use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation
+
   /**
    * The cache storage container, an array by default, stored in a file under templates
    */
@@ -67,10 +70,15 @@ class CRM_Utils_Cache_SerializeCache implements CRM_Utils_Cache_Interface {
 
   /**
    * @param string $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function get($key) {
+  public function get($key, $default = NULL) {
+    if ($default !== NULL) {
+      throw new \RuntimeException("FIXME: " . __CLASS__ . "::get() only supports NULL default");
+    }
+
     if (array_key_exists($key, $this->_cache)) {
       return $this->_cache[$key];
     }
@@ -85,32 +93,41 @@ class CRM_Utils_Cache_SerializeCache implements CRM_Utils_Cache_Interface {
   /**
    * @param string $key
    * @param mixed $value
+   * @param null|int|\DateInterval $ttl
+   * @return bool
    */
-  public function set($key, &$value) {
+  public function set($key, $value, $ttl = NULL) {
+    if ($ttl !== NULL) {
+      throw new \RuntimeException("FIXME: " . __CLASS__ . "::set() should support non-NULL TTL");
+    }
     if (file_exists($this->fileName($key))) {
-      return;
+      return FALSE; // WTF, write-once cache?!
     }
     $this->_cache[$key] = $value;
-    file_put_contents($this->fileName($key), "<?php //" . serialize($value));
+    $bytes = file_put_contents($this->fileName($key), "<?php //" . serialize($value));
+    return ($bytes !== FALSE);
   }
 
   /**
    * @param string $key
+   * @return bool
    */
   public function delete($key) {
     if (file_exists($this->fileName($key))) {
       unlink($this->fileName($key));
     }
     unset($this->_cache[$key]);
+    return TRUE;
   }
 
   /**
    * @param null $key
+   * @return bool
    */
   public function flush($key = NULL) {
     $prefix = "CRM_";
     if (!$handle = opendir(CIVICRM_TEMPLATE_COMPILEDIR)) {
-      return; // die? Error?
+      return FALSE; // die? Error?
     }
     while (FALSE !== ($entry = readdir($handle))) {
       if (substr($entry, 0, 4) == $prefix) {
@@ -120,6 +137,11 @@ class CRM_Utils_Cache_SerializeCache implements CRM_Utils_Cache_Interface {
     closedir($handle);
     unset($this->_cache);
     $this->_cache = array();
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
   }
 
 }
diff --git a/civicrm/CRM/Utils/Cache/SqlGroup.php b/civicrm/CRM/Utils/Cache/SqlGroup.php
index 6f3f45f792..aa4d15e472 100644
--- a/civicrm/CRM/Utils/Cache/SqlGroup.php
+++ b/civicrm/CRM/Utils/Cache/SqlGroup.php
@@ -38,6 +38,12 @@
  */
 class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface {
 
+  // 6*60*60
+  const DEFAULT_TTL = 21600;
+
+  const TS_FMT = 'Y-m-d H:i:s';
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+
   /**
    * The host name of the memcached server.
    *
@@ -53,7 +59,18 @@ class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface {
   /**
    * @var array in-memory cache to optimize redundant get()s
    */
-  protected $frontCache;
+  protected $valueCache;
+
+  /**
+   * @var array in-memory cache to optimize redundant get()s
+   *   Note: expiresCache[$key]===NULL means cache-miss
+   */
+  protected $expiresCache;
+
+  /**
+   * @var string
+   */
+  protected $table;
 
   /**
    * Constructor.
@@ -68,6 +85,7 @@ class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface {
    * @return \CRM_Utils_Cache_SqlGroup
    */
   public function __construct($config) {
+    $this->table = CRM_Core_DAO_Cache::getTableName();
     if (isset($config['group'])) {
       $this->group = $config['group'];
     }
@@ -80,7 +98,7 @@ class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface {
     else {
       $this->componentID = NULL;
     }
-    $this->frontCache = array();
+    $this->valueCache = array();
     if (CRM_Utils_Array::value('prefetch', $config, TRUE)) {
       $this->prefetch();
     }
@@ -89,22 +107,76 @@ class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface {
   /**
    * @param string $key
    * @param mixed $value
+   * @param null|int|\DateInterval $ttl
+   * @return bool
    */
-  public function set($key, &$value) {
-    CRM_Core_BAO_Cache::setItem($value, $this->group, $key, $this->componentID);
-    $this->frontCache[$key] = $value;
+  public function set($key, $value, $ttl = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+
+    $lock = Civi::lockManager()->acquire("cache.{$this->group}_{$key}._null");
+    if (!$lock->isAcquired()) {
+      throw new \CRM_Utils_Cache_CacheException("SqlGroup: Failed to acquire lock on cache key.");
+    }
+
+    $dataExists = CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM {$this->table} WHERE {$this->where($key)}");
+    $expires = CRM_Utils_Date::convertCacheTtlToExpires($ttl, self::DEFAULT_TTL);
+
+    $dataSerialized = CRM_Core_BAO_Cache::encode($value);
+
+    // This table has a wonky index, so we cannot use REPLACE or
+    // "INSERT ... ON DUPE". Instead, use SELECT+(INSERT|UPDATE).
+    if ($dataExists) {
+      $sql = "UPDATE {$this->table} SET data = %1, created_date = FROM_UNIXTIME(%2), expired_date = FROM_UNIXTIME(%3) WHERE {$this->where($key)}";
+      $args = array(
+        1 => array($dataSerialized, 'String'),
+        2 => array(time(), 'Positive'),
+        3 => array($expires, 'Positive'),
+      );
+      $dao = CRM_Core_DAO::executeQuery($sql, $args, FALSE, NULL, FALSE, FALSE);
+    }
+    else {
+      $sql = "INSERT INTO {$this->table} (group_name,path,data,created_date,expired_date) VALUES (%1,%2,%3,FROM_UNIXTIME(%4),FROM_UNIXTIME(%5))";
+      $args = array(
+        1 => [$this->group, 'String'],
+        2 => [$key, 'String'],
+        3 => [$dataSerialized, 'String'],
+        4 => [time(), 'Positive'],
+        5 => [$expires, 'Positive'],
+      );
+      $dao = CRM_Core_DAO::executeQuery($sql, $args, FALSE, NULL, FALSE, FALSE);
+    }
+
+    $lock->release();
+
+    $dao->free();
+
+    $this->valueCache[$key] = CRM_Core_BAO_Cache::decode($dataSerialized);
+    $this->expiresCache[$key] = $expires;
+    return TRUE;
   }
 
   /**
    * @param string $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function get($key) {
-    if (!array_key_exists($key, $this->frontCache)) {
-      $this->frontCache[$key] = CRM_Core_BAO_Cache::getItem($this->group, $key, $this->componentID);
+  public function get($key, $default = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    if (!isset($this->expiresCache[$key]) || time() >= $this->expiresCache[$key]) {
+      $sql = "SELECT path, data, UNIX_TIMESTAMP(expired_date) as expires FROM {$this->table} WHERE " . $this->where($key);
+      $dao = CRM_Core_DAO::executeQuery($sql);
+      while ($dao->fetch()) {
+        $this->expiresCache[$key] = $dao->expires;
+        $this->valueCache[$key] = CRM_Core_BAO_Cache::decode($dao->data);
+      }
+      $dao->free();
     }
-    return $this->frontCache[$key];
+    return (isset($this->expiresCache[$key]) && time() < $this->expiresCache[$key]) ? $this->reobjectify($this->valueCache[$key]) : $default;
+  }
+
+  private function reobjectify($value) {
+    return is_object($value) ? unserialize(serialize($value)) : $value;
   }
 
   /**
@@ -114,24 +186,60 @@ class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface {
    * @return mixed
    */
   public function getFromFrontCache($key, $default = NULL) {
-    return CRM_Utils_Array::value($key, $this->frontCache, $default);
+    if (isset($this->expiresCache[$key]) && time() < $this->expiresCache[$key] && $this->valueCache[$key]) {
+      return $this->reobjectify($this->valueCache[$key]);
+    }
+    else {
+      return $default;
+    }
+  }
+
+  public function has($key) {
+    $this->get($key);
+    return isset($this->expiresCache[$key]) && time() < $this->expiresCache[$key];
   }
 
   /**
    * @param string $key
+   * @return bool
    */
   public function delete($key) {
-    CRM_Core_BAO_Cache::deleteGroup($this->group, $key);
-    unset($this->frontCache[$key]);
+    CRM_Utils_Cache::assertValidKey($key);
+    CRM_Core_DAO::executeQuery("DELETE FROM {$this->table} WHERE {$this->where($key)}");
+    unset($this->valueCache[$key]);
+    unset($this->expiresCache[$key]);
+    return TRUE;
   }
 
   public function flush() {
-    CRM_Core_BAO_Cache::deleteGroup($this->group);
-    $this->frontCache = array();
+    CRM_Core_DAO::executeQuery("DELETE FROM {$this->table} WHERE {$this->where()}");
+    $this->valueCache = array();
+    $this->expiresCache = array();
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
   }
 
   public function prefetch() {
-    $this->frontCache = CRM_Core_BAO_Cache::getItems($this->group, $this->componentID);
+    $dao = CRM_Core_DAO::executeQuery("SELECT path, data, UNIX_TIMESTAMP(expired_date) AS expires FROM {$this->table} WHERE " . $this->where(NULL));
+    $this->valueCache = array();
+    $this->expiresCache = array();
+    while ($dao->fetch()) {
+      $this->valueCache[$dao->path] = CRM_Core_BAO_Cache::decode($dao->data);
+      $this->expiresCache[$dao->path] = $dao->expires;
+    }
+    $dao->free();
+  }
+
+  protected function where($path = NULL) {
+    $clauses = array();
+    $clauses[] = ('group_name = "' . CRM_Core_DAO::escapeString($this->group) . '"');
+    if ($path) {
+      $clauses[] = ('path = "' . CRM_Core_DAO::escapeString($path) . '"');
+    }
+    return $clauses ? implode(' AND ', $clauses) : '(1)';
   }
 
 }
diff --git a/civicrm/CRM/Utils/Check.php b/civicrm/CRM/Utils/Check.php
index 24fd07062a..57d15a741f 100644
--- a/civicrm/CRM/Utils/Check.php
+++ b/civicrm/CRM/Utils/Check.php
@@ -223,7 +223,7 @@ class CRM_Utils_Check {
       break;
     }
 
-    Civi::settings()->set('systemStatusCheckResult', $maxSeverity);
+    Civi::cache('checks')->set('systemStatusCheckResult', $maxSeverity);
 
     return ($max) ? $maxSeverity : $messages;
   }
diff --git a/civicrm/CRM/Utils/Date.php b/civicrm/CRM/Utils/Date.php
index 3d4570d7e1..74aa2e35ca 100644
--- a/civicrm/CRM/Utils/Date.php
+++ b/civicrm/CRM/Utils/Date.php
@@ -695,6 +695,58 @@ class CRM_Utils_Date {
     return TRUE;
   }
 
+  /**
+   * Translate a TTL to a concrete expiration time.
+   *
+   * @param NULL|int|DateInterval $ttl
+   * @param int $default
+   *   The value to use if $ttl is not specified (NULL).
+   * @return int
+   *   Timestamp (seconds since epoch).
+   * @throws \CRM_Utils_Cache_InvalidArgumentException
+   */
+  public static function convertCacheTtlToExpires($ttl, $default) {
+    if ($ttl === NULL) {
+      $ttl = $default;
+    }
+
+    if (is_int($ttl)) {
+      return time() + $ttl;
+    }
+    elseif ($ttl instanceof DateInterval) {
+      return date_add(new DateTime(), $ttl)->getTimestamp();
+    }
+    else {
+      throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache TTL");
+    }
+  }
+
+  /**
+   * Normalize a TTL.
+   *
+   * @param NULL|int|DateInterval $ttl
+   * @param int $default
+   *   The value to use if $ttl is not specified (NULL).
+   * @return int
+   *   Seconds until expiration.
+   * @throws \CRM_Utils_Cache_InvalidArgumentException
+   */
+  public static function convertCacheTtl($ttl, $default) {
+    if ($ttl === NULL) {
+      return $default;
+    }
+    elseif (is_int($ttl)) {
+      return $ttl;
+    }
+    elseif ($ttl instanceof DateInterval) {
+      return date_add(new DateTime(), $ttl)->getTimestamp() - time();
+    }
+    else {
+      throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache TTL");
+    }
+  }
+
+
   /**
    * @param null $timeStamp
    *
diff --git a/civicrm/CRM/Utils/DeprecatedUtils.php b/civicrm/CRM/Utils/DeprecatedUtils.php
index 00afe4332e..7c726a3ee0 100644
--- a/civicrm/CRM/Utils/DeprecatedUtils.php
+++ b/civicrm/CRM/Utils/DeprecatedUtils.php
@@ -1268,7 +1268,7 @@ function _civicrm_api3_deprecated_contact_check_params(
     // @todo switch to using api version
     // $dupes = civicrm_api3('Contact', 'duplicatecheck', (array('match' => $params, 'dedupe_rule_id' => $dedupeRuleGroupID)));
     // $ids = $dupes['count'] ? implode(',', array_keys($dupes['values'])) : NULL;
-    $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($params, $params['contact_type'], 'Unsupervised', array(), CRM_Utils_Array::value('check_permissions', $params, $dedupeRuleGroupID));
+    $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($params, $params['contact_type'], 'Unsupervised', array(), CRM_Utils_Array::value('check_permissions', $params), $dedupeRuleGroupID);
     if ($ids != NULL) {
       $error = CRM_Core_Error::createError("Found matching contacts: " . implode(',', $ids),
         CRM_Core_Error::DUPLICATE_CONTACT,
diff --git a/civicrm/CRM/Utils/Hook.php b/civicrm/CRM/Utils/Hook.php
index 0ad0debd39..1f75b3ab8d 100644
--- a/civicrm/CRM/Utils/Hook.php
+++ b/civicrm/CRM/Utils/Hook.php
@@ -2418,4 +2418,36 @@ abstract class CRM_Utils_Hook {
     );
   }
 
+  /**
+   * This hook is called before a scheduled job is executed
+   *
+   * @param CRM_Core_DAO_Job $job
+   *   The job to be executed
+   * @param array $params
+   *   The arguments to be given to the job
+   */
+  public static function preJob($job, $params) {
+    return self::singleton()->invoke(array('job', 'params'), $job, $params,
+      self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject,
+      'civicrm_preJob'
+    );
+  }
+
+  /**
+   * This hook is called after a scheduled job is executed
+   *
+   * @param CRM_Core_DAO_Job $job
+   *   The job that was executed
+   * @param array $params
+   *   The arguments given to the job
+   * @param array $result
+   *   The result of the API call, or the thrown exception if any
+   */
+  public static function postJob($job, $params, $result) {
+    return self::singleton()->invoke(array('job', 'params', 'result'), $job, $params, $result,
+      self::$_nullObject, self::$_nullObject, self::$_nullObject,
+      'civicrm_postJob'
+    );
+  }
+
 }
diff --git a/civicrm/CRM/Utils/Migrate/Import.php b/civicrm/CRM/Utils/Migrate/Import.php
index 1736130bf0..dc9e24419a 100644
--- a/civicrm/CRM/Utils/Migrate/Import.php
+++ b/civicrm/CRM/Utils/Migrate/Import.php
@@ -242,6 +242,15 @@ WHERE      v.option_group_id = %1
             elseif (in_array($customGroup->extends, array('Individual', 'Organization', 'Household'))) {
               $valueIDs = $optionValues;
             }
+            elseif (in_array($customGroup->extends, array('Contribution', 'ContributionRecur'))) {
+              $sql = "SELECT id
+                      FROM civicrm_financial_type
+                      WHERE name IN ('{$optValues}')";
+              $dao = &CRM_Core_DAO::executeQuery($sql);
+              while ($dao->fetch()) {
+                $valueIDs[] = $dao->id;
+              }
+            }
             else {
               $sql = "
 SELECT     v.value
diff --git a/civicrm/CRM/Utils/SQL/TempTable.php b/civicrm/CRM/Utils/SQL/TempTable.php
new file mode 100644
index 0000000000..cedc46c816
--- /dev/null
+++ b/civicrm/CRM/Utils/SQL/TempTable.php
@@ -0,0 +1,272 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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-2018
+ *
+ * Table naming rules:
+ *   - MySQL imposes a 64 char limit.
+ *   - All temp tables start with "civicrm_tmp".
+ *   - Durable temp tables: "civicrm_tmp_d_{12}_{32}"
+ *   - Ephemeral temp tables: "civicrm_tmp_e_{12}_{32}"
+ *
+ * To use `TempTable`:
+ *   - Begin by calling `CRM_Utils_SQL_TempTable::build()`.
+ *   - Optionally, describe the table with `setDurable()`, `setCategory()`, `setId()`.
+ *   - Finally, call `getName()` or `createWithQuery()` or `createWithColumns()`.
+ *
+ * Example 1: Just create a table name. You'll be responsible for CREATE/DROP actions.
+ *
+ * $name = CRM_Utils_SQL_TempTable::build()->getName();
+ * $name = CRM_Utils_SQL_TempTable::build()->setDurable()->getName();
+ * $name = CRM_Utils_SQL_TempTable::build()->setCategory('contactstats')->setId($contact['id'])->getName();
+ *
+ * Example 2: Create a temp table using the results of a SELECT query.
+ *
+ * $tmpTbl = CRM_Utils_SQL_TempTable::build()->createWithQuery('SELECT id, display_name FROM civicrm_contact');
+ * $tmpTbl = CRM_Utils_SQL_TempTable::build()->createWithQuery(CRM_Utils_SQL_Select::from('civicrm_contact')->select('display_name'));
+ *
+ * Example 3: Create an empty temp table with list of columns.
+ *
+ * $tmpTbl = CRM_Utils_SQL_TempTable::build()->setDurable()->setUtf8()->createWithColumns('id int(10, name varchar(64)');
+ *
+ * Example 4: Drop a table that you previously created.
+ *
+ * $tmpTbl->drop();
+ *
+ * Example 5: Auto-drop a temp table when $tmpTbl falls out of scope
+ *
+ * $tmpTbl->setAutodrop();
+ *
+ */
+class CRM_Utils_SQL_TempTable {
+
+  const UTF8 = 'DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci';
+  const CATEGORY_LENGTH = 12;
+  const CATEGORY_REGEXP = ';^[a-zA-Z0-9]+$;';
+  const ID_LENGTH = 37; // MAX{64} - CATEGORY_LENGTH{12} - CONST_LENGHTH{15} = 37
+  const ID_REGEXP = ';^[a-zA-Z0-9_]+$;';
+
+  /**
+   * @var bool
+   */
+  protected $durable, $utf8;
+
+  protected $category;
+
+  protected $id;
+
+  protected $autodrop;
+
+  /**
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public static function build() {
+    $t = new CRM_Utils_SQL_TempTable();
+    $t->category = NULL;
+    $t->id = md5(uniqid('', TRUE));
+    // The constant CIVICRM_TEMP_FORCE_DURABLE is for local debugging.
+    $t->durable = CRM_Utils_Constant::value('CIVICRM_TEMP_FORCE_DURABLE', FALSE);
+    // I suspect it would be better to just say utf8=true, but a lot of existing queries don't do the utf8 bit.
+    $t->utf8 = CRM_Utils_Constant::value('CIVICRM_TEMP_FORCE_UTF8', FALSE);
+    $t->autodrop = FALSE;
+    return $t;
+  }
+
+  public function __destruct() {
+    if ($this->autodrop) {
+      $this->drop();
+    }
+  }
+
+  /**
+   * Determine the full table name.
+   *
+   * @return string
+   *   Ex: 'civicrm_tmp_d_foo_abcd1234abcd1234'
+   */
+  public function getName() {
+    $parts = ['civicrm', 'tmp'];
+    $parts[] = ($this->durable ? 'd' : 'e');
+    $parts[] = $this->category ? $this->category : 'dflt';
+    $parts[] = $this->id ? $this->id : 'dflt';
+    return implode('_', $parts);
+  }
+
+  /**
+   * Create the table using results from a SELECT query.
+   *
+   * @param string|CRM_Utils_SQL_Select $selectQuery
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function createWithQuery($selectQuery) {
+    $sql = sprintf('%s %s AS %s',
+      $this->toSQL('CREATE'),
+      $this->utf8 ? self::UTF8 : '',
+      ($selectQuery instanceof CRM_Utils_SQL_Select ? $selectQuery->toSQL() : $selectQuery)
+    );
+    CRM_Core_DAO::executeQuery($sql, array(), TRUE, NULL, TRUE, FALSE);
+    return $this;
+  }
+
+  /**
+   * Create the empty table.
+   *
+   * @parma string $columns
+   *   SQL column listing.
+   *   Ex: 'id int(10), name varchar(64)'.
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function createWithColumns($columns) {
+    $sql = sprintf('%s (%s) %s',
+      $this->toSQL('CREATE'),
+      $columns,
+      $this->utf8 ? self::UTF8 : ''
+    );
+    CRM_Core_DAO::executeQuery($sql, array(), TRUE, NULL, TRUE, FALSE);
+    return $this;
+  }
+
+  /**
+   * Drop the table.
+   *
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function drop() {
+    $sql = $this->toSQL('DROP', 'IF EXISTS');
+    CRM_Core_DAO::executeQuery($sql, array(), TRUE, NULL, TRUE, FALSE);
+    return $this;
+  }
+
+  /**
+   * @param string $action
+   *   Ex: 'CREATE', 'DROP'
+   * @param string|NULL $ifne
+   *   Ex: 'IF EXISTS', 'IF NOT EXISTS'.
+   * @return string
+   *   Ex: 'CREATE TEMPORARY TABLE `civicrm_tmp_e_foo_abcd1234`'
+   *   Ex: 'CREATE TABLE IF NOT EXISTS `civicrm_tmp_d_foo_abcd1234`'
+   */
+  private function toSQL($action, $ifne = NULL) {
+    $parts = [];
+    $parts[] = $action;
+    if (!$this->durable) {
+      $parts[] = 'TEMPORARY';
+    }
+    $parts[] = 'TABLE';
+    if ($ifne) {
+      $parts[] = $ifne;
+    }
+    $parts[] = '`' . $this->getName() . '`';
+    return implode(' ', $parts);
+  }
+
+  /**
+   * @return string|NULL
+   */
+  public function getCategory() {
+    return $this->category;
+  }
+
+  /**
+   * @return string|NULL
+   */
+  public function getId() {
+    return $this->id;
+  }
+
+  /**
+   * @return bool
+   */
+  public function isAutodrop() {
+    return $this->autodrop;
+  }
+
+  /**
+   * @return bool
+   */
+  public function isDurable() {
+    return $this->durable;
+  }
+
+  /**
+   * @return bool
+   */
+  public function isUtf8() {
+    return $this->utf8;
+  }
+
+  /**
+   * @param bool $autodrop
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function setAutodrop($autodrop = TRUE) {
+    $this->autodrop = $autodrop;
+    return $this;
+  }
+
+  /**
+   * @param string|NULL $category
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function setCategory($category) {
+    if ($category && !preg_match(self::CATEGORY_REGEXP, $category) || strlen($category) > self::CATEGORY_LENGTH) {
+      throw new \RuntimeException("Malformed temp table category");
+    }
+    $this->category = $category;
+    return $this;
+  }
+
+  /**
+   * @parma bool $value
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function setDurable($durable = TRUE) {
+    $this->durable = $durable;
+    return $this;
+  }
+
+  /**
+   * @param mixed $id
+   * @return CRM_Utils_SQL_TempTable
+   */
+  public function setId($id) {
+    if ($id && !preg_match(self::ID_REGEXP, $id) || strlen($id) > self::ID_LENGTH) {
+      throw new \RuntimeException("Malformed temp table id");
+    }
+    $this->id = $id;
+    return $this;
+  }
+
+  public function setUtf8($value = TRUE) {
+    $this->utf8 = $value;
+    return $this;
+  }
+
+}
diff --git a/civicrm/CRM/Utils/System.php b/civicrm/CRM/Utils/System.php
index e2d72f5303..fb5bb879ea 100644
--- a/civicrm/CRM/Utils/System.php
+++ b/civicrm/CRM/Utils/System.php
@@ -1416,8 +1416,21 @@ class CRM_Utils_System {
   public static function flushCache() {
     // flush out all cache entries so we can reload new data
     // a bit aggressive, but livable for now
-    $cache = CRM_Utils_Cache::singleton();
-    $cache->flush();
+    CRM_Utils_Cache::singleton()->flush();
+
+    // Traditionally, systems running on memory-backed caches were quite
+    // zealous about destroying *all* memory-backed caches during a flush().
+    // These flushes simulate that legacy behavior. However, they should probably
+    // be removed at some point.
+    $localDrivers = ['CRM_Utils_Cache_Arraycache', 'CRM_Utils_Cache_NoCache'];
+    if (Civi\Core\Container::isContainerBooted()
+      && !in_array(get_class(CRM_Utils_Cache::singleton()), $localDrivers)) {
+      Civi::cache('settings')->flush();
+      Civi::cache('js_strings')->flush();
+      Civi::cache('community_messages')->flush();
+      CRM_Extension_System::singleton()->getCache()->flush();
+      CRM_Cxn_CiviCxnHttp::singleton()->getCache()->flush();
+    }
 
     // also reset the various static memory caches
 
diff --git a/civicrm/Civi.php b/civicrm/Civi.php
index ae5dc6f4b1..8acfdba768 100644
--- a/civicrm/Civi.php
+++ b/civicrm/Civi.php
@@ -26,20 +26,15 @@ class Civi {
   public static $statics = array();
 
   /**
-   * EXPERIMENTAL. Retrieve a named cache instance.
-   *
-   * This interface is flagged as experimental due to political
-   * ambiguity in PHP community -- PHP-FIG has an open but
-   * somewhat controversial draft standard for caching. Based on
-   * the current draft, it's expected that this function could
-   * simultaneously support both CRM_Utils_Cache_Interface and
-   * PSR-6, but that depends on whether PSR-6 changes any more.
+   * Retrieve a named cache instance.
    *
    * @param string $name
    *   The name of the cache. The 'default' cache is biased toward
    *   high-performance caches (eg memcache/redis/apc) when
    *   available and falls back to single-request (static) caching.
    * @return CRM_Utils_Cache_Interface
+   *   NOTE: Beginning in CiviCRM v5.4, the cache instance complies with
+   *   PSR-16 (\Psr\SimpleCache\CacheInterface).
    */
   public static function cache($name = 'default') {
     return \Civi\Core\Container::singleton()->get('cache.' . $name);
diff --git a/civicrm/Civi/Angular/Manager.php b/civicrm/Civi/Angular/Manager.php
index 62d896e2f8..61f36af0ee 100644
--- a/civicrm/Civi/Angular/Manager.php
+++ b/civicrm/Civi/Angular/Manager.php
@@ -246,7 +246,7 @@ class Manager {
    *   Invalid partials configuration.
    */
   public function getPartials($name) {
-    $cacheKey = "angular-partials::$name";
+    $cacheKey = "angular-partials_$name";
     $cacheValue = $this->cache->get($cacheKey);
     if ($cacheValue === NULL) {
       $cacheValue = ChangeSet::applyResourceFilters($this->getChangeSets(), 'partials', $this->getRawPartials($name));
diff --git a/civicrm/Civi/Core/Container.php b/civicrm/Civi/Core/Container.php
index ffe1bfbe3b..82dc0614de 100644
--- a/civicrm/Civi/Core/Container.php
+++ b/civicrm/Civi/Core/Container.php
@@ -65,7 +65,7 @@ class Container {
     // services. Consequently, we assume a minimal service available -- the classloader
     // has been setup, and civicrm.settings.php is loaded, but nothing else works.
 
-    $cacheMode = defined('CIVICRM_CONTAINER_CACHE') ? CIVICRM_CONTAINER_CACHE : 'always';
+    $cacheMode = defined('CIVICRM_CONTAINER_CACHE') ? CIVICRM_CONTAINER_CACHE : 'auto';
 
     // In pre-installation environments, don't bother with caching.
     if (!defined('CIVICRM_TEMPLATE_COMPILEDIR') || !defined('CIVICRM_DSN') || $cacheMode === 'never' || \CRM_Utils_System::isInUpgradeMode()) {
@@ -160,12 +160,18 @@ class Container {
 
     $container->setDefinition('psr_log', new Definition('CRM_Core_Error_Log', array()));
 
-    foreach (array('js_strings', 'community_messages') as $cacheName) {
-      $container->setDefinition("cache.{$cacheName}", new Definition(
+    $basicCaches = array(
+      'js_strings' => 'js_strings',
+      'community_messages' => 'community_messages',
+      'checks' => 'checks',
+      'session' => 'CiviCRM Session',
+    );
+    foreach ($basicCaches as $cacheSvc => $cacheGrp) {
+      $container->setDefinition("cache.{$cacheSvc}", new Definition(
         'CRM_Utils_Cache_Interface',
         array(
           array(
-            'name' => $cacheName,
+            'name' => $cacheGrp,
             'type' => array('*memory*', 'SqlGroup', 'ArrayCache'),
           ),
         )
diff --git a/civicrm/Civi/Core/SettingsManager.php b/civicrm/Civi/Core/SettingsManager.php
index 8147afca98..e56d723b2f 100644
--- a/civicrm/Civi/Core/SettingsManager.php
+++ b/civicrm/Civi/Core/SettingsManager.php
@@ -205,7 +205,7 @@ class SettingsManager {
       return self::getSystemDefaults($entity);
     }
 
-    $cacheKey = 'defaults:' . $entity;
+    $cacheKey = 'defaults_' . $entity;
     $defaults = $this->cache->get($cacheKey);
     if (!is_array($defaults)) {
       $specs = SettingsMetadata::getMetadata(array(
diff --git a/civicrm/ang/crmCaseType.js b/civicrm/ang/crmCaseType.js
index ee9efb9603..12500df615 100644
--- a/civicrm/ang/crmCaseType.js
+++ b/civicrm/ang/crmCaseType.js
@@ -67,6 +67,13 @@
                 limit: 0
               }
             }];
+            reqs.defaultAssigneeTypes = ['OptionValue', 'get', {
+              option_group_id: 'activity_default_assignee',
+              sequential: 1,
+              options: {
+                limit: 0
+              }
+            }];
             reqs.relTypes = ['RelationshipType', 'get', {
               sequential: 1,
               options: {
@@ -230,41 +237,101 @@
   });
 
   crmCaseType.controller('CaseTypeCtrl', function($scope, crmApi, apiCalls) {
-    // CRM_Case_XMLProcessor::REL_TYPE_CNAME
-    var REL_TYPE_CNAME = CRM.crmCaseType.REL_TYPE_CNAME,
+    var REL_TYPE_CNAME, defaultAssigneeDefaultValue, ts;
+
+    (function init () {
+      // CRM_Case_XMLProcessor::REL_TYPE_CNAME
+      REL_TYPE_CNAME = CRM.crmCaseType.REL_TYPE_CNAME;
+
+      ts = $scope.ts = CRM.ts(null);
+      $scope.locks = { caseTypeName: true, activitySetName: true };
+      $scope.workflows = { timeline: 'Timeline', sequence: 'Sequence' };
+      defaultAssigneeDefaultValue = _.find(apiCalls.defaultAssigneeTypes.values, { is_default: '1' }) || {};
+
+      storeApiCallsResults();
+      initCaseType();
+      initCaseTypeDefinition();
+      initSelectedStatuses();
+    })();
+
+    /// Stores the api calls results in the $scope object
+    function storeApiCallsResults() {
+      $scope.activityStatuses = apiCalls.actStatuses.values;
+      $scope.caseStatuses = _.indexBy(apiCalls.caseStatuses.values, 'name');
+      $scope.activityTypes = _.indexBy(apiCalls.actTypes.values, 'name');
+      $scope.activityTypeOptions = _.map(apiCalls.actTypes.values, formatActivityTypeOption);
+      $scope.defaultAssigneeTypes = apiCalls.defaultAssigneeTypes.values;
+      $scope.relationshipTypeOptions = _.map(apiCalls.relTypes.values, function(type) {
+        return {id: type[REL_TYPE_CNAME], text: type.label_b_a};
+      });
+      $scope.defaultRelationshipTypeOptions = getDefaultRelationshipTypeOptions();
+      // stores the default assignee values indexed by their option name:
+      $scope.defaultAssigneeTypeValues = _.chain($scope.defaultAssigneeTypes)
+        .indexBy('name').mapValues('value').value();
+    }
 
-    ts = $scope.ts = CRM.ts(null);
+    /// Returns the default relationship type options. If the relationship is
+    /// bidirectional (Ex: Spouse of) it adds a single option otherwise it adds
+    /// two options representing the relationship type directions
+    /// (Ex: Employee of, Employer is)
+    function getDefaultRelationshipTypeOptions() {
+      return _.transform(apiCalls.relTypes.values, function(result, relType) {
+        var isBidirectionalRelationship = relType.label_a_b === relType.label_b_a;
+
+        result.push({
+          label: relType.label_b_a,
+          value: relType.id + '_b_a'
+        });
 
-    $scope.activityStatuses = apiCalls.actStatuses.values;
-    $scope.caseStatuses = _.indexBy(apiCalls.caseStatuses.values, 'name');
-    $scope.activityTypes = _.indexBy(apiCalls.actTypes.values, 'name');
-    $scope.activityTypeOptions = _.map(apiCalls.actTypes.values, formatActivityTypeOption);
-    $scope.relationshipTypeOptions = _.map(apiCalls.relTypes.values, function(type) {
-      return {id: type[REL_TYPE_CNAME], text: type.label_b_a};
-    });
-    $scope.locks = {caseTypeName: true, activitySetName: true};
+        if (!isBidirectionalRelationship) {
+          result.push({
+            label: relType.label_a_b,
+            value: relType.id + '_a_b'
+          });
+        }
+      }, []);
+    }
 
-    $scope.workflows = {
-      'timeline': 'Timeline',
-      'sequence': 'Sequence'
-    };
+    /// initializes the case type object
+    function initCaseType() {
+      var isNewCaseType = !apiCalls.caseType;
+
+      if (isNewCaseType) {
+        $scope.caseType = _.cloneDeep(newCaseTypeTemplate);
+      } else {
+        $scope.caseType = apiCalls.caseType;
+      }
+    }
 
-    $scope.caseType = apiCalls.caseType ? apiCalls.caseType : _.cloneDeep(newCaseTypeTemplate);
-    $scope.caseType.definition = $scope.caseType.definition || [];
-    $scope.caseType.definition.activityTypes = $scope.caseType.definition.activityTypes || [];
-    $scope.caseType.definition.activitySets = $scope.caseType.definition.activitySets || [];
-    _.each($scope.caseType.definition.activitySets, function (set) {
-      _.each(set.activityTypes, function (type, name) {
-        type.label = $scope.activityTypes[type.name].label;
+    /// initializes the case type definition object
+    function initCaseTypeDefinition() {
+      $scope.caseType.definition = $scope.caseType.definition || [];
+      $scope.caseType.definition.activityTypes = $scope.caseType.definition.activityTypes || [];
+      $scope.caseType.definition.activitySets = $scope.caseType.definition.activitySets || [];
+      $scope.caseType.definition.caseRoles = $scope.caseType.definition.caseRoles || [];
+      $scope.caseType.definition.statuses = $scope.caseType.definition.statuses || [];
+      $scope.caseType.definition.timelineActivityTypes = $scope.caseType.definition.timelineActivityTypes || [];
+
+      _.each($scope.caseType.definition.activitySets, function (set) {
+        _.each(set.activityTypes, function (type, name) {
+          var isDefaultAssigneeTypeUndefined = _.isUndefined(type.default_assignee_type);
+          type.label = $scope.activityTypes[type.name].label;
+
+          if (isDefaultAssigneeTypeUndefined) {
+            type.default_assignee_type = defaultAssigneeDefaultValue.value;
+          }
+        });
       });
-    });
-    $scope.caseType.definition.caseRoles = $scope.caseType.definition.caseRoles || [];
-    $scope.caseType.definition.statuses = $scope.caseType.definition.statuses || [];
+    }
 
-    $scope.selectedStatuses = {};
-    _.each(apiCalls.caseStatuses.values, function (status) {
-      $scope.selectedStatuses[status.name] = !$scope.caseType.definition.statuses.length || $scope.caseType.definition.statuses.indexOf(status.name) > -1;
-    });
+    /// initializes the selected statuses
+    function initSelectedStatuses() {
+      $scope.selectedStatuses = {};
+
+      _.each(apiCalls.caseStatuses.values, function (status) {
+        $scope.selectedStatuses[status.name] = !$scope.caseType.definition.statuses.length || $scope.caseType.definition.statuses.indexOf(status.name) > -1;
+      });
+    }
 
     $scope.addActivitySet = function(workflow) {
       var activitySet = {};
@@ -288,14 +355,28 @@
     }
 
     function addActivityToSet(activitySet, activityTypeName) {
-      activitySet.activityTypes.push({
-        name: activityTypeName,
-        label: $scope.activityTypes[activityTypeName].label,
-        status: 'Scheduled',
-        reference_activity: 'Open Case',
-        reference_offset: '1',
-        reference_select: 'newest'
-      });
+      var activity = {
+          name: activityTypeName,
+          label: $scope.activityTypes[activityTypeName].label,
+          status: 'Scheduled',
+          reference_activity: 'Open Case',
+          reference_offset: '1',
+          reference_select: 'newest',
+          default_assignee_type: $scope.defaultAssigneeTypeValues.NONE
+      };
+      activitySet.activityTypes.push(activity);
+      if(typeof activitySet.timeline !== "undefined" && activitySet.timeline == "1") {
+        $scope.caseType.definition.timelineActivityTypes.push(activity);
+      }
+    }
+
+    function resetTimelineActivityTypes() {
+        $scope.caseType.definition.timelineActivityTypes = [];
+        angular.forEach($scope.caseType.definition.activitySets, function(activitySet) {
+            angular.forEach(activitySet.activityTypes, function(activityType) {
+                $scope.caseType.definition.timelineActivityTypes.push(activityType);
+            });
+        });
     }
 
     function createActivity(name, callback) {
@@ -334,6 +415,12 @@
       }
     };
 
+    /// Clears the activity's default assignee values for relationship and contact
+    $scope.clearActivityDefaultAssigneeValues = function(activity) {
+      activity.default_assignee_relationship = null;
+      activity.default_assignee_contact = null;
+    };
+
     /// Add a new role
     $scope.addRole = function(roles, roleName) {
       var names = _.pluck($scope.caseType.definition.caseRoles, 'name');
@@ -363,6 +450,7 @@
       var idx = _.indexOf(array, item);
       if (idx != -1) {
         array.splice(idx, 1);
+        resetTimelineActivityTypes();
       }
     };
 
@@ -462,6 +550,7 @@
     if (!$scope.isForkable()) {
       CRM.alert(ts('The CiviCase XML file for this case-type prohibits editing the definition.'));
     }
+
   });
 
   crmCaseType.controller('CaseTypeListCtrl', function($scope, crmApi, caseTypes) {
diff --git a/civicrm/ang/crmCaseType/timelineTable.html b/civicrm/ang/crmCaseType/timelineTable.html
index bc38d3ddd5..4d044f1b9d 100644
--- a/civicrm/ang/crmCaseType/timelineTable.html
+++ b/civicrm/ang/crmCaseType/timelineTable.html
@@ -11,6 +11,7 @@ Required vars: activitySet
     <th>{{ts('Reference')}}</th>
     <th>{{ts('Offset')}}</th>
     <th>{{ts('Select')}}</th>
+    <th>{{ts('Default assignee')}}</th>
     <th></th>
   </tr>
   </thead>
@@ -21,13 +22,13 @@ Required vars: activitySet
       <i class="crm-i fa-arrows grip-n-drag"></i>
     </td>
     <td>
-      <i class="crm-i {{ activityTypes[activity.name].icon }}"></i>
-      {{ activity.label }}
+      <i class="crm-i {{activityTypes[activity.name].icon}}"></i>
+      {{activity.label}}
     </td>
     <td>
       <select
         ui-jq="select2"
-        ui-options="{dropdownAutoWidth : true}"
+        ui-options="{dropdownAutoWidth: true}"
         ng-model="activity.status"
         ng-options="actStatus.name as actStatus.label for actStatus in activityStatuses|orderBy:'label'"
         >
@@ -37,9 +38,9 @@ Required vars: activitySet
     <td>
       <select
         ui-jq="select2"
-        ui-options="{dropdownAutoWidth : true}"
+        ui-options="{dropdownAutoWidth: true}"
         ng-model="activity.reference_activity"
-        ng-options="activityType.name as activityType.label for activityType in activitySet.activityTypes"
+        ng-options="activityType.name as activityType.label for activityType in caseType.definition.timelineActivityTypes"
         >
         <option value="">-- Case Start --</option>
       </select>
@@ -55,12 +56,41 @@ Required vars: activitySet
     <td>
       <select
         ui-jq="select2"
-        ui-options="{dropdownAutoWidth : true}"
+        ui-options="{dropdownAutoWidth: true}"
         ng-model="activity.reference_select"
         ng-options="key as value for (key,value) in {newest: ts('Newest'), oldest: ts('Oldest')}"
         >
       </select>
     </td>
+    <td>
+      <select
+        ui-jq="select2"
+        ui-options="{dropdownAutoWidth: true}"
+        ng-model="activity.default_assignee_type"
+        ng-options="option.value as option.label for option in defaultAssigneeTypes"
+        ng-change="clearActivityDefaultAssigneeValues(activity)"
+      ></select>
+
+      <p ng-if="activity.default_assignee_type === defaultAssigneeTypeValues.BY_RELATIONSHIP">
+        <select
+          ui-jq="select2"
+          ui-options="{dropdownAutoWidth: true}"
+          ng-model="activity.default_assignee_relationship"
+          ng-options="option.value as option.label for option in defaultRelationshipTypeOptions"
+          required
+        ></select>
+      </p>
+
+      <p ng-if="activity.default_assignee_type === defaultAssigneeTypeValues.SPECIFIC_CONTACT">
+        <input
+          type="text"
+          ng-model="activity.default_assignee_contact"
+          placeholder="- Select contact -"
+          crm-entityref="{ entity: 'Contact' }"
+          data-create-links="true"
+          required />
+      </p>
+    </td>
     <td>
       <a class="crm-hover-button"
          crm-icon="fa-trash"
@@ -74,7 +104,7 @@ Required vars: activitySet
 
   <tfoot>
   <tr class="addRow">
-    <td colspan="6">
+    <td colspan="8">
       <span crm-add-name=""
            crm-options="activityTypeOptions"
            crm-var="newActivity"
diff --git a/civicrm/ang/crmMailing/BlockPreview.html b/civicrm/ang/crmMailing/BlockPreview.html
index 1addd5fb46..6315466e83 100644
--- a/civicrm/ang/crmMailing/BlockPreview.html
+++ b/civicrm/ang/crmMailing/BlockPreview.html
@@ -37,7 +37,7 @@ Vars: mailing:obj, testContact:obj, testGroup:obj, crmMailing:FormController
         crm-multiple-email
       />
     </div>
-    <button crm-icon="fa-paper-plane" title="{{crmMailing.$invalid || !testContact.email ? ts('Complete all required fields first') : ts('Send test message to %1', {1: testContact.email})}}" ng-disabled="crmMailing.$invalid || !testContact.email" ng-click="doSend({email: testContact.email})">{{ts('Send test')}}</button>
+    <button crm-icon="fa-paper-plane" title="{{crmMailing.$invalid || !testContact.email ? ts('Complete all required fields first') : ts('Send test message to %1', {1: testContact.email})}}" ng-disabled="crmMailing.$invalid || !testContact.email" ng-click="doSend({email: testContact.email})" class="crmMailing-btn-primary">{{ts('Send test')}}</button>
   </div>
   <div class="preview-group" ng-form="">
     <div>
@@ -51,7 +51,7 @@ Vars: mailing:obj, testContact:obj, testGroup:obj, crmMailing:FormController
         class="crm-action-menu fa-envelope-o"
         />
     </div>
-    <button crm-icon="fa-paper-plane" title="{{crmMailing.$invalid || !testGroup.gid ? ts('Complete all required fields first') : ts('Send test message to group')}}" ng-disabled="crmMailing.$invalid || !testGroup.gid" crm-confirm="{resizable: true, width: '40%', height: '40%', open: previewTestGroup}" on-yes="doSend({gid: testGroup.gid})">{{ts('Send test')}}</button>
+    <button crm-icon="fa-paper-plane" title="{{crmMailing.$invalid || !testGroup.gid ? ts('Complete all required fields first') : ts('Send test message to group')}}" ng-disabled="crmMailing.$invalid || !testGroup.gid" crm-confirm="{resizable: true, width: '40%', height: '40%', open: previewTestGroup}" on-yes="doSend({gid: testGroup.gid})" class="crmMailing-btn-primary">{{ts('Send test')}}</button>
   </div>
   <div class="clear"></div>
 </div>
diff --git a/civicrm/ang/crmMailing/EditMailingCtrl/2step.html b/civicrm/ang/crmMailing/EditMailingCtrl/2step.html
index 94752d6d92..cabd6f307c 100644
--- a/civicrm/ang/crmMailing/EditMailingCtrl/2step.html
+++ b/civicrm/ang/crmMailing/EditMailingCtrl/2step.html
@@ -42,7 +42,7 @@
           <div crm-mailing-block-schedule crm-mailing="mailing"></div>
         </div>
         <center>
-          <a class="button crmMailing-submit-button" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
+          <a class="button crmMailing-submit-button crmMailing-btn-primary" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
             <div>{{ts('Submit Mailing')}}</div>
           </a>
         </center>
@@ -52,10 +52,11 @@
         <button
           crm-icon="fa-trash"
           ng-show="checkPerm('delete in CiviMail')"
+          class="crmMailing-btn-danger-outline"
           ng-disabled="block.check()"
           crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
           on-yes="delete()">{{ts('Delete Draft')}}</button>
-        <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
+        <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)" class="crmMailing-btn-secondary-outline">{{ts('Save Draft')}}</button>
       </span>
     </div>
   </div>
diff --git a/civicrm/ang/crmMailing/EditMailingCtrl/unified.html b/civicrm/ang/crmMailing/EditMailingCtrl/unified.html
index 93a8758764..cc3056fa2f 100644
--- a/civicrm/ang/crmMailing/EditMailingCtrl/unified.html
+++ b/civicrm/ang/crmMailing/EditMailingCtrl/unified.html
@@ -37,11 +37,12 @@
       <div crm-mailing-block-schedule crm-mailing="mailing"></div>
     </div>
 
-    <button crm-icon="fa-paper-plane" ng-disabled="block.check() || crmMailingSubform.$invalid" ng-click="submit()">{{ts('Submit Mailing')}}</button>
-    <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
+    <button crm-icon="fa-paper-plane" class="crmMailing-btn-primary" ng-disabled="block.check() || crmMailingSubform.$invalid" ng-click="submit()">{{ts('Submit Mailing')}}</button>
+    <button crm-icon="fa-floppy-o" class="crmMailing-btn-secondary-outline" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
     <button
       crm-icon="fa-trash"
       ng-show="checkPerm('delete in CiviMail')"
+      class="crmMailing-btn-danger-outline"
       ng-disabled="block.check()"
       crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
       on-yes="delete()">{{ts('Delete Draft')}}</button>
diff --git a/civicrm/ang/crmMailing/EditMailingCtrl/unified2.html b/civicrm/ang/crmMailing/EditMailingCtrl/unified2.html
index 04ff30dd06..1506b8d593 100644
--- a/civicrm/ang/crmMailing/EditMailingCtrl/unified2.html
+++ b/civicrm/ang/crmMailing/EditMailingCtrl/unified2.html
@@ -33,11 +33,12 @@
       <div crm-mailing-block-schedule crm-mailing="mailing"></div>
     </div>
 
-    <button crm-icon="fa-paper-plane" ng-disabled="block.check() || crmMailingSubform.$invalid" ng-click="submit()">{{ts('Submit Mailing')}}</button>
-    <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
+    <button crm-icon="fa-paper-plane" class="crmMailing-btn-primary" ng-disabled="block.check() || crmMailingSubform.$invalid" ng-click="submit()">{{ts('Submit Mailing')}}</button>
+    <button crm-icon="fa-floppy-o" class="crmMailing-secondary-outline" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
     <button
       crm-icon="fa-trash"
       ng-show="checkPerm('delete in CiviMail')"
+      class="crmMailing-btn-danger-outline"
       ng-disabled="block.check()"
       crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
       on-yes="delete()">{{ts('Delete Draft')}}</button>
diff --git a/civicrm/ang/crmMailing/EditMailingCtrl/wizard.html b/civicrm/ang/crmMailing/EditMailingCtrl/wizard.html
index ee589fd776..9854cc5c95 100644
--- a/civicrm/ang/crmMailing/EditMailingCtrl/wizard.html
+++ b/civicrm/ang/crmMailing/EditMailingCtrl/wizard.html
@@ -45,7 +45,7 @@
           <div crm-mailing-block-review crm-mailing="mailing" crm-mailing-attachments="attachments"></div>
         </div>
         <center>
-          <a class="button crmMailing-submit-button" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
+          <a class="button crmMailing-submit-button crmMailing-btn-primary" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
             <div>{{ts('Submit Mailing')}}</div>
           </a>
         </center>
@@ -55,10 +55,11 @@
         <button
           crm-icon="fa-trash"
           ng-show="checkPerm('delete in CiviMail')"
+          class="crmMailing-btn-danger-outline"
           ng-disabled="block.check()"
           crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
           on-yes="delete()">{{ts('Delete Draft')}}</button>
-        <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
+        <button crm-icon="fa-floppy-o" class="crmMailing-btn-secondary-outline" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
       </span>
     </div>
   </div>
diff --git a/civicrm/ang/crmMailing/EditMailingCtrl/workflow.html b/civicrm/ang/crmMailing/EditMailingCtrl/workflow.html
index b5ae948c47..affa76d84e 100644
--- a/civicrm/ang/crmMailing/EditMailingCtrl/workflow.html
+++ b/civicrm/ang/crmMailing/EditMailingCtrl/workflow.html
@@ -47,12 +47,12 @@
           <div crm-mailing-block-approve crm-mailing="mailing"></div>
         </div>
         <center ng-if="!checkPerm('approve mailings') && !checkPerm('access CiviMail')">
-          <a class="button crmMailing-submit-button" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
+          <a class="button crmMailing-submit-button crmMailing-btn-primary" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
             <div>{{ts('Submit Mailing')}}</div>
           </a>
         </center>
         <center ng-if="checkPerm('approve mailings') || checkPerm('access CiviMail')">
-          <a class="button crmMailing-submit-button" ng-click="approve('Approved')" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
+          <a class="button crmMailing-submit-button crmMailing-btn-primary" ng-click="approve('Approved')" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
             <div>{{ts('Submit and Approve Mailing')}}</div>
           </a>
         </center>
@@ -62,10 +62,11 @@
         <button
           crm-icon="fa-trash"
           ng-show="checkPerm('delete in CiviMail')"
+          class="crmMailing-btn-danger-outline"
           ng-disabled="block.check()"
           crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
           on-yes="delete()">{{ts('Delete Draft')}}</button>
-        <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
+        <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)" class="crmMailing-btn-secondary-outline">{{ts('Save Draft')}}</button>
       </span>
     </div>
   </div>
diff --git a/civicrm/ang/crmMailing/Recipients.js b/civicrm/ang/crmMailing/Recipients.js
index ceae2db871..759fd4c91a 100644
--- a/civicrm/ang/crmMailing/Recipients.js
+++ b/civicrm/ang/crmMailing/Recipients.js
@@ -229,6 +229,11 @@
                 page_num: rcpAjaxState.page_i,
                 params: filterParams,
               };
+
+              if('civicrm_mailing' === rcpAjaxState.entity) {
+                params["api.MailingRecipients.getcount"] = {};
+              }
+
               return params;
             },
             transport: function(params) {
@@ -246,12 +251,18 @@
             results: function(data) {
               results = {
                 children: $.map(data.values, function(obj) {
-                  return {   id: obj.id + ' ' + rcpAjaxState.entity + ' ' + rcpAjaxState.type,
-                             text: obj.label };
+                  if('civicrm_mailing' === rcpAjaxState.entity) {
+                    return obj["api.MailingRecipients.getcount"] > 0 ? {   id: obj.id + ' ' + rcpAjaxState.entity + ' ' + rcpAjaxState.type,
+                               text: obj.label } : '';
+                  }
+                  else {
+                    return {   id: obj.id + ' ' + rcpAjaxState.entity + ' ' + rcpAjaxState.type,
+                               text: obj.label };
+                  }
                 })
               };
 
-              if (rcpAjaxState.page_i == 1 && data.count) {
+              if (rcpAjaxState.page_i == 1 && data.count && results.children.length > 0) {
                 results.text = ts((rcpAjaxState.type == 'include'? 'Include ' : 'Exclude ') +
                   (rcpAjaxState.entity == 'civicrm_group'? 'Group' : 'Mailing'));
               }
diff --git a/civicrm/ang/crmUi/wizard.html b/civicrm/ang/crmUi/wizard.html
index 539f30113d..116f060dcf 100644
--- a/civicrm/ang/crmUi/wizard.html
+++ b/civicrm/ang/crmUi/wizard.html
@@ -9,7 +9,7 @@
   </ul>
   <div class="crm-wizard-body" ng-transclude></div>
   <div class="crm-wizard-buttons">
-    <button crm-icon="fa-chevron-left" ng-click="crmUiWizardCtrl.previous()" ng-show="!crmUiWizardCtrl.$first()">{{ts('Previous')}}</button>
-    <button crm-icon="fa-chevron-right" title="{{!crmUiWizardCtrl.$validStep() ? ts('Complete all required fields first') : ts('Next step')}}" ng-click="crmUiWizardCtrl.next()" ng-show="!crmUiWizardCtrl.$last()" ng-disabled="!crmUiWizardCtrl.$validStep()">{{ts('Next')}}</button>
+    <button crm-icon="fa-chevron-left" ng-click="crmUiWizardCtrl.previous()" ng-show="!crmUiWizardCtrl.$first()" class="crmUi-btn-primary">{{ts('Previous')}}</button>
+    <button crm-icon="fa-chevron-right" title="{{!crmUiWizardCtrl.$validStep() ? ts('Complete all required fields first') : ts('Next step')}}" ng-click="crmUiWizardCtrl.next()" ng-show="!crmUiWizardCtrl.$last()" ng-disabled="!crmUiWizardCtrl.$validStep()" class="crmUi-btn-primary">{{ts('Next')}}</button>
   </div>
 </div>
diff --git a/civicrm/api/v3/Case.php b/civicrm/api/v3/Case.php
index 0d27319d97..7aa2bdf88f 100644
--- a/civicrm/api/v3/Case.php
+++ b/civicrm/api/v3/Case.php
@@ -39,12 +39,12 @@
  * @param array $params
  *
  * @code
- * //REQUIRED for create:
+ * // REQUIRED for create:
  * 'case_type_id' => int OR
  * 'case_type' => str (provide one or the other)
  * 'contact_id' => int // case client
  * 'subject' => str
- * //REQUIRED for update:
+ * // REQUIRED for update:
  * 'id' => case Id
  *
  * //OPTIONAL
@@ -77,42 +77,44 @@ function civicrm_api3_case_create($params) {
     // Update an existing case
     // FIXME: Some of this logic should move to the BAO object?
     // FIXME: Should we check if case with ID actually exists?
-    if (!isset($params['case_id']) && isset($params['id'])) {
-      $params['case_id'] = $params['id'];
-    }
 
     if (array_key_exists('creator_id', $params)) {
       throw new API_Exception('You cannot update creator id');
     }
 
-    $mergedCaseId = $origContactIds = array();
+    $mergedCaseIds = $origContactIds = array();
 
-    // get original contact id and creator id of case
+    // If a contact ID is specified we need to make sure this is the main contact ID for the case (and update if necessary)
     if (!empty($params['contact_id'])) {
       $origContactIds = CRM_Case_BAO_Case::retrieveContactIdsByCaseId($params['id']);
-      $origContactId = CRM_Utils_Array::first($origContactIds);
-    }
 
-    // FIXME: Refactor as separate method to get contactId
-    if (count($origContactIds) > 1) {
-      // check valid orig contact id
-      if (empty($params['orig_contact_id'])) {
-        throw new API_Exception('Case is linked with more than one contact id. Provide the required params orig_contact_id to be replaced');
+      // Get the original contact ID for the case
+      // FIXME: Refactor as separate method to get contactId
+      if (count($origContactIds) > 1) {
+        // Multiple original contact IDs. Need to specify which one to use as a parameter
+        if (empty($params['orig_contact_id'])) {
+          throw new API_Exception('Case is linked with more than one contact id. Provide the required params orig_contact_id to be replaced');
+        }
+        if (!empty($params['orig_contact_id']) && !in_array($params['orig_contact_id'], $origContactIds)) {
+          throw new API_Exception('Invalid case contact id (orig_contact_id)');
+        }
+        $origContactId = $params['orig_contact_id'];
       }
-      if (!empty($params['orig_contact_id']) && !in_array($params['orig_contact_id'], $origContactIds)) {
-        throw new API_Exception('Invalid case contact id (orig_contact_id)');
+      else {
+        // Only one original contact ID
+        $origContactId = CRM_Utils_Array::first($origContactIds);
       }
-      $origContactId = $params['orig_contact_id'];
-    }
 
-    // check for same contact id for edit Client
-    if (!empty($params['contact_id']) && !in_array($params['contact_id'], $origContactIds)) {
-      $mergedCaseId = CRM_Case_BAO_Case::mergeCases($params['contact_id'], $params['case_id'], $origContactId, NULL, TRUE);
-    }
+      // Get the specified main contact ID for the case
+      $mainContactId = CRM_Utils_Array::first($params['contact_id']);
 
-    // If we merged cases then update the merged case
-    if (!empty($mergedCaseId[0])) {
-      $params['id'] = $mergedCaseId[0];
+      // If the main contact ID is not in the list of original contact IDs for the case we need to change the main contact ID for the case
+      // This means we'll end up with a new case ID
+      if (!in_array($mainContactId, $origContactIds)) {
+        $mergedCaseIds = CRM_Case_BAO_Case::mergeCases($mainContactId, $params['id'], $origContactId, NULL, TRUE);
+        // If we merged cases then the first element will contain the case ID of the merged case - update that one
+        $params['id'] = CRM_Utils_Array::first($mergedCaseIds);
+      }
     }
   }
 
@@ -146,8 +148,11 @@ function civicrm_api3_case_create($params) {
 /**
  * When creating a new case, run the xmlProcessor to get all necessary params/configuration
  *  for the new case, as cases use an xml file to store their configuration.
+ *
  * @param $params
  * @param $caseBAO
+ *
+ * @throws \Exception
  */
 function _civicrm_api3_case_create_xmlProcessor($params, $caseBAO) {
   // Format params for xmlProcessor
@@ -629,7 +634,9 @@ function _civicrm_api3_case_read(&$cases, $options) {
   foreach ($cases as &$case) {
     if (empty($options['return']) || !empty($options['return']['contact_id'])) {
       // Legacy support for client_id - TODO: in apiv4 remove 'client_id'
-      $case['client_id'] = $case['contact_id'] = CRM_Case_BAO_Case::retrieveContactIdsByCaseId($case['id']);
+      // FIXME: Historically we return a 1-based array. Changing that risks breaking API clients that
+      //   have been hardcoded to index "1", instead of the first array index (eg. using reset(), foreach etc)
+      $case['client_id'] = $case['contact_id'] = CRM_Case_BAO_Case::retrieveContactIdsByCaseId($case['id'], NULL, 1);
     }
     if (!empty($options['return']['contacts'])) {
       //get case contacts
@@ -700,10 +707,26 @@ function _civicrm_api3_case_format_params(&$params) {
   _civicrm_api3_custom_format_params($params, $values, 'Case');
   $params = array_merge($params, $values);
 
+  // A single or multiple contact_id (client_id) can be passed as a value or array.
+  // Convert single value to array here to simplify processing in later functions which expect an array.
+  if (isset($params['contact_id'])) {
+    if (!is_array($params['contact_id'])) {
+      $params['contact_id'] = array($params['contact_id']);
+    }
+  }
+
+  // DEPRECATED: case_id - use id parameter instead.
+  if (!isset($params['id']) && isset($params['case_id'])) {
+    $params['id'] = $params['case_id'];
+  }
+
+  // When creating a new case, either case_type_id or case_type must be specified.
   if (empty($params['case_type_id']) && empty($params['case_type'])) {
+    // If both case_type_id and case_type are empty we are updating a case so return here.
     return;
   }
 
+  // We are creating a new case
   // figure out case_type_id from case_type and vice-versa
   $caseTypes = CRM_Case_PseudoConstant::caseType('name', FALSE);
   if (empty($params['case_type_id'])) {
diff --git a/civicrm/api/v3/ContributionRecur.php b/civicrm/api/v3/ContributionRecur.php
index 144e62ad1a..5403ef353d 100644
--- a/civicrm/api/v3/ContributionRecur.php
+++ b/civicrm/api/v3/ContributionRecur.php
@@ -84,7 +84,7 @@ function civicrm_api3_contribution_recur_get($params) {
  */
 function civicrm_api3_contribution_recur_cancel($params) {
   civicrm_api3_verify_one_mandatory($params, NULL, array('id'));
-  return CRM_Contribute_BAO_ContributionRecur::cancelRecurContribution($params['id'], CRM_Core_DAO::$_nullObject) ? civicrm_api3_create_success() : civicrm_api3_create_error(ts('Error while cancelling recurring contribution'));
+  return CRM_Contribute_BAO_ContributionRecur::cancelRecurContribution($params['id']) ? civicrm_api3_create_success() : civicrm_api3_create_error(ts('Error while cancelling recurring contribution'));
 }
 
 /**
diff --git a/civicrm/api/v3/Job.php b/civicrm/api/v3/Job.php
index 7a7e699dc8..ab655369d1 100644
--- a/civicrm/api/v3/Job.php
+++ b/civicrm/api/v3/Job.php
@@ -61,6 +61,41 @@ function civicrm_api3_job_create($params) {
   return _civicrm_api3_basic_create(_civicrm_api3_get_BAO(__FUNCTION__), $params, 'Job');
 }
 
+/**
+ * Adjust metadata for clone spec action.
+ *
+ * @param array $spec
+ */
+function _civicrm_api3_job_clone_spec(&$spec) {
+  $spec['id']['title'] = 'Job ID to clone';
+  $spec['id']['type'] = CRM_Utils_Type::T_INT;
+  $spec['id']['api.required'] = 1;
+  $spec['is_active']['title'] = 'Job is Active?';
+  $spec['is_active']['type'] = CRM_Utils_Type::T_BOOLEAN;
+  $spec['is_active']['api.required'] = 0;
+}
+
+/**
+ * Clone Job.
+ *
+ * @param array $params
+ *
+ * @return array
+ * @throws \API_Exception
+ * @throws \CiviCRM_API3_Exception
+ */
+function civicrm_api3_job_clone($params) {
+  if (empty($params['id'])) {
+    throw new API_Exception("Mandatory key(s) missing from params array: id field is required");
+  }
+  $id = $params['id'];
+  unset($params['id']);
+  $params['last_run'] = 'null';
+  $params['scheduled_run_date'] = 'null';
+  $newJobDAO = CRM_Core_BAO_Job::copy($id, $params);
+  return civicrm_api3('Job', 'get', array('id' => $newJobDAO->id));
+}
+
 /**
  * Retrieve one or more job.
  *
@@ -584,14 +619,15 @@ function civicrm_api3_job_cleanup($params) {
   $session   = CRM_Utils_Array::value('session', $params, TRUE);
   $tempTable = CRM_Utils_Array::value('tempTables', $params, TRUE);
   $jobLog    = CRM_Utils_Array::value('jobLog', $params, TRUE);
+  $expired   = CRM_Utils_Array::value('expiredDbCache', $params, TRUE);
   $prevNext  = CRM_Utils_Array::value('prevNext', $params, TRUE);
   $dbCache   = CRM_Utils_Array::value('dbCache', $params, FALSE);
   $memCache  = CRM_Utils_Array::value('memCache', $params, FALSE);
   $tplCache  = CRM_Utils_Array::value('tplCache', $params, FALSE);
   $wordRplc  = CRM_Utils_Array::value('wordRplc', $params, FALSE);
 
-  if ($session || $tempTable || $prevNext) {
-    CRM_Core_BAO_Cache::cleanup($session, $tempTable, $prevNext);
+  if ($session || $tempTable || $prevNext || $expired) {
+    CRM_Core_BAO_Cache::cleanup($session, $tempTable, $prevNext, $expired);
   }
 
   if ($jobLog) {
diff --git a/civicrm/api/v3/Membership.php b/civicrm/api/v3/Membership.php
index 4f0c657a91..34af5cd09d 100644
--- a/civicrm/api/v3/Membership.php
+++ b/civicrm/api/v3/Membership.php
@@ -101,9 +101,12 @@ function civicrm_api3_membership_create($params) {
   _civicrm_api3_custom_format_params($params, $values, 'Membership');
   $params = array_merge($params, $values);
 
+  // Calculate membership dates
   // Fixme: This code belongs in the BAO
   if (empty($params['id']) || !empty($params['num_terms'])) {
+    // If this is a new membership or we have a specified number of terms calculate membership dates.
     if (empty($params['id'])) {
+      // This is a new membership, calculate the membership dates.
       $calcDates = CRM_Member_BAO_MembershipType::getDatesForMembershipType(
         $params['membership_type_id'],
         CRM_Utils_Array::value('join_date', $params),
@@ -113,6 +116,7 @@ function civicrm_api3_membership_create($params) {
       );
     }
     else {
+      // This is an existing membership, calculate the membership dates after renewal
       $calcDates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType(
         $params['id'],
         NULL,
@@ -128,20 +132,20 @@ function civicrm_api3_membership_create($params) {
   }
 
   // Fixme: This code belongs in the BAO
-  $action = CRM_Core_Action::ADD;
-  // we need user id during add mode
-  $ids = array();
-  if (!empty($params['contact_id'])) {
-    $ids['userId'] = $params['contact_id'];
+  if (empty($params['id'])) {
+    $params['action'] = CRM_Core_Action::ADD;
+    // we need user id during add mode
+    $ids = array();
+    if (!empty($params['contact_id'])) {
+      $ids['userId'] = $params['contact_id'];
+    }
   }
-  //for edit membership id should be present
-  // probably not required now.
-  if (!empty($params['id'])) {
+  else {
+    // edit mode
+    $params['action'] = CRM_Core_Action::UPDATE;
+    // $ids['membership'] is required in CRM_Price_BAO_LineItem::processPriceSet
     $ids['membership'] = $params['id'];
-    $action = CRM_Core_Action::UPDATE;
   }
-  //need to pass action to handle related memberships.
-  $params['action'] = $action;
 
   $membershipBAO = CRM_Member_BAO_Membership::create($params, $ids, TRUE);
 
diff --git a/civicrm/api/v3/ReportTemplate.php b/civicrm/api/v3/ReportTemplate.php
index e121c41146..d13cd54b44 100644
--- a/civicrm/api/v3/ReportTemplate.php
+++ b/civicrm/api/v3/ReportTemplate.php
@@ -113,6 +113,7 @@ function civicrm_api3_report_template_delete($params) {
 function civicrm_api3_report_template_getrows($params) {
   civicrm_api3_verify_one_mandatory($params, NULL, array('report_id', 'instance_id'));
   list($rows, $instance, $metadata) = _civicrm_api3_report_template_getrows($params);
+  $instance->cleanUpTemporaryTables();
   return civicrm_api3_create_success($rows, $params, 'ReportTemplate', 'getrows', CRM_Core_DAO::$_nullObject, $metadata);
 }
 
@@ -187,6 +188,7 @@ function _civicrm_api3_report_template_getrows($params) {
 function civicrm_api3_report_template_getstatistics($params) {
   list($rows, $reportInstance, $metadata) = _civicrm_api3_report_template_getrows($params);
   $stats = $reportInstance->statistics($rows);
+  $reportInstance->cleanUpTemporaryTables();
   return civicrm_api3_create_success($stats, $params, 'ReportTemplate', 'getstatistics', CRM_Core_DAO::$_nullObject, $metadata);
 }
 /**
diff --git a/civicrm/api/v3/examples/Setting/GetFields.php b/civicrm/api/v3/examples/Setting/GetFields.php
index 8a4ccd83d8..32c450e058 100644
--- a/civicrm/api/v3/examples/Setting/GetFields.php
+++ b/civicrm/api/v3/examples/Setting/GetFields.php
@@ -1170,21 +1170,6 @@ function setting_getfields_expectedresult() {
         'description' => '',
         'help_text' => '',
       ),
-      'systemStatusCheckResult' => array(
-        'group_name' => 'CiviCRM Preferences',
-        'group' => 'core',
-        'name' => 'systemStatusCheckResult',
-        'type' => 'Integer',
-        'quick_form_type' => 'Element',
-        'html_type' => 'text',
-        'default' => 0,
-        'add' => '4.7',
-        'title' => 'systemStatusCheckResult',
-        'is_domain' => 1,
-        'is_contact' => 0,
-        'description' => '',
-        'help_text' => '',
-      ),
       'recentItemsMaxCount' => array(
         'group_name' => 'CiviCRM Preferences',
         'group' => 'core',
diff --git a/civicrm/api/v3/utils.php b/civicrm/api/v3/utils.php
index 814d11e0b8..094beb2375 100644
--- a/civicrm/api/v3/utils.php
+++ b/civicrm/api/v3/utils.php
@@ -1977,43 +1977,15 @@ function _civicrm_api_get_custom_fields($entity, &$params) {
     // Regular fields have a 'name' property
     $value['name'] = 'custom_' . $key;
     $value['title'] = $value['label'];
-    $value['type'] = _getStandardTypeFromCustomDataType($value);
+    if ($value['data_type'] == 'Date' && CRM_Utils_Array::value('time_format', $value, 0) > 0) {
+      $value['data_type'] = 'DateTime';
+    }
+    $value['type'] = CRM_Utils_Array::value($value['data_type'], CRM_Core_BAO_CustomField::dataToType());
     $ret['custom_' . $key] = $value;
   }
   return $ret;
 }
 
-/**
- * Translate the custom field data_type attribute into a std 'type'.
- *
- * @param array $value
- *
- * @return int
- */
-function _getStandardTypeFromCustomDataType($value) {
-  $dataType = $value['data_type'];
-  //CRM-15792 - If date custom field contains timeformat change type to DateTime
-  if ($value['data_type'] == 'Date' && isset($value['time_format']) && $value['time_format'] > 0) {
-    $dataType = 'DateTime';
-  }
-  $mapping = array(
-    'String' => CRM_Utils_Type::T_STRING,
-    'Int' => CRM_Utils_Type::T_INT,
-    'Money' => CRM_Utils_Type::T_MONEY,
-    'Memo' => CRM_Utils_Type::T_LONGTEXT,
-    'Float' => CRM_Utils_Type::T_FLOAT,
-    'Date' => CRM_Utils_Type::T_DATE,
-    'DateTime' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
-    'Boolean' => CRM_Utils_Type::T_BOOLEAN,
-    'StateProvince' => CRM_Utils_Type::T_INT,
-    'File' => CRM_Utils_Type::T_STRING,
-    'Link' => CRM_Utils_Type::T_STRING,
-    'ContactReference' => CRM_Utils_Type::T_INT,
-    'Country' => CRM_Utils_Type::T_INT,
-  );
-  return $mapping[$dataType];
-}
-
 
 /**
  * Fill params array with alternate (alias) values where a field has an alias and that is filled & the main field isn't.
diff --git a/civicrm/bower_components/jquery-ui/.bower.json b/civicrm/bower_components/jquery-ui/.bower.json
index 2373c86bc6..69ba102964 100644
--- a/civicrm/bower_components/jquery-ui/.bower.json
+++ b/civicrm/bower_components/jquery-ui/.bower.json
@@ -16,6 +16,6 @@
     "commit": "dec4c50123193d4f7c8ae6cd0bff45478e1ad276"
   },
   "_source": "https://github.com/components/jqueryui.git",
-  "_target": "~1.12",
+  "_target": ">=1.9",
   "_originalSource": "jquery-ui"
 }
\ No newline at end of file
diff --git a/civicrm/civicrm-version.php b/civicrm/civicrm-version.php
index 8a36ebdbb1..7f1c1ef4ef 100644
--- a/civicrm/civicrm-version.php
+++ b/civicrm/civicrm-version.php
@@ -1,7 +1,7 @@
 <?php
 /** @deprecated */
 function civicrmVersion( ) {
-  return array( 'version'  => '5.3.2',
+  return array( 'version'  => '5.4.0',
                 'cms'      => 'Wordpress',
                 'revision' => '' );
 }
diff --git a/civicrm/composer.json b/civicrm/composer.json
index 9d81135dbb..1078ccf3d3 100644
--- a/civicrm/composer.json
+++ b/civicrm/composer.json
@@ -55,7 +55,8 @@
     "pear/Net_SMTP": "1.6.*",
     "pear/Net_socket": "1.0.*",
     "civicrm/civicrm-setup": "~0.2.0",
-    "guzzlehttp/guzzle": "^6.3"
+    "guzzlehttp/guzzle": "^6.3",
+    "psr/simple-cache": "~1.0.1"
   },
   "repositories": [
     {
diff --git a/civicrm/composer.lock b/civicrm/composer.lock
index 0cce99a2a8..8f3e4d2659 100644
--- a/civicrm/composer.lock
+++ b/civicrm/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "9c5441f5ce4c51ed3a8cc326693cd904",
+    "content-hash": "233f9c457d9e7d49a6d96c356e1035f1",
     "packages": [
         {
             "name": "civicrm/civicrm-cxn-rpc",
@@ -1131,6 +1131,54 @@
             ],
             "time": "2012-12-21T11:40:51+00:00"
         },
+        {
+            "name": "psr/simple-cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "time": "2017-10-23T01:57:42+00:00"
+        },
         {
             "name": "sabberworm/php-css-parser",
             "version": "6.0.1",
diff --git a/civicrm/css/searchForm.css b/civicrm/css/searchForm.css
index bbafc5d745..a0f57b283a 100644
--- a/civicrm/css/searchForm.css
+++ b/civicrm/css/searchForm.css
@@ -62,3 +62,32 @@
   color: #41477E;
   font-weight: bold;
 }
+
+.advanced-search-fields {
+  display: grid;
+  grid-template-columns: [col] repeat(3, calc(100% / 3 - 10px));
+  width: 100%;
+}
+
+.advanced-search-fields .search-field {
+  padding: 5px;
+}
+
+.advanced-search-fields .search-field__span-2 {
+  grid-column: col / span 2;
+}
+.advanced-search-fields .search-field__span-3 {
+  grid-column: col / span 3;
+}
+
+.advanced-search-fields .search-field__checkbox {
+  display: flex;
+}
+
+.advanced-search-fields .search-field__checkbox input[type="checkbox"] {
+  order: -1;
+}
+
+.advanced-search-fields .search-field__checkbox label {
+  padding-right: 5px;
+}
diff --git a/civicrm/ext/iatspayments/iATS_4.4.14.diff b/civicrm/ext/iatspayments/iATS_4.4.14.diff
new file mode 100644
index 0000000000..02327db7ad
--- /dev/null
+++ b/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/civicrm/ext/iatspayments/iATS_4.5.8.diff b/civicrm/ext/iatspayments/iATS_4.5.8.diff
new file mode 100644
index 0000000000..48e9b1c5e1
--- /dev/null
+++ b/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/civicrm/js/crm.ajax.js b/civicrm/js/crm.ajax.js
index 9cb5eb601e..e1742da8fd 100644
--- a/civicrm/js/crm.ajax.js
+++ b/civicrm/js/crm.ajax.js
@@ -68,7 +68,7 @@
       url: CRM.url('civicrm/ajax/rest'),
       dataType: 'json',
       data: params,
-      type: params.action.indexOf('get') < 0 ? 'POST' : 'GET'
+      type: params.action.indexOf('get') === 0 ? 'GET' : 'POST'
     });
     if (status) {
       // Default status messages
diff --git a/civicrm/packages/Cache/IntegrationTests/LICENSE b/civicrm/packages/Cache/IntegrationTests/LICENSE
new file mode 100644
index 0000000000..f166f6e87e
--- /dev/null
+++ b/civicrm/packages/Cache/IntegrationTests/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Aaron Scherer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/civicrm/packages/Cache/IntegrationTests/LegacySimpleCacheTest.php b/civicrm/packages/Cache/IntegrationTests/LegacySimpleCacheTest.php
new file mode 100644
index 0000000000..abf59427f0
--- /dev/null
+++ b/civicrm/packages/Cache/IntegrationTests/LegacySimpleCacheTest.php
@@ -0,0 +1,757 @@
+<?php
+
+/*
+ * This file is part of php-cache organization.
+ *
+ * (c) 2015-2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\IntegrationTests;
+
+use PHPUnit_Framework_TestCase as TestCase;
+use Psr\SimpleCache\CacheInterface;
+
+abstract class LegacySimpleCacheTest extends TestCase
+{
+    /**
+     * @type array with functionName => reason.
+     */
+    protected $skippedTests = [];
+
+    /**
+     * @type CacheInterface
+     */
+    protected $cache;
+
+    /**
+     * @return CacheInterface that is used in the tests
+     */
+    abstract public function createSimpleCache();
+
+    protected function setUp()
+    {
+        $this->cache = $this->createSimpleCache();
+    }
+
+    protected function tearDown()
+    {
+        if ($this->cache !== null) {
+            $this->cache->clear();
+        }
+    }
+
+    /**
+     * Data provider for invalid keys.
+     *
+     * @return array
+     */
+    public static function invalidKeys()
+    {
+        return [
+            [''],
+            [true],
+            [false],
+            [null],
+            [2],
+            [2.5],
+            ['{str'],
+            ['rand{'],
+            ['rand{str'],
+            ['rand}str'],
+            ['rand(str'],
+            ['rand)str'],
+            ['rand/str'],
+            ['rand\\str'],
+            ['rand@str'],
+            ['rand:str'],
+            [new \stdClass()],
+            [['array']],
+        ];
+    }
+
+    /**
+     * @return array
+     */
+    public static function invalidTtl()
+    {
+        return [
+            [''],
+            [true],
+            [false],
+            ['abc'],
+            [2.5],
+            [' 1'], // can be casted to a int
+            ['12foo'], // can be casted to a int
+            ['025'], // can be interpreted as hex
+            [new \stdClass()],
+            [['array']],
+        ];
+    }
+
+    /**
+     * Data provider for valid keys.
+     *
+     * @return array
+     */
+    public static function validKeys()
+    {
+        return [
+            ['AbC19_.'],
+            ['1234567890123456789012345678901234567890123456789012345678901234'],
+        ];
+    }
+
+    /**
+     * Data provider for valid data to store.
+     *
+     * @return array
+     */
+    public static function validData()
+    {
+        return [
+            ['AbC19_.'],
+            [4711],
+            [47.11],
+            [true],
+            [null],
+            [['key' => 'value']],
+            [new \stdClass()],
+        ];
+    }
+
+    public function testSet()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $result = $this->cache->set('key', 'value');
+        $this->assertTrue($result, 'set() must return true if success');
+        $this->assertEquals('value', $this->cache->get('key'));
+    }
+
+    public function testSetTtl()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $result = $this->cache->set('key1', 'value', 1);
+        $this->assertTrue($result, 'set() must return true if success');
+        $this->assertEquals('value', $this->cache->get('key1'));
+        sleep(2);
+        $this->assertNull($this->cache->get('key1'), 'Value must expire after ttl.');
+
+        $this->cache->set('key2', 'value', new \DateInterval('PT1S'));
+        $this->assertEquals('value', $this->cache->get('key2'));
+        sleep(2);
+        $this->assertNull($this->cache->get('key2'), 'Value must expire after ttl.');
+    }
+
+    public function testSetExpiredTtl()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->set('key0', 'value');
+        $this->cache->set('key0', 'value', 0);
+        $this->assertNull($this->cache->get('key0'));
+        $this->assertFalse($this->cache->has('key0'));
+
+        $this->cache->set('key1', 'value', -1);
+        $this->assertNull($this->cache->get('key1'));
+        $this->assertFalse($this->cache->has('key1'));
+    }
+
+    public function testGet()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->assertNull($this->cache->get('key'));
+        $this->assertEquals('foo', $this->cache->get('key', 'foo'));
+
+        $this->cache->set('key', 'value');
+        $this->assertEquals('value', $this->cache->get('key', 'foo'));
+    }
+
+    public function testDelete()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->assertTrue($this->cache->delete('key'), 'Deleting a value that does not exist should return true');
+        $this->cache->set('key', 'value');
+        $this->assertTrue($this->cache->delete('key'), 'Delete must return true on success');
+        $this->assertNull($this->cache->get('key'), 'Values must be deleted on delete()');
+    }
+
+    public function testClear()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->assertTrue($this->cache->clear(), 'Clearing an empty cache should return true');
+        $this->cache->set('key', 'value');
+        $this->assertTrue($this->cache->clear(), 'Delete must return true on success');
+        $this->assertNull($this->cache->get('key'), 'Values must be deleted on clear()');
+    }
+
+    public function testSetMultiple()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $result = $this->cache->setMultiple(['key0' => 'value0', 'key1' => 'value1']);
+        $this->assertTrue($result, 'setMultiple() must return true if success');
+        $this->assertEquals('value0', $this->cache->get('key0'));
+        $this->assertEquals('value1', $this->cache->get('key1'));
+    }
+
+    public function testSetMultipleWithIntegerArrayKey()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $result = $this->cache->setMultiple(['0' => 'value0']);
+        $this->assertTrue($result, 'setMultiple() must return true if success');
+        $this->assertEquals('value0', $this->cache->get('0'));
+    }
+
+    public function testSetMultipleTtl()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->setMultiple(['key2' => 'value2', 'key3' => 'value3'], 1);
+        $this->assertEquals('value2', $this->cache->get('key2'));
+        $this->assertEquals('value3', $this->cache->get('key3'));
+        sleep(2);
+        $this->assertNull($this->cache->get('key2'), 'Value must expire after ttl.');
+        $this->assertNull($this->cache->get('key3'), 'Value must expire after ttl.');
+
+        $this->cache->setMultiple(['key4' => 'value4'], new \DateInterval('PT1S'));
+        $this->assertEquals('value4', $this->cache->get('key4'));
+        sleep(2);
+        $this->assertNull($this->cache->get('key4'), 'Value must expire after ttl.');
+    }
+
+    public function testSetMultipleExpiredTtl()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->setMultiple(['key0' => 'value0', 'key1' => 'value1'], 0);
+        $this->assertNull($this->cache->get('key0'));
+        $this->assertNull($this->cache->get('key1'));
+    }
+
+    public function testSetMultipleWithGenerator()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $gen = function () {
+            yield 'key0' => 'value0';
+            yield 'key1' => 'value1';
+        };
+
+        $this->cache->setMultiple($gen());
+        $this->assertEquals('value0', $this->cache->get('key0'));
+        $this->assertEquals('value1', $this->cache->get('key1'));
+    }
+
+    public function testGetMultiple()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $result = $this->cache->getMultiple(['key0', 'key1']);
+        $keys   = [];
+        foreach ($result as $i => $r) {
+            $keys[] = $i;
+            $this->assertNull($r);
+        }
+        sort($keys);
+        $this->assertSame(['key0', 'key1'], $keys);
+
+        $this->cache->set('key3', 'value');
+        $result = $this->cache->getMultiple(['key2', 'key3', 'key4'], 'foo');
+        $keys   = [];
+        foreach ($result as $key => $r) {
+            $keys[] = $key;
+            if ($key === 'key3') {
+                $this->assertEquals('value', $r);
+            } else {
+                $this->assertEquals('foo', $r);
+            }
+        }
+        sort($keys);
+        $this->assertSame(['key2', 'key3', 'key4'], $keys);
+    }
+
+    public function testGetMultipleWithGenerator()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $gen = function () {
+            yield 1 => 'key0';
+            yield 1 => 'key1';
+        };
+
+        $this->cache->set('key0', 'value0');
+        $result = $this->cache->getMultiple($gen());
+        $keys   = [];
+        foreach ($result as $key => $r) {
+            $keys[] = $key;
+            if ($key === 'key0') {
+                $this->assertEquals('value0', $r);
+            } elseif ($key === 'key1') {
+                $this->assertNull($r);
+            } else {
+                $this->assertFalse(true, 'This should not happend');
+            }
+        }
+        sort($keys);
+        $this->assertSame(['key0', 'key1'], $keys);
+        $this->assertEquals('value0', $this->cache->get('key0'));
+        $this->assertNull($this->cache->get('key1'));
+    }
+
+    public function testDeleteMultiple()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->assertTrue($this->cache->deleteMultiple([]), 'Deleting a empty array should return true');
+        $this->assertTrue($this->cache->deleteMultiple(['key']), 'Deleting a value that does not exist should return true');
+
+        $this->cache->set('key0', 'value0');
+        $this->cache->set('key1', 'value1');
+        $this->assertTrue($this->cache->deleteMultiple(['key0', 'key1']), 'Delete must return true on success');
+        $this->assertNull($this->cache->get('key0'), 'Values must be deleted on deleteMultiple()');
+        $this->assertNull($this->cache->get('key1'), 'Values must be deleted on deleteMultiple()');
+    }
+
+    public function testDeleteMultipleGenerator()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $gen = function () {
+            yield 1 => 'key0';
+            yield 1 => 'key1';
+        };
+        $this->cache->set('key0', 'value0');
+        $this->assertTrue($this->cache->deleteMultiple($gen()), 'Deleting a generator should return true');
+
+        $this->assertNull($this->cache->get('key0'), 'Values must be deleted on deleteMultiple()');
+        $this->assertNull($this->cache->get('key1'), 'Values must be deleted on deleteMultiple()');
+    }
+
+    public function testHas()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->assertFalse($this->cache->has('key0'));
+        $this->cache->set('key0', 'value0');
+        $this->assertTrue($this->cache->has('key0'));
+    }
+
+    public function testBasicUsageWithLongKey()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $key = str_repeat('a', 300);
+
+        $this->assertFalse($this->cache->has($key));
+        $this->assertTrue($this->cache->set($key, 'value'));
+
+        $this->assertTrue($this->cache->has($key));
+        $this->assertSame('value', $this->cache->get($key));
+
+        $this->assertTrue($this->cache->delete($key));
+
+        $this->assertFalse($this->cache->has($key));
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     * @dataProvider invalidKeys
+     */
+    public function testGetInvalidKeys($key)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->get($key);
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     * @dataProvider invalidKeys
+     */
+    public function testGetMultipleInvalidKeys($key)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $result = $this->cache->getMultiple(['key1', $key, 'key2']);
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function testGetMultipleNoIterable()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $result = $this->cache->getMultiple('key');
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     * @dataProvider invalidKeys
+     */
+    public function testSetInvalidKeys($key)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->set($key, 'foobar');
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     * @dataProvider invalidKeys
+     */
+    public function testSetMultipleInvalidKeys($key)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        if (is_int($key)) {
+            $this->markTestSkipped('As keys, strings are always casted to ints so they should be accepted');
+        }
+
+        $values = function () use ($key) {
+            yield 'key1' => 'foo';
+            yield $key => 'bar';
+            yield 'key2' => 'baz';
+        };
+        $this->cache->setMultiple($values());
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function testSetMultipleNoIterable()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->setMultiple('key');
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     * @dataProvider invalidKeys
+     */
+    public function testHasInvalidKeys($key)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->has($key);
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     * @dataProvider invalidKeys
+     */
+    public function testDeleteInvalidKeys($key)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->delete($key);
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     * @dataProvider invalidKeys
+     */
+    public function testDeleteMultipleInvalidKeys($key)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->deleteMultiple(['key1', $key, 'key2']);
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function testDeleteMultipleNoIterable()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->deleteMultiple('key');
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     * @dataProvider invalidTtl
+     */
+    public function testSetInvalidTtl($ttl)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->set('key', 'value', $ttl);
+    }
+
+    /**
+     * @expectedException \Psr\SimpleCache\InvalidArgumentException
+     * @dataProvider invalidTtl
+     */
+    public function testSetMultipleInvalidTtl($ttl)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->setMultiple(['key' => 'value'], $ttl);
+    }
+
+    public function testNullOverwrite()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->set('key', 5);
+        $this->cache->set('key', null);
+
+        $this->assertNull($this->cache->get('key'), 'Setting null to a key must overwrite previous value');
+    }
+
+    public function testDataTypeString()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->set('key', '5');
+        $result = $this->cache->get('key');
+        $this->assertTrue('5' === $result, 'Wrong data type. If we store a string we must get an string back.');
+        $this->assertTrue(is_string($result), 'Wrong data type. If we store a string we must get an string back.');
+    }
+
+    public function testDataTypeInteger()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->set('key', 5);
+        $result = $this->cache->get('key');
+        $this->assertTrue(5 === $result, 'Wrong data type. If we store an int we must get an int back.');
+        $this->assertTrue(is_int($result), 'Wrong data type. If we store an int we must get an int back.');
+    }
+
+    public function testDataTypeFloat()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $float = 1.23456789;
+        $this->cache->set('key', $float);
+        $result = $this->cache->get('key');
+        $this->assertTrue(is_float($result), 'Wrong data type. If we store float we must get an float back.');
+        $this->assertEquals($float, $result);
+    }
+
+    public function testDataTypeBoolean()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->set('key', false);
+        $result = $this->cache->get('key');
+        $this->assertTrue(is_bool($result), 'Wrong data type. If we store boolean we must get an boolean back.');
+        $this->assertFalse($result);
+        $this->assertTrue($this->cache->has('key'), 'has() should return true when true are stored. ');
+    }
+
+    public function testDataTypeArray()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $array = ['a' => 'foo', 2 => 'bar'];
+        $this->cache->set('key', $array);
+        $result = $this->cache->get('key');
+        $this->assertTrue(is_array($result), 'Wrong data type. If we store array we must get an array back.');
+        $this->assertEquals($array, $result);
+    }
+
+    public function testDataTypeObject()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $object    = new \stdClass();
+        $object->a = 'foo';
+        $this->cache->set('key', $object);
+        $result = $this->cache->get('key');
+        $this->assertTrue(is_object($result), 'Wrong data type. If we store object we must get an object back.');
+        $this->assertEquals($object, $result);
+    }
+
+    public function testBinaryData()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $data = '';
+        for ($i = 0; $i < 256; $i++) {
+            $data .= chr($i);
+        }
+
+        $array = ['a' => 'foo', 2 => 'bar'];
+        $this->cache->set('key', $data);
+        $result = $this->cache->get('key');
+        $this->assertTrue($data === $result, 'Binary data must survive a round trip.');
+    }
+
+    /**
+     * @dataProvider validKeys
+     */
+    public function testSetValidKeys($key)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->set($key, 'foobar');
+        $this->assertEquals('foobar', $this->cache->get($key));
+    }
+
+    /**
+     * @dataProvider validKeys
+     */
+    public function testSetMultipleValidKeys($key)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->setMultiple([$key => 'foobar']);
+        $result = $this->cache->getMultiple([$key]);
+        $keys   = [];
+        foreach ($result as $i => $r) {
+            $keys[] = $i;
+            $this->assertEquals($key, $i);
+            $this->assertEquals('foobar', $r);
+        }
+        $this->assertSame([$key], $keys);
+    }
+
+    /**
+     * @dataProvider validData
+     */
+    public function testSetValidData($data)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->set('key', $data);
+        $this->assertEquals($data, $this->cache->get('key'));
+    }
+
+    /**
+     * @dataProvider validData
+     */
+    public function testSetMultipleValidData($data)
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $this->cache->setMultiple(['key' => $data]);
+        $result = $this->cache->getMultiple(['key']);
+        $keys   = [];
+        foreach ($result as $i => $r) {
+            $keys[] = $i;
+            $this->assertEquals($data, $r);
+        }
+        $this->assertSame(['key'], $keys);
+    }
+
+    public function testObjectAsDefaultValue()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $obj      = new \stdClass();
+        $obj->foo = 'value';
+        $this->assertEquals($obj, $this->cache->get('key', $obj));
+    }
+
+    public function testObjectDoesNotChangeInCache()
+    {
+        if (isset($this->skippedTests[__FUNCTION__])) {
+            $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+        }
+
+        $obj      = new \stdClass();
+        $obj->foo = 'value';
+        $this->cache->set('key', $obj);
+        $obj->foo = 'changed';
+
+        $cacheObject = $this->cache->get('key');
+        $this->assertEquals('value', $cacheObject->foo, 'Object in cache should not have their values changed.');
+    }
+}
diff --git a/civicrm/packages/Cache/IntegrationTests/README.md b/civicrm/packages/Cache/IntegrationTests/README.md
new file mode 100644
index 0000000000..8b6b87a345
--- /dev/null
+++ b/civicrm/packages/Cache/IntegrationTests/README.md
@@ -0,0 +1,7 @@
+This is a fork of the unit-test from https://github.com/php-cache/integration-tests/ which provides support for older
+versions of PHPUnit. It merely:
+
+* Changes the base-class to `PHPUnit_Framework_TestCase`.
+* Changes the name to avoid collsions (`Cache\IntegrationTests\LegacySimpleCacheTest`).
+
+This class is only used for testing -- it is not required at runtime.
diff --git a/civicrm/packages/DB.php b/civicrm/packages/DB.php
index 2a06764f95..30dd48b380 100644
--- a/civicrm/packages/DB.php
+++ b/civicrm/packages/DB.php
@@ -790,7 +790,7 @@ class DB
             $parsed['dbsyntax'] = $str;
         }
 
-        if (!count($dsn)) {
+        if (empty($dsn)) {
             return $parsed;
         }
 
diff --git a/civicrm/packages/DB/common.php b/civicrm/packages/DB/common.php
index afa8359ba9..ee50c0880d 100644
--- a/civicrm/packages/DB/common.php
+++ b/civicrm/packages/DB/common.php
@@ -1342,7 +1342,8 @@ class DB_common extends PEAR
             }
         }
         // modifyLimitQuery() would be nice here, but it causes BC issues
-        if (sizeof($params) > 0) {
+        $params = (array) $params;
+        if (count($params) > 0) {
             $sth = $this->prepare($query);
             if (DB::isError($sth)) {
                 return $sth;
@@ -1650,7 +1651,8 @@ class DB_common extends PEAR
             }
         }
 
-        if (sizeof($params) > 0) {
+        $params = (array) $params;
+        if (count($params) > 0) {
             $sth = $this->prepare($query);
 
             if (DB::isError($sth)) {
diff --git a/civicrm/packages/HTML/QuickForm/date.php b/civicrm/packages/HTML/QuickForm/date.php
index 040c800f37..9e816e680d 100644
--- a/civicrm/packages/HTML/QuickForm/date.php
+++ b/civicrm/packages/HTML/QuickForm/date.php
@@ -159,40 +159,48 @@ class HTML_QuickForm_date extends HTML_QuickForm_group
                 $separator .= $sign;
             } else {
                 $loadSelect = true;
+                $ariaLabel = $this->getLabel() ?: '';
                 switch ($sign) {
                     case 'D':
                         // Sunday is 0 like with 'w' in date()
                         $options = $locale['weekdays_short'];
                         $emptyText = ts('-day of week-');
+                        $ariaLabel .= ts(' day of week');
                         break;
                     case 'l':
                         $options = $locale['weekdays_long'];
                         $emptyText = ts('-day of week-');
+                        $ariaLabel .= ts(' day of week');
                         break;
                     case 'd':
                         $options = $this->_createOptionList(1, 31);
                         $emptyText = ts('-day-');
+                        $ariaLabel .= ts(' day');
                         break;
                     case 'j':
                         // the no-zero-padding option (CRM-2793)
                         $options = $this->_createOptionList(1, 31, 1, false);
                         $emptyText = ts('-day-');
+                        $ariaLabel .= ts(' day');
                         break;
                     case 'M':
                         $options = $locale['months_short'];
                         array_unshift($options , '');
                         unset($options[0]);
                         $emptyText = ts('-month-');
+                        $ariaLabel .= ts(' month');
                         break;
                     case 'm':
                         $options = $this->_createOptionList(1, 12);
                         $emptyText = ts('-month-');
+                        $ariaLabel .= ts(' month');
                         break;
                     case 'F':
                         $options = $locale['months_long'];
                         array_unshift($options , '');
                         unset($options[0]);
                         $emptyText = ts('-month-');
+                        $ariaLabel .= ts(' month');
                         break;
                     case 'Y':
                         $options = $this->_createOptionList(
@@ -201,6 +209,7 @@ class HTML_QuickForm_date extends HTML_QuickForm_group
                             $this->_options['minYear'] > $this->_options['maxYear']? -1: 1
                         );
                         $emptyText = ts('-year-');
+                        $ariaLabel .= ts(' year');
                         break;
                     case 'y':
                         $options = $this->_createOptionList(
@@ -210,10 +219,12 @@ class HTML_QuickForm_date extends HTML_QuickForm_group
                         );
                         array_walk($options, create_function('&$v,$k','$v = substr($v,-2);'));
                         $emptyText = ts('-year-');
+                        $ariaLabel .= ts(' year');
                         break;
                     case 'h':
                         $options = $this->_createOptionList(1, 12);
                         $emptyText = ts('-hour-');
+                        $ariaLabel .= ts(' hour');
                         break;
                     case 'g':
                         $options = $this->_createOptionList(1, 12);
@@ -222,22 +233,27 @@ class HTML_QuickForm_date extends HTML_QuickForm_group
                     case 'H':
                         $options = $this->_createOptionList(0, 23);
                         $emptyText = ts('-hour-');
+                        $ariaLabel .= ts(' hour');
                         break;
                     case 'i':
                         $options = $this->_createOptionList(0, 59, $this->_options['optionIncrement']['i']);
                         $emptyText = ts('-min-');
+                        $ariaLabel .= ts(' minute');
                         break;
                     case 's':
                         $options = $this->_createOptionList(0, 59, $this->_options['optionIncrement']['s']);
                         $emptyText = ts('-sec-');
+                        $ariaLabel .= ts(' second');
                         break;
                     case 'a':
                         $options = array('am' => 'am', 'pm' => 'pm');
                         $emptyText = '-am/pm-';
+                        $ariaLabel .= ts(' am or pm');
                         break;
                     case 'A':
                         $options = array('AM' => 'AM', 'PM' => 'PM');
                         $emptyText = '-AM/PM-';
+                        $ariaLabel .= ts(' AM or PM');
                         break;
                     case 'W':
                         $options = $this->_createOptionList(1, 53);
@@ -276,6 +292,9 @@ class HTML_QuickForm_date extends HTML_QuickForm_group
                     $attribs = $this->getAttributes();
                     $elementName = $this->getName();
                     $attribs['id'] = $elementName.'['.$sign.']';
+                    if ($ariaLabel !== '') {
+                      $attribs['aria-label'] = $ariaLabel;
+                    }
 
                     $this->_elements[] = new HTML_QuickForm_select($sign, null, $options, $attribs);
                 }
diff --git a/civicrm/packages/Mail/mime.php b/civicrm/packages/Mail/mime.php
index ca815231c5..8ed49b1087 100644
--- a/civicrm/packages/Mail/mime.php
+++ b/civicrm/packages/Mail/mime.php
@@ -1,4 +1,5 @@
 <?php
+
 /**
  * The Mail_Mime class is used to create MIME E-mail messages
  *
@@ -7,7 +8,7 @@
  * contain plain-text bodies, HTML bodies, attachments, inline
  * images and specific headers.
  *
- * Compatible with PHP versions 4 and 5
+ * Compatible with PHP version 5 and 7
  *
  * LICENSE: This LICENSE is in the BSD license style.
  * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
@@ -23,8 +24,8 @@
  * - Redistributions in binary form must reproduce the above copyright
  *   notice, this list of conditions and the following disclaimer in the
  *   documentation and/or other materials provided with the distribution.
- * - Neither the name of the authors, nor the names of its contributors 
- *   may be used to endorse or promote products derived from this 
+ * - Neither the name of the authors, nor the names of its contributors
+ *   may be used to endorse or promote products derived from this
  *   software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
@@ -48,7 +49,7 @@
  * @author    Aleksander Machniak <alec@php.net>
  * @copyright 2003-2006 PEAR <pear-group@php.net>
  * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version   CVS: $Id: mime.php 300942 2010-07-02 12:27:56Z alec $
+ * @version   Release: @package_version@
  * @link      http://pear.php.net/package/Mail_mime
  *
  *            This class is based on HTML Mime Mail class from
@@ -58,20 +59,7 @@
  */
 
 
-/**
- * require PEAR
- *
- * This package depends on PEAR to raise errors.
- */
 require_once 'PEAR.php';
-
-/**
- * require Mail_mimePart
- *
- * Mail_mimePart contains the code required to
- * create all the different parts a mail can
- * consist of.
- */
 require_once 'Mail/mimePart.php';
 
 
@@ -98,49 +86,50 @@ class Mail_mime
      * Contains the plain text part of the email
      *
      * @var string
-     * @access private
      */
-    var $_txtbody;
+    protected $txtbody;
 
     /**
      * Contains the html part of the email
      *
      * @var string
-     * @access private
      */
-    var $_htmlbody;
+    protected $htmlbody;
 
     /**
-     * list of the attached images
+     * Contains the text/calendar part of the email
+     *
+     * @var string
+     */
+    protected $calbody;
+
+    /**
+     * List of the attached images
      *
      * @var array
-     * @access private
      */
-    var $_html_images = array();
+    protected $html_images = array();
 
     /**
-     * list of the attachements
+     * List of the attachements
      *
      * @var array
-     * @access private
      */
-    var $_parts = array();
+    protected $parts = array();
 
     /**
      * Headers for the mail
      *
      * @var array
-     * @access private
      */
-    var $_headers = array();
+    protected $headers = array();
 
     /**
      * Build parameters
      *
      * @var array
-     * @access private
      */
-    var $_build_params = array(
+    protected $build_params = array(
         // What encoding to use for the headers
         // Options: quoted-printable or base64
         'head_encoding' => 'quoted-printable',
@@ -150,18 +139,28 @@ class Mail_mime
         // What encoding to use for html
         // Options: 7bit, 8bit, base64, or quoted-printable
         'html_encoding' => 'quoted-printable',
+        // What encoding to use for calendar part
+        // Options: 7bit, 8bit, base64, or quoted-printable
+        'calendar_encoding' => 'quoted-printable',
         // The character set to use for html
         'html_charset'  => 'ISO-8859-1',
         // The character set to use for text
         'text_charset'  => 'ISO-8859-1',
+        // The character set to use for calendar part
+        'calendar_charset'  => 'UTF-8',
         // The character set to use for headers
         'head_charset'  => 'ISO-8859-1',
         // End-of-line sequence
         'eol'           => "\r\n",
         // Delay attachment files IO until building the message
-        'delay_file_io' => false
+        'delay_file_io' => false,
+        // Default calendar method
+        'calendar_method' => 'request',
+        // multipart part preamble (RFC2046 5.1.1)
+        'preamble' => '',
     );
 
+
     /**
      * Constructor function
      *
@@ -170,22 +169,19 @@ class Mail_mime
      *                      See $_build_params.
      *
      * @return void
-     * @access public
      */
-    function __construct($params = array())
+    public function __construct($params = array())
     {
         // Backward-compatible EOL setting
         if (is_string($params)) {
-            $this->_build_params['eol'] = $params;
+            $this->build_params['eol'] = $params;
         } else if (defined('MAIL_MIME_CRLF') && !isset($params['eol'])) {
-            $this->_build_params['eol'] = MAIL_MIME_CRLF;
+            $this->build_params['eol'] = MAIL_MIME_CRLF;
         }
 
         // Update build parameters
         if (!empty($params) && is_array($params)) {
-            while (list($key, $value) = each($params)) {
-                $this->_build_params[$key] = $value;
-            }
+            $this->build_params = array_merge($this->build_params, $params);
         }
     }
 
@@ -196,12 +192,11 @@ class Mail_mime
      * @param string $value Parameter value
      *
      * @return void
-     * @access public
-     * @since 1.6.0
+     * @since  1.6.0
      */
-    function setParam($name, $value)
+    public function setParam($name, $value)
     {
-        $this->_build_params[$name] = $value;
+        $this->build_params[$name] = $value;
     }
 
     /**
@@ -210,12 +205,11 @@ class Mail_mime
      * @param string $name Parameter name
      *
      * @return mixed Parameter value
-     * @access public
-     * @since 1.6.0
+     * @since  1.6.0
      */
-    function getParam($name)
+    public function getParam($name)
     {
-        return isset($this->_build_params[$name]) ? $this->_build_params[$name] : null;
+        return isset($this->build_params[$name]) ? $this->build_params[$name] : null;
     }
 
     /**
@@ -224,113 +218,116 @@ class Mail_mime
      * text/plain part that emails clients who don't support
      * html should show.
      *
-     * @param string $data   Either a string or
-     *                       the file name with the contents
+     * @param string $data   Either a string or the file name with the contents
      * @param bool   $isfile If true the first param should be treated
      *                       as a file name, else as a string (default)
      * @param bool   $append If true the text or file is appended to
      *                       the existing body, else the old body is
      *                       overwritten
      *
-     * @return mixed         True on success or PEAR_Error object
-     * @access public
+     * @return mixed True on success or PEAR_Error object
      */
-    function setTXTBody($data, $isfile = false, $append = false)
+    public function setTXTBody($data, $isfile = false, $append = false)
     {
-        if (!$isfile) {
-            if (!$append) {
-                $this->_txtbody = $data;
-            } else {
-                $this->_txtbody .= $data;
-            }
-        } else {
-            $cont = $this->_file2str($data);
-            if (PEAR::isError($cont)) {
-                return $cont;
-            }
-            if (!$append) {
-                $this->_txtbody = $cont;
-            } else {
-                $this->_txtbody .= $cont;
-            }
-        }
-
-        // wordwrap the txtbody to be 750 characters to comply with RFC 2821
-        // CRM-3133
-        $this->_txtbody = wordwrap( $this->_txtbody, 750 );
-        return true;
+        return $this->setBody('txtbody', $data, $isfile, $append);
     }
 
     /**
      * Get message text body
      *
      * @return string Text body
-     * @access public
-     * @since 1.6.0
+     * @since  1.6.0
      */
-    function getTXTBody()
+    public function getTXTBody()
     {
-        return $this->_txtbody;
+        return $this->txtbody;
     }
 
     /**
      * Adds a html part to the mail.
      *
-     * @param string $data   Either a string or the file name with the
-     *                       contents
+     * @param string $data   Either a string or the file name with the contents
      * @param bool   $isfile A flag that determines whether $data is a
      *                       filename, or a string(false, default)
      *
-     * @return bool          True on success
-     * @access public
+     * @return bool True on success or PEAR_Error object
      */
-    function setHTMLBody($data, $isfile = false)
+    public function setHTMLBody($data, $isfile = false)
     {
-        if (!$isfile) {
-            $this->_htmlbody = $data;
-        } else {
-            $cont = $this->_file2str($data);
-            if (PEAR::isError($cont)) {
-                return $cont;
-            }
-            $this->_htmlbody = $cont;
-        }
-
-        // wordwrap the htmlbody to be 750 characters to comply with RFC 2821
-        // CRM-3133
-        $this->_htmlbody = wordwrap( $this->_htmlbody, 750 );
-        return true;
+        return $this->setBody('htmlbody', $data, $isfile);
     }
 
     /**
      * Get message HTML body
      *
      * @return string HTML body
-     * @access public
-     * @since 1.6.0
+     * @since  1.6.0
+     */
+    public function getHTMLBody()
+    {
+        return $this->htmlbody;
+    }
+
+    /**
+     * Function to set a body of text/calendar part (not attachment)
+     *
+     * @param string $data     Either a string or the file name with the contents
+     * @param bool   $isfile   If true the first param should be treated
+     *                         as a file name, else as a string (default)
+     * @param bool   $append   If true the text or file is appended to
+     *                         the existing body, else the old body is
+     *                         overwritten
+     * @param string $method   iCalendar object method
+     * @param string $charset  iCalendar character set
+     * @param string $encoding Transfer encoding
+     *
+     * @return mixed True on success or PEAR_Error object
+     * @since  1.9.0
+     */
+    public function setCalendarBody($data, $isfile = false, $append = false,
+        $method = 'request', $charset = 'UTF-8', $encoding = 'quoted-printable'
+    ) {
+        $result = $this->setBody('calbody', $data, $isfile, $append);
+
+        if ($result === true) {
+            $this->build_params['calendar_method']  = $method;
+            $this->build_params['calendar_charset'] = $charset;
+            $this->build_params['calendar_encoding'] = $encoding;
+        }
+    }
+
+    /**
+     * Get body of calendar part
+     *
+     * @return string Calendar part body
+     * @since  1.9.0
      */
-    function getHTMLBody()
+    public function getCalendarBody()
     {
-        return $this->_htmlbody;
+        return $this->calbody;
     }
 
     /**
      * Adds an image to the list of embedded images.
+     * Images added this way will be added as related parts of the HTML message.
+     *
+     * To correctly match the HTML image with the related attachment
+     * HTML should refer to it by a filename (specified in $file or $name
+     * arguments) or by cid:<content-id> (specified in $content_id arg).
      *
      * @param string $file       The image file name OR image data itself
      * @param string $c_type     The content type
-     * @param string $name       The filename of the image.
-     *                           Only used if $file is the image data.
+     * @param string $name       The filename of the image. Used to find
+     *                           the image in HTML content.
      * @param bool   $isfile     Whether $file is a filename or not.
      *                           Defaults to true
      * @param string $content_id Desired Content-ID of MIME part
      *                           Defaults to generated unique ID
      *
-     * @return bool          True on success
-     * @access public
+     * @return bool True on success
      */
-    function addHTMLImage($file,
-        $c_type='application/octet-stream',
+    public function addHTMLImage($file,
+        $c_type = 'application/octet-stream',
         $name = '',
         $isfile = true,
         $content_id = null
@@ -339,25 +336,26 @@ class Mail_mime
 
         if ($isfile) {
             // Don't load file into memory
-            if ($this->_build_params['delay_file_io']) {
+            if ($this->build_params['delay_file_io']) {
                 $filedata = null;
                 $bodyfile = $file;
             } else {
-                if (PEAR::isError($filedata = $this->_file2str($file))) {
+                if (self::isError($filedata = $this->file2str($file))) {
                     return $filedata;
                 }
             }
-            $filename = ($name ? $name : $file);
+
+            $filename = $name ? $name : $file;
         } else {
             $filedata = $file;
             $filename = $name;
         }
 
         if (!$content_id) {
-            $content_id = md5(uniqid(time()));
+            $content_id = preg_replace('/[^0-9a-zA-Z]/', '', uniqid(time(), true));
         }
 
-        $this->_html_images[] = array(
+        $this->html_images[] = array(
             'body'      => $filedata,
             'body_file' => $bodyfile,
             'name'      => $filename,
@@ -371,36 +369,35 @@ class Mail_mime
     /**
      * Adds a file to the list of attachments.
      *
-     * @param string $file        The file name of the file to attach
-     *                            OR the file contents itself
+     * @param mixed  $file        The file name or the file contents itself,
+     *                            it can be also Mail_mimePart object
      * @param string $c_type      The content type
      * @param string $name        The filename of the attachment
      *                            Only use if $file is the contents
-     * @param bool   $isfile      Whether $file is a filename or not
-     *                            Defaults to true
-     * @param string $encoding    The type of encoding to use.
-     *                            Defaults to base64.
-     *                            Possible values: 7bit, 8bit, base64, 
-     *                            or quoted-printable.
+     * @param bool   $isfile      Whether $file is a filename or not. Defaults to true
+     * @param string $encoding    The type of encoding to use. Defaults to base64.
+     *                            Possible values: 7bit, 8bit, base64 or quoted-printable.
      * @param string $disposition The content-disposition of this file
      *                            Defaults to attachment.
      *                            Possible values: attachment, inline.
-     * @param string $charset     The character set used in the filename
-     *                            of this attachment.
+     * @param string $charset     The character set of attachment's content.
      * @param string $language    The language of the attachment
      * @param string $location    The RFC 2557.4 location of the attachment
-     * @param string $n_encoding  Encoding for attachment name (Content-Type)
+     * @param string $n_encoding  Encoding of the attachment's name in Content-Type
      *                            By default filenames are encoded using RFC2231 method
      *                            Here you can set RFC2047 encoding (quoted-printable
      *                            or base64) instead
-     * @param string $f_encoding  Encoding for attachment filename (Content-Disposition)
-     *                            See $n_encoding description
+     * @param string $f_encoding  Encoding of the attachment's filename
+     *                            in Content-Disposition header.
      * @param string $description Content-Description header
+     * @param string $h_charset   The character set of the headers e.g. filename
+     *                            If not specified, $charset will be used
+     * @param array  $add_headers Additional part headers. Array keys can be in form
+     *                            of <header_name>:<parameter_name>
      *
-     * @return mixed              True on success or PEAR_Error object
-     * @access public
+     * @return mixed True on success or PEAR_Error object
      */
-    function addAttachment($file,
+    public function addAttachment($file,
         $c_type      = 'application/octet-stream',
         $name        = '',
         $isfile      = true,
@@ -411,22 +408,29 @@ class Mail_mime
         $location    = '',
         $n_encoding  = null,
         $f_encoding  = null,
-        $description = ''
+        $description = '',
+        $h_charset   = null,
+        $add_headers = array()
     ) {
+        if ($file instanceof Mail_mimePart) {
+            $this->parts[] = $file;
+            return true;
+        }
+
         $bodyfile = null;
 
         if ($isfile) {
             // Don't load file into memory
-            if ($this->_build_params['delay_file_io']) {
+            if ($this->build_params['delay_file_io']) {
                 $filedata = null;
                 $bodyfile = $file;
             } else {
-                if (PEAR::isError($filedata = $this->_file2str($file))) {
+                if (self::isError($filedata = $this->file2str($file))) {
                     return $filedata;
                 }
             }
             // Force the name the user supplied, otherwise use $file
-            $filename = ($name ? $name : $file);
+            $filename = ($name ? $name : $this->basename($file));
         } else {
             $filedata = $file;
             $filename = $name;
@@ -434,58 +438,68 @@ class Mail_mime
 
         if (!strlen($filename)) {
             $msg = "The supplied filename for the attachment can't be empty";
-            $err = PEAR::raiseError($msg);
-            return $err;
+            return self::raiseError($msg);
         }
-        $filename = $this->_basename($filename);
 
-        $this->_parts[] = array(
+        $this->parts[] = array(
             'body'        => $filedata,
             'body_file'   => $bodyfile,
             'name'        => $filename,
             'c_type'      => $c_type,
-            'encoding'    => $encoding,
             'charset'     => $charset,
+            'encoding'    => $encoding,
             'language'    => $language,
             'location'    => $location,
             'disposition' => $disposition,
             'description' => $description,
+            'add_headers' => $add_headers,
             'name_encoding'     => $n_encoding,
-            'filename_encoding' => $f_encoding
+            'filename_encoding' => $f_encoding,
+            'headers_charset'   => $h_charset,
         );
 
         return true;
     }
 
+    /**
+     * Checks if the current message has many parts
+     *
+     * @return bool True if the message has many parts, False otherwise.
+     * @since  1.9.0
+     */
+    public function isMultipart()
+    {
+        return count($this->parts) > 0 || count($this->html_images) > 0
+            || (strlen($this->htmlbody) > 0 && strlen($this->txtbody) > 0);
+    }
+
     /**
      * Get the contents of the given file name as string
      *
      * @param string $file_name Path of file to process
      *
-     * @return string           Contents of $file_name
-     * @access private
+     * @return string Contents of $file_name
      */
-    function &_file2str($file_name)
+    protected function file2str($file_name)
     {
         // Check state of file and raise an error properly
         if (!file_exists($file_name)) {
-            $err = PEAR::raiseError('File not found: ' . $file_name);
-            return $err;
+            return self::raiseError('File not found: ' . $file_name);
         }
         if (!is_file($file_name)) {
-            $err = PEAR::raiseError('Not a regular file: ' . $file_name);
-            return $err;
+            return self::raiseError('Not a regular file: ' . $file_name);
         }
         if (!is_readable($file_name)) {
-            $err = PEAR::raiseError('File is not readable: ' . $file_name);
-            return $err;
+            return self::raiseError('File is not readable: ' . $file_name);
         }
 
         // Temporarily reset magic_quotes_runtime and read file contents
         if ($magic_quote_setting = get_magic_quotes_runtime()) {
             @ini_set('magic_quotes_runtime', 0);
         }
+
         $cont = file_get_contents($file_name);
+
         if ($magic_quote_setting) {
             @ini_set('magic_quotes_runtime', $magic_quote_setting);
         }
@@ -497,53 +511,44 @@ class Mail_mime
      * Adds a text subpart to the mimePart object and
      * returns it during the build process.
      *
-     * @param mixed  &$obj The object to add the part to, or
-     *                     null if a new object is to be created.
-     * @param string $text The text to add.
+     * @param mixed $obj The object to add the part to, or
+     *                   anything else if a new object is to be created.
      *
-     * @return object      The text mimePart object
-     * @access private
+     * @return object The text mimePart object
      */
-    function &_addTextPart(&$obj, $text)
+    protected function addTextPart($obj = null)
     {
-        $params['content_type'] = 'text/plain';
-        $params['encoding']     = $this->_build_params['text_encoding'];
-        $params['charset']      = $this->_build_params['text_charset'];
-        $params['eol']          = $this->_build_params['eol'];
-
-        if (is_object($obj)) {
-            $ret = $obj->addSubpart($text, $params);
-            return $ret;
-        } else {
-            $ret = new Mail_mimePart($text, $params);
-            return $ret;
-        }
+        return $this->addBodyPart($obj, $this->txtbody, 'text/plain', 'text');
     }
 
     /**
      * Adds a html subpart to the mimePart object and
      * returns it during the build process.
      *
-     * @param mixed &$obj The object to add the part to, or
-     *                    null if a new object is to be created.
+     * @param mixed $obj The object to add the part to, or
+     *                   anything else if a new object is to be created.
      *
-     * @return object     The html mimePart object
-     * @access private
+     * @return object The html mimePart object
      */
-    function &_addHtmlPart(&$obj)
+    protected function addHtmlPart($obj = null)
     {
-        $params['content_type'] = 'text/html';
-        $params['encoding']     = $this->_build_params['html_encoding'];
-        $params['charset']      = $this->_build_params['html_charset'];
-        $params['eol']          = $this->_build_params['eol'];
+        return $this->addBodyPart($obj, $this->htmlbody, 'text/html', 'html');
+    }
 
-        if (is_object($obj)) {
-            $ret = $obj->addSubpart($this->_htmlbody, $params);
-            return $ret;
-        } else {
-            $ret = new Mail_mimePart($this->_htmlbody, $params);
-            return $ret;
-        }
+    /**
+     * Adds a calendar subpart to the mimePart object and
+     * returns it during the build process.
+     *
+     * @param mixed $obj The object to add the part to, or
+     *                   anything else if a new object is to be created.
+     *
+     * @return object The text mimePart object
+     */
+    protected function addCalendarPart($obj = null)
+    {
+        $ctype = 'text/calendar; method='. $this->build_params['calendar_method'];
+
+        return $this->addBodyPart($obj, $this->calbody, $ctype, 'calendar');
     }
 
     /**
@@ -551,18 +556,17 @@ class Mail_mime
      * the initial content-type and returns it during the
      * build process.
      *
+     * @param array $params Additional part parameters
+     *
      * @return object The multipart/mixed mimePart object
-     * @access private
      */
-    function &_addMixedPart()
+    protected function addMixedPart($params = array())
     {
-        $params                 = array();
         $params['content_type'] = 'multipart/mixed';
-        $params['eol']          = $this->_build_params['eol'];
+        $params['eol']          = $this->build_params['eol'];
 
         // Create empty multipart/mixed Mail_mimePart object to return
-        $ret = new Mail_mimePart('', $params);
-        return $ret;
+        return new Mail_mimePart('', $params);
     }
 
     /**
@@ -570,23 +574,23 @@ class Mail_mime
      * object (or creates one), and returns it during
      * the build process.
      *
-     * @param mixed &$obj The object to add the part to, or
-     *                    null if a new object is to be created.
+     * @param mixed $obj The object to add the part to, or
+     *                   anything else if a new object is to be created.
      *
-     * @return object     The multipart/mixed mimePart object
-     * @access private
+     * @return object The multipart/mixed mimePart object
      */
-    function &_addAlternativePart(&$obj)
+    protected function addAlternativePart($obj = null)
     {
         $params['content_type'] = 'multipart/alternative';
-        $params['eol']          = $this->_build_params['eol'];
+        $params['eol']          = $this->build_params['eol'];
 
         if (is_object($obj)) {
-            return $obj->addSubpart('', $params);
+            $ret = $obj->addSubpart('', $params);
         } else {
             $ret = new Mail_mimePart('', $params);
-            return $ret;
         }
+
+        return $ret;
     }
 
     /**
@@ -594,44 +598,43 @@ class Mail_mime
      * object (or creates one), and returns it during
      * the build process.
      *
-     * @param mixed &$obj The object to add the part to, or
-     *                    null if a new object is to be created
+     * @param mixed $obj The object to add the part to, or
+     *                   anything else if a new object is to be created
      *
-     * @return object     The multipart/mixed mimePart object
-     * @access private
+     * @return object The multipart/mixed mimePart object
      */
-    function &_addRelatedPart(&$obj)
+    protected function addRelatedPart($obj = null)
     {
         $params['content_type'] = 'multipart/related';
-        $params['eol']          = $this->_build_params['eol'];
+        $params['eol']          = $this->build_params['eol'];
 
         if (is_object($obj)) {
-            return $obj->addSubpart('', $params);
+            $ret = $obj->addSubpart('', $params);
         } else {
             $ret = new Mail_mimePart('', $params);
-            return $ret;
         }
+
+        return $ret;
     }
 
     /**
      * Adds an html image subpart to a mimePart object
      * and returns it during the build process.
      *
-     * @param object &$obj  The mimePart to add the image to
+     * @param object $obj   The mimePart to add the image to
      * @param array  $value The image information
      *
-     * @return object       The image mimePart object
-     * @access private
+     * @return object The image mimePart object
      */
-    function &_addHtmlImagePart(&$obj, $value)
+    protected function addHtmlImagePart($obj, $value)
     {
         $params['content_type'] = $value['c_type'];
         $params['encoding']     = 'base64';
         $params['disposition']  = 'inline';
-        $params['dfilename']    = $value['name'];
+        $params['filename']     = $value['name'];
         $params['cid']          = $value['cid'];
         $params['body_file']    = $value['body_file'];
-        $params['eol']          = $this->_build_params['eol'];
+        $params['eol']          = $this->build_params['eol'];
 
         if (!empty($value['name_encoding'])) {
             $params['name_encoding'] = $value['name_encoding'];
@@ -640,36 +643,44 @@ class Mail_mime
             $params['filename_encoding'] = $value['filename_encoding'];
         }
 
-        $ret = $obj->addSubpart($value['body'], $params);
-        return $ret;
+        return $obj->addSubpart($value['body'], $params);
     }
 
     /**
      * Adds an attachment subpart to a mimePart object
      * and returns it during the build process.
      *
-     * @param object &$obj  The mimePart to add the image to
-     * @param array  $value The attachment information
+     * @param object $obj   The mimePart to add the image to
+     * @param mixed  $value The attachment information array or Mail_mimePart object
      *
-     * @return object       The image mimePart object
-     * @access private
+     * @return object The image mimePart object
      */
-    function &_addAttachmentPart(&$obj, $value)
+    protected function addAttachmentPart($obj, $value)
     {
-        $params['eol']          = $this->_build_params['eol'];
-        $params['dfilename']    = $value['name'];
+        if ($value instanceof Mail_mimePart) {
+            return $obj->addSubpart($value);
+        }
+
+        $params['eol']          = $this->build_params['eol'];
+        $params['filename']     = $value['name'];
         $params['encoding']     = $value['encoding'];
         $params['content_type'] = $value['c_type'];
         $params['body_file']    = $value['body_file'];
-        $params['disposition']  = isset($value['disposition']) ? 
+        $params['disposition']  = isset($value['disposition']) ?
                                   $value['disposition'] : 'attachment';
-        if ($value['charset']) {
+
+        // content charset
+        if (!empty($value['charset'])) {
             $params['charset'] = $value['charset'];
         }
-        if ($value['language']) {
+        // headers charset (filename, description)
+        if (!empty($value['headers_charset'])) {
+            $params['headers_charset'] = $value['headers_charset'];
+        }
+        if (!empty($value['language'])) {
             $params['language'] = $value['language'];
         }
-        if ($value['location']) {
+        if (!empty($value['location'])) {
             $params['location'] = $value['location'];
         }
         if (!empty($value['name_encoding'])) {
@@ -681,84 +692,80 @@ class Mail_mime
         if (!empty($value['description'])) {
             $params['description'] = $value['description'];
         }
+        if (is_array($value['add_headers'])) {
+            $params['headers'] = $value['add_headers'];
+        }
 
-        $ret = $obj->addSubpart($value['body'], $params);
-        return $ret;
+        return $obj->addSubpart($value['body'], $params);
     }
 
     /**
      * Returns the complete e-mail, ready to send using an alternative
      * mail delivery method. Note that only the mailpart that is made
      * with Mail_Mime is created. This means that,
-     * YOU WILL HAVE NO TO: HEADERS UNLESS YOU SET IT YOURSELF 
+     * YOU WILL HAVE NO TO: HEADERS UNLESS YOU SET IT YOURSELF
      * using the $headers parameter!
-     * 
+     *
      * @param string $separation The separation between these two parts.
      * @param array  $params     The Build parameters passed to the
-     *                           &get() function. See &get for more info.
+     *                           get() function. See get() for more info.
      * @param array  $headers    The extra headers that should be passed
-     *                           to the &headers() function.
+     *                           to the headers() method.
      *                           See that function for more info.
      * @param bool   $overwrite  Overwrite the existing headers with new.
      *
      * @return mixed The complete e-mail or PEAR error object
-     * @access public
      */
-    function getMessage($separation = null, $params = null, $headers = null,
+    public function getMessage($separation = null, $params = null, $headers = null,
         $overwrite = false
     ) {
         if ($separation === null) {
-            $separation = $this->_build_params['eol'];
+            $separation = $this->build_params['eol'];
         }
 
         $body = $this->get($params);
 
-        if (PEAR::isError($body)) {
+        if (self::isError($body)) {
             return $body;
         }
 
-        $head = $this->txtHeaders($headers, $overwrite);
-        $mail = $head . $separation . $body;
-        return $mail;
+        return $this->txtHeaders($headers, $overwrite) . $separation . $body;
     }
 
     /**
      * Returns the complete e-mail body, ready to send using an alternative
      * mail delivery method.
-     * 
+     *
      * @param array $params The Build parameters passed to the
-     *                      &get() function. See &get for more info.
+     *                      get() method. See get() for more info.
      *
      * @return mixed The e-mail body or PEAR error object
-     * @access public
-     * @since 1.6.0
+     * @since  1.6.0
      */
-    function getMessageBody($params = null)
+    public function getMessageBody($params = null)
     {
         return $this->get($params, null, true);
     }
 
     /**
      * Writes (appends) the complete e-mail into file.
-     * 
+     *
      * @param string $filename  Output file location
      * @param array  $params    The Build parameters passed to the
-     *                          &get() function. See &get for more info.
+     *                          get() method. See get() for more info.
      * @param array  $headers   The extra headers that should be passed
-     *                          to the &headers() function.
+     *                          to the headers() function.
      *                          See that function for more info.
      * @param bool   $overwrite Overwrite the existing headers with new.
      *
      * @return mixed True or PEAR error object
-     * @access public
-     * @since 1.6.0
+     * @since  1.6.0
      */
-    function saveMessage($filename, $params = null, $headers = null, $overwrite = false)
+    public function saveMessage($filename, $params = null, $headers = null, $overwrite = false)
     {
         // Check state of file and raise an error properly
         if (file_exists($filename) && !is_writable($filename)) {
-            $err = PEAR::raiseError('File is not writable: ' . $filename);
-            return $err;
+            return self::raiseError('File is not writable: ' . $filename);
         }
 
         // Temporarily reset magic_quotes_runtime and read file contents
@@ -767,15 +774,13 @@ class Mail_mime
         }
 
         if (!($fh = fopen($filename, 'ab'))) {
-            $err = PEAR::raiseError('Unable to open file: ' . $filename);
-            return $err;
+            return self::raiseError('Unable to open file: ' . $filename);
         }
 
         // Write message headers into file (skipping Content-* headers)
         $head = $this->txtHeaders($headers, $overwrite, true);
         if (fwrite($fh, $head) === false) {
-            $err = PEAR::raiseError('Error writing to file: ' . $filename);
-            return $err;
+            return self::raiseError('Error writing to file: ' . $filename);
         }
 
         fclose($fh);
@@ -791,22 +796,29 @@ class Mail_mime
     }
 
     /**
-     * Writes (appends) the complete e-mail body into file.
-     * 
-     * @param string $filename Output file location
-     * @param array  $params   The Build parameters passed to the
-     *                         &get() function. See &get for more info.
+     * Writes (appends) the complete e-mail body into file or stream.
+     *
+     * @param mixed $filename Output filename or file pointer where to save
+     *                        the message instead of returning it
+     * @param array $params   The Build parameters passed to the
+     *                        get() method. See get() for more info.
      *
      * @return mixed True or PEAR error object
-     * @access public
-     * @since 1.6.0
+     * @since  1.6.0
      */
-    function saveMessageBody($filename, $params = null)
+    public function saveMessageBody($filename, $params = null)
     {
-        // Check state of file and raise an error properly
-        if (file_exists($filename) && !is_writable($filename)) {
-            $err = PEAR::raiseError('File is not writable: ' . $filename);
-            return $err;
+        if (!is_resource($filename)) {
+            // Check state of file and raise an error properly
+            if (!file_exists($filename) || !is_writable($filename)) {
+                return self::raiseError('File is not writable: ' . $filename);
+            }
+
+            if (!($fh = fopen($filename, 'ab'))) {
+                return self::raiseError('Unable to open file: ' . $filename);
+            }
+        } else {
+            $fh = $filename;
         }
 
         // Temporarily reset magic_quotes_runtime and read file contents
@@ -814,107 +826,126 @@ class Mail_mime
             @ini_set('magic_quotes_runtime', 0);
         }
 
-        if (!($fh = fopen($filename, 'ab'))) {
-            $err = PEAR::raiseError('Unable to open file: ' . $filename);
-            return $err;
+        // Write the rest of the message into file
+        $res = $this->get($params, $fh, true);
+
+        if (!is_resource($filename)) {
+            fclose($fh);
         }
 
-        // Write the rest of the message into file
-        $res = $this->get($params, $filename, true);
+        if ($magic_quote_setting) {
+            @ini_set('magic_quotes_runtime', $magic_quote_setting);
+        }
 
         return $res ? $res : true;
     }
 
     /**
-     * Builds the multipart message from the list ($this->_parts) and
+     * Builds the multipart message from the list ($this->parts) and
      * returns the mime content.
      *
-     * @param array    $params    Build parameters that change the way the email
-     *                            is built. Should be associative. See $_build_params.
-     * @param resource $filename  Output file where to save the message instead of
-     *                            returning it
-     * @param boolean  $skip_head True if you want to return/save only the message
-     *                            without headers
+     * @param array   $params    Build parameters that change the way the email
+     *                           is built. Should be associative. See $_build_params.
+     * @param mixed   $filename  Output filename or file pointer where to save
+     *                           the message instead of returning it
+     * @param boolean $skip_head True if you want to return/save only the message
+     *                           without headers
      *
      * @return mixed The MIME message content string, null or PEAR error object
-     * @access public
      */
-    function &get($params = null, $filename = null, $skip_head = false)
+    public function get($params = null, $filename = null, $skip_head = false)
     {
-        if (isset($params)) {
-            while (list($key, $value) = each($params)) {
-                $this->_build_params[$key] = $value;
-            }
+        if (!empty($params) && is_array($params)) {
+            $this->build_params = array_merge($this->build_params, $params);
         }
 
-        if (isset($this->_headers['From'])) {
+        if (isset($this->headers['From'])) {
             // Bug #11381: Illegal characters in domain ID
-            if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $this->_headers['From'], $matches)) {
+            if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $this->headers['From'], $matches)) {
                 $domainID = $matches[1];
             } else {
                 $domainID = '@localhost';
             }
-            foreach ($this->_html_images as $i => $img) {
-                $cid = $this->_html_images[$i]['cid']; 
+
+            foreach ($this->html_images as $i => $img) {
+                $cid = $this->html_images[$i]['cid'];
                 if (!preg_match('#'.preg_quote($domainID).'$#', $cid)) {
-                    $this->_html_images[$i]['cid'] = $cid . $domainID;
+                    $this->html_images[$i]['cid'] = $cid . $domainID;
                 }
             }
         }
 
-        if (count($this->_html_images) && isset($this->_htmlbody)) {
-            foreach ($this->_html_images as $key => $value) {
-                $regex   = array();
-                $regex[] = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' .
-                            preg_quote($value['name'], '#') . '\3#';
-                $regex[] = '#(?i)url(?-i)\(\s*(["\']?)' .
-                            preg_quote($value['name'], '#') . '\1\s*\)#';
+        if (count($this->html_images) && isset($this->htmlbody)) {
+            foreach ($this->html_images as $key => $value) {
+                $rval  = preg_quote($value['name'], '#');
+                $regex = array(
+                    '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' . $rval . '\3#',
+                    '#(?i)url(?-i)\(\s*(["\']?)' . $rval . '\1\s*\)#',
+                );
 
-                $rep   = array();
-                $rep[] = '\1\2=\3cid:' . $value['cid'] .'\3';
-                $rep[] = 'url(\1cid:' . $value['cid'] . '\1)';
+                $rep = array(
+                    '\1\2=\3cid:' . $value['cid'] .'\3',
+                    'url(\1cid:' . $value['cid'] . '\1)',
+                );
 
-                $this->_htmlbody = preg_replace($regex, $rep, $this->_htmlbody);
-                $this->_html_images[$key]['name']
-                    = $this->_basename($this->_html_images[$key]['name']);
+                $this->htmlbody = preg_replace($regex, $rep, $this->htmlbody);
+                $this->html_images[$key]['name']
+                    = $this->basename($this->html_images[$key]['name']);
             }
         }
 
-        $this->_checkParams();
+        $this->checkParams();
 
-        $null        = null;
-        $attachments = count($this->_parts)                 ? true : false;
-        $html_images = count($this->_html_images)           ? true : false;
-        $html        = strlen($this->_htmlbody)             ? true : false;
-        $text        = (!$html && strlen($this->_txtbody))  ? true : false;
+        $attachments = count($this->parts) > 0;
+        $html_images = count($this->html_images) > 0;
+        $html        = strlen($this->htmlbody) > 0;
+        $calendar    = strlen($this->calbody) > 0;
+        $has_text    = strlen($this->txtbody) > 0;
+        $text        = !$html && $has_text;
+        $mixed_params = array('preamble' => $this->build_params['preamble']);
 
         switch (true) {
+        case $calendar && !$attachments && !$text && !$html:
+            $message = $this->addCalendarPart();
+            break;
+
+        case $calendar && !$attachments:
+            $message = $this->addAlternativePart($mixed_params);
+            if ($has_text) {
+                $this->addTextPart($message);
+            }
+            if ($html) {
+                $this->addHtmlPart($message);
+            }
+            $this->addCalendarPart($message);
+            break;
+
         case $text && !$attachments:
-            $message =& $this->_addTextPart($null, $this->_txtbody);
+            $message = $this->addTextPart();
             break;
 
         case !$text && !$html && $attachments:
-            $message =& $this->_addMixedPart();
-            for ($i = 0; $i < count($this->_parts); $i++) {
-                $this->_addAttachmentPart($message, $this->_parts[$i]);
+            $message = $this->addMixedPart($mixed_params);
+            for ($i = 0; $i < count($this->parts); $i++) {
+                $this->addAttachmentPart($message, $this->parts[$i]);
             }
             break;
 
         case $text && $attachments:
-            $message =& $this->_addMixedPart();
-            $this->_addTextPart($message, $this->_txtbody);
-            for ($i = 0; $i < count($this->_parts); $i++) {
-                $this->_addAttachmentPart($message, $this->_parts[$i]);
+            $message = $this->addMixedPart($mixed_params);
+            $this->addTextPart($message);
+            for ($i = 0; $i < count($this->parts); $i++) {
+                $this->addAttachmentPart($message, $this->parts[$i]);
             }
             break;
 
         case $html && !$attachments && !$html_images:
-            if (isset($this->_txtbody)) {
-                $message =& $this->_addAlternativePart($null);
-                $this->_addTextPart($message, $this->_txtbody);
-                $this->_addHtmlPart($message);
+            if ($has_text) {
+                $message = $this->addAlternativePart();
+                $this->addTextPart($message);
+                $this->addHtmlPart($message);
             } else {
-                $message =& $this->_addHtmlPart($null);
+                $message = $this->addHtmlPart();
             }
             break;
 
@@ -924,23 +955,23 @@ class Mail_mime
             //    * Content-Type: multipart/related;
             //       * html
             //       * image...
-            if (isset($this->_txtbody)) {
-                $message =& $this->_addAlternativePart($null);
-                $this->_addTextPart($message, $this->_txtbody);
-
-                $ht =& $this->_addRelatedPart($message);
-                $this->_addHtmlPart($ht);
-                for ($i = 0; $i < count($this->_html_images); $i++) {
-                    $this->_addHtmlImagePart($ht, $this->_html_images[$i]);
+            if ($has_text) {
+                $message = $this->addAlternativePart();
+                $this->addTextPart($message);
+
+                $ht = $this->addRelatedPart($message);
+                $this->addHtmlPart($ht);
+                for ($i = 0; $i < count($this->html_images); $i++) {
+                    $this->addHtmlImagePart($ht, $this->html_images[$i]);
                 }
             } else {
                 // * Content-Type: multipart/related;
                 //    * html
                 //    * image...
-                $message =& $this->_addRelatedPart($null);
-                $this->_addHtmlPart($message);
-                for ($i = 0; $i < count($this->_html_images); $i++) {
-                    $this->_addHtmlImagePart($message, $this->_html_images[$i]);
+                $message = $this->addRelatedPart();
+                $this->addHtmlPart($message);
+                for ($i = 0; $i < count($this->html_images); $i++) {
+                    $this->addHtmlImagePart($message, $this->html_images[$i]);
                 }
             }
             /*
@@ -950,62 +981,60 @@ class Mail_mime
             //        * text
             //        * html
             //    * image...
-            $message =& $this->_addRelatedPart($null);
-            if (isset($this->_txtbody)) {
-                $alt =& $this->_addAlternativePart($message);
-                $this->_addTextPart($alt, $this->_txtbody);
-                $this->_addHtmlPart($alt);
+            $message = $this->addRelatedPart();
+            if ($has_text) {
+                $alt = $this->addAlternativePart($message);
+                $this->addTextPart($alt);
+                $this->addHtmlPart($alt);
             } else {
-                $this->_addHtmlPart($message);
+                $this->addHtmlPart($message);
             }
-            for ($i = 0; $i < count($this->_html_images); $i++) {
-                $this->_addHtmlImagePart($message, $this->_html_images[$i]);
+            for ($i = 0; $i < count($this->html_images); $i++) {
+                $this->addHtmlImagePart($message, $this->html_images[$i]);
             }
             */
             break;
 
         case $html && $attachments && !$html_images:
-            $message =& $this->_addMixedPart();
-            if (isset($this->_txtbody)) {
-                $alt =& $this->_addAlternativePart($message);
-                $this->_addTextPart($alt, $this->_txtbody);
-                $this->_addHtmlPart($alt);
+            $message = $this->addMixedPart($mixed_params);
+            if ($has_text) {
+                $alt = $this->addAlternativePart($message);
+                $this->addTextPart($alt);
+                $this->addHtmlPart($alt);
             } else {
-                $this->_addHtmlPart($message);
+                $this->addHtmlPart($message);
             }
-            for ($i = 0; $i < count($this->_parts); $i++) {
-                $this->_addAttachmentPart($message, $this->_parts[$i]);
+            for ($i = 0; $i < count($this->parts); $i++) {
+                $this->addAttachmentPart($message, $this->parts[$i]);
             }
             break;
 
         case $html && $attachments && $html_images:
-            $message =& $this->_addMixedPart();
-            if (isset($this->_txtbody)) {
-                $alt =& $this->_addAlternativePart($message);
-                $this->_addTextPart($alt, $this->_txtbody);
-                $rel =& $this->_addRelatedPart($alt);
+            $message = $this->addMixedPart($mixed_params);
+            if ($has_text) {
+                $alt = $this->addAlternativePart($message);
+                $this->addTextPart($alt);
+                $rel = $this->addRelatedPart($alt);
             } else {
-                $rel =& $this->_addRelatedPart($message);
+                $rel = $this->addRelatedPart($message);
             }
-            $this->_addHtmlPart($rel);
-            for ($i = 0; $i < count($this->_html_images); $i++) {
-                $this->_addHtmlImagePart($rel, $this->_html_images[$i]);
+            $this->addHtmlPart($rel);
+            for ($i = 0; $i < count($this->html_images); $i++) {
+                $this->addHtmlImagePart($rel, $this->html_images[$i]);
             }
-            for ($i = 0; $i < count($this->_parts); $i++) {
-                $this->_addAttachmentPart($message, $this->_parts[$i]);
+            for ($i = 0; $i < count($this->parts); $i++) {
+                $this->addAttachmentPart($message, $this->parts[$i]);
             }
             break;
-
         }
 
         if (!isset($message)) {
-            $ret = null;
-            return $ret;
+            return null;
         }
 
         // Use saved boundary
-        if (!empty($this->_build_params['boundary'])) {
-            $boundary = $this->_build_params['boundary'];
+        if (!empty($this->build_params['boundary'])) {
+            $boundary = $this->build_params['boundary'];
         } else {
             $boundary = null;
         }
@@ -1014,20 +1043,18 @@ class Mail_mime
         if ($filename) {
             // Append mimePart message headers and body into file
             $headers = $message->encodeToFile($filename, $boundary, $skip_head);
-            if (PEAR::isError($headers)) {
+            if (self::isError($headers)) {
                 return $headers;
             }
-            $this->_headers = array_merge($this->_headers, $headers);
-            $ret = null;
-            return $ret;
+            $this->headers = array_merge($this->headers, $headers);
+            return null;
         } else {
             $output = $message->encode($boundary, $skip_head);
-            if (PEAR::isError($output)) {
+            if (self::isError($output)) {
                 return $output;
             }
-            $this->_headers = array_merge($this->_headers, $output['headers']);
-            $body = $output['body'];
-            return $body;
+            $this->headers = array_merge($this->headers, $output['headers']);
+            return $output['body'];
         }
     }
 
@@ -1041,11 +1068,10 @@ class Mail_mime
      * @param bool  $overwrite    Overwrite already existing headers.
      * @param bool  $skip_content Don't return content headers: Content-Type,
      *                            Content-Disposition and Content-Transfer-Encoding
-     * 
-     * @return array              Assoc array with the mime headers
-     * @access public
+     *
+     * @return array Assoc array with the mime headers
      */
-    function &headers($xtra_headers = null, $overwrite = false, $skip_content = false)
+    public function headers($xtra_headers = null, $overwrite = false, $skip_content = false)
     {
         // Add mime version header
         $headers['MIME-Version'] = '1.0';
@@ -1055,7 +1081,7 @@ class Mail_mime
         // we got them when called before get() or something in the message
         // has been changed after get() [#14780]
         if (!$skip_content) {
-            $headers += $this->_contentHeaders();
+            $headers += $this->contentHeaders();
         }
 
         if (!empty($xtra_headers)) {
@@ -1063,22 +1089,22 @@ class Mail_mime
         }
 
         if ($overwrite) {
-            $this->_headers = array_merge($this->_headers, $headers);
+            $this->headers = array_merge($this->headers, $headers);
         } else {
-            $this->_headers = array_merge($headers, $this->_headers);
+            $this->headers = array_merge($headers, $this->headers);
         }
 
-        $headers = $this->_headers;
+        $headers = $this->headers;
 
         if ($skip_content) {
             unset($headers['Content-Type']);
             unset($headers['Content-Transfer-Encoding']);
             unset($headers['Content-Disposition']);
-        } else if (!empty($this->_build_params['ctype'])) {
-            $headers['Content-Type'] = $this->_build_params['ctype'];
+        } else if (!empty($this->build_params['ctype'])) {
+            $headers['Content-Type'] = $this->build_params['ctype'];
         }
 
-        $encodedHeaders = $this->_encodeHeaders($headers);
+        $encodedHeaders = $this->encodeHeaders($headers);
         return $encodedHeaders;
     }
 
@@ -1092,10 +1118,9 @@ class Mail_mime
      * @param bool  $skip_content Don't return content headers: Content-Type,
      *                            Content-Disposition and Content-Transfer-Encoding
      *
-     * @return string             Plain text headers
-     * @access public
+     * @return string Plain text headers
      */
-    function txtHeaders($xtra_headers = null, $overwrite = false, $skip_content = false)
+    public function txtHeaders($xtra_headers = null, $overwrite = false, $skip_content = false)
     {
         $headers = $this->headers($xtra_headers, $overwrite, $skip_content);
 
@@ -1108,7 +1133,7 @@ class Mail_mime
         }
 
         $ret = '';
-        $eol = $this->_build_params['eol'];
+        $eol = $this->build_params['eol'];
 
         foreach ($headers as $key => $val) {
             if (is_array($val)) {
@@ -1126,31 +1151,28 @@ class Mail_mime
     /**
      * Sets message Content-Type header.
      * Use it to build messages with various content-types e.g. miltipart/raport
-     * not supported by _contentHeaders() function.
+     * not supported by contentHeaders() function.
      *
      * @param string $type   Type name
      * @param array  $params Hash array of header parameters
      *
      * @return void
-     * @access public
-     * @since 1.7.0
+     * @since  1.7.0
      */
-    function setContentType($type, $params = array())
+    public function setContentType($type, $params = array())
     {
         $header = $type;
 
-        $eol = !empty($this->_build_params['eol'])
-            ? $this->_build_params['eol'] : "\r\n";
+        $eol = !empty($this->build_params['eol']) ? $this->build_params['eol'] : "\r\n";
 
         // add parameters
-        $token_regexp = '#([^\x21,\x23-\x27,\x2A,\x2B,\x2D'
-            . ',\x2E,\x30-\x39,\x41-\x5A,\x5E-\x7E])#';
+        $token_regexp = '#([^\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E])#';
+
         if (is_array($params)) {
             foreach ($params as $name => $value) {
                 if ($name == 'boundary') {
-                    $this->_build_params['boundary'] = $value;
-                }
-                if (!preg_match($token_regexp, $value)) {
+                    $this->build_params['boundary'] = $value;
+                } else if (!preg_match($token_regexp, $value)) {
                     $header .= ";$eol $name=$value";
                 } else {
                     $value = addcslashes($value, '\\"');
@@ -1160,15 +1182,15 @@ class Mail_mime
         }
 
         // add required boundary parameter if not defined
-        if (preg_match('/^multipart\//i', $type)) {
-            if (empty($this->_build_params['boundary'])) {
-                $this->_build_params['boundary'] = '=_' . md5(rand() . microtime());
+        if (stripos($type, 'multipart/') === 0) {
+            if (empty($this->build_params['boundary'])) {
+                $this->build_params['boundary'] = '=_' . md5(rand() . microtime());
             }
 
-            $header .= ";$eol boundary=\"".$this->_build_params['boundary']."\"";
+            $header .= ";$eol boundary=\"".$this->build_params['boundary']."\"";
         }
 
-        $this->_build_params['ctype'] = $header;
+        $this->build_params['ctype'] = $header;
     }
 
     /**
@@ -1177,11 +1199,10 @@ class Mail_mime
      * @param string $subject String to set the subject to.
      *
      * @return void
-     * @access public
      */
-    function setSubject($subject)
+    public function setSubject($subject)
     {
-        $this->_headers['Subject'] = $subject;
+        $this->headers['Subject'] = $subject;
     }
 
     /**
@@ -1190,11 +1211,10 @@ class Mail_mime
      * @param string $email The email address to use
      *
      * @return void
-     * @access public
      */
-    function setFrom($email)
+    public function setFrom($email)
     {
-        $this->_headers['From'] = $email;
+        $this->headers['From'] = $email;
     }
 
     /**
@@ -1204,14 +1224,13 @@ class Mail_mime
      * @param string $email The email direction to add
      *
      * @return void
-     * @access public
      */
-    function addTo($email)
+    public function addTo($email)
     {
-        if (isset($this->_headers['To'])) {
-            $this->_headers['To'] .= ", $email";
+        if (isset($this->headers['To'])) {
+            $this->headers['To'] .= ", $email";
         } else {
-            $this->_headers['To'] = $email;
+            $this->headers['To'] = $email;
         }
     }
 
@@ -1222,14 +1241,13 @@ class Mail_mime
      * @param string $email The email direction to add
      *
      * @return void
-     * @access public
      */
-    function addCc($email)
+    public function addCc($email)
     {
-        if (isset($this->_headers['Cc'])) {
-            $this->_headers['Cc'] .= ", $email";
+        if (isset($this->headers['Cc'])) {
+            $this->headers['Cc'] .= ", $email";
         } else {
-            $this->_headers['Cc'] = $email;
+            $this->headers['Cc'] = $email;
         }
     }
 
@@ -1240,14 +1258,13 @@ class Mail_mime
      * @param string $email The email direction to add
      *
      * @return void
-     * @access public
      */
-    function addBcc($email)
+    public function addBcc($email)
     {
-        if (isset($this->_headers['Bcc'])) {
-            $this->_headers['Bcc'] .= ", $email";
+        if (isset($this->headers['Bcc'])) {
+            $this->headers['Bcc'] .= ", $email";
         } else {
-            $this->_headers['Bcc'] = $email;
+            $this->headers['Bcc'] = $email;
         }
     }
 
@@ -1255,20 +1272,19 @@ class Mail_mime
      * Since the PHP send function requires you to specify
      * recipients (To: header) separately from the other
      * headers, the To: header is not properly encoded.
-     * To fix this, you can use this public method to 
-     * encode your recipients before sending to the send
-     * function
+     * To fix this, you can use this public method to encode
+     * your recipients before sending to the send function.
      *
      * @param string $recipients A comma-delimited list of recipients
      *
-     * @return string            Encoded data
-     * @access public
+     * @return string Encoded data
      */
-    function encodeRecipients($recipients)
+    public function encodeRecipients($recipients)
     {
-        $input = array("To" => $recipients);
-        $retval = $this->_encodeHeaders($input);
-        return $retval["To"] ;
+        $input  = array('To' => $recipients);
+        $retval = $this->encodeHeaders($input);
+
+        return $retval['To'] ;
     }
 
     /**
@@ -1277,14 +1293,14 @@ class Mail_mime
      * @param array $input  The header data to encode
      * @param array $params Extra build parameters
      *
-     * @return array        Encoded data
-     * @access private
+     * @return array Encoded data
      */
-    function _encodeHeaders($input, $params = array())
+    protected function encodeHeaders($input, $params = array())
     {
-        $build_params = $this->_build_params;
-        while (list($key, $value) = each($params)) {
-            $build_params[$key] = $value;
+        $build_params = $this->build_params;
+
+        if (!empty($params)) {
+            $build_params = array_merge($build_params, $params);
         }
 
         foreach ($input as $hdr_name => $hdr_value) {
@@ -1295,11 +1311,13 @@ class Mail_mime
                         $build_params['head_charset'], $build_params['head_encoding']
                     );
                 }
-            } else {
+            } else if ($hdr_value !== null) {
                 $input[$hdr_name] = $this->encodeHeader(
                     $hdr_name, $hdr_value,
                     $build_params['head_charset'], $build_params['head_encoding']
                 );
+            } else {
+                unset($input[$hdr_name]);
             }
         }
 
@@ -1314,26 +1332,24 @@ class Mail_mime
      * @param string $charset  Character set name
      * @param string $encoding Encoding name (base64 or quoted-printable)
      *
-     * @return string          Encoded header data (without a name)
-     * @access public
-     * @since 1.5.3
+     * @return string Encoded header data (without a name)
+     * @since  1.5.3
      */
-    function encodeHeader($name, $value, $charset, $encoding)
+    public function encodeHeader($name, $value, $charset, $encoding)
     {
         return Mail_mimePart::encodeHeader(
-            $name, $value, $charset, $encoding, $this->_build_params['eol']
+            $name, $value, $charset, $encoding, $this->build_params['eol']
         );
     }
 
     /**
-     * Get file's basename (locale independent) 
+     * Get file's basename (locale independent)
      *
      * @param string $filename Filename
      *
-     * @return string          Basename
-     * @access private
+     * @return string Basename
      */
-    function _basename($filename)
+    protected function basename($filename)
     {
         // basename() is not unicode safe and locale dependent
         if (stristr(PHP_OS, 'win') || stristr(PHP_OS, 'netware')) {
@@ -1347,18 +1363,27 @@ class Mail_mime
      * Get Content-Type and Content-Transfer-Encoding headers of the message
      *
      * @return array Headers array
-     * @access private
      */
-    function _contentHeaders()
+    protected function contentHeaders()
     {
-        $attachments = count($this->_parts)                 ? true : false;
-        $html_images = count($this->_html_images)           ? true : false;
-        $html        = strlen($this->_htmlbody)             ? true : false;
-        $text        = (!$html && strlen($this->_txtbody))  ? true : false;
+        $attachments = count($this->parts) > 0;
+        $html_images = count($this->html_images) > 0;
+        $html        = strlen($this->htmlbody) > 0;
+        $calendar    = strlen($this->calbody) > 0;
+        $has_text    = strlen($this->txtbody) > 0;
+        $text        = !$html && $has_text;
         $headers     = array();
 
         // See get()
         switch (true) {
+        case $calendar && !$attachments && !$html && !$has_text:
+            $headers['Content-Type'] = 'text/calendar';
+            break;
+
+        case $calendar && !$attachments:
+            $headers['Content-Type'] = 'multipart/alternative';
+            break;
+
         case $text && !$attachments:
             $headers['Content-Type'] = 'text/plain';
             break;
@@ -1370,16 +1395,16 @@ class Mail_mime
             $headers['Content-Type'] = 'multipart/mixed';
             break;
 
-        case $html && !$attachments && !$html_images && isset($this->_txtbody):
-        case $html && !$attachments && $html_images && isset($this->_txtbody):
+        case $html && !$attachments && !$html_images && $has_text:
+        case $html && !$attachments && $html_images && $has_text:
             $headers['Content-Type'] = 'multipart/alternative';
             break;
 
-        case $html && !$attachments && !$html_images && !isset($this->_txtbody):
+        case $html && !$attachments && !$html_images && !$has_text:
             $headers['Content-Type'] = 'text/html';
             break;
 
-        case $html && !$attachments && $html_images && !isset($this->_txtbody):
+        case $html && !$attachments && $html_images && !$has_text:
             $headers['Content-Type'] = 'multipart/related';
             break;
 
@@ -1387,36 +1412,60 @@ class Mail_mime
             return $headers;
         }
 
-        $this->_checkParams();
+        $this->checkParams();
 
-        $eol = !empty($this->_build_params['eol'])
-            ? $this->_build_params['eol'] : "\r\n";
+        $eol = !empty($this->build_params['eol'])
+            ? $this->build_params['eol'] : "\r\n";
 
         if ($headers['Content-Type'] == 'text/plain') {
             // single-part message: add charset and encoding
-            $headers['Content-Type']
-                .= ";$eol charset=" . $this->_build_params['text_charset'];
+            if ($this->build_params['text_charset']) {
+                $charset = 'charset=' . $this->build_params['text_charset'];
+                // place charset parameter in the same line, if possible
+                // 26 = strlen("Content-Type: text/plain; ")
+                $headers['Content-Type']
+                    .= (strlen($charset) + 26 <= 76) ? "; $charset" : ";$eol $charset";
+            }
+
             $headers['Content-Transfer-Encoding']
-                = $this->_build_params['text_encoding'];
+                = $this->build_params['text_encoding'];
         } else if ($headers['Content-Type'] == 'text/html') {
             // single-part message: add charset and encoding
-            $headers['Content-Type']
-                .= ";$eol charset=" . $this->_build_params['html_charset'];
+            if ($this->build_params['html_charset']) {
+                $charset = 'charset=' . $this->build_params['html_charset'];
+                // place charset parameter in the same line, if possible
+                $headers['Content-Type']
+                    .= (strlen($charset) + 25 <= 76) ? "; $charset" : ";$eol $charset";
+            }
+            $headers['Content-Transfer-Encoding']
+                = $this->build_params['html_encoding'];
+        } else if ($headers['Content-Type'] == 'text/calendar') {
+            // single-part message: add charset and encoding
+            if ($this->build_params['calendar_charset']) {
+                $charset = 'charset=' . $this->build_params['calendar_charset'];
+                $headers['Content-Type'] .= "; $charset";
+            }
+
+            if ($this->build_params['calendar_method']) {
+                $method = 'method=' . $this->build_params['calendar_method'];
+                $headers['Content-Type'] .= "; $method";
+            }
+
             $headers['Content-Transfer-Encoding']
-                = $this->_build_params['html_encoding'];
+                = $this->build_params['calendar_encoding'];
         } else {
-            // multipart message: add charset and boundary
-            if (!empty($this->_build_params['boundary'])) {
-                $boundary = $this->_build_params['boundary'];
-            } else if (!empty($this->_headers['Content-Type'])
-                && preg_match('/boundary="([^"]+)"/', $this->_headers['Content-Type'], $m)
+            // multipart message: and boundary
+            if (!empty($this->build_params['boundary'])) {
+                $boundary = $this->build_params['boundary'];
+            } else if (!empty($this->headers['Content-Type'])
+                && preg_match('/boundary="([^"]+)"/', $this->headers['Content-Type'], $m)
             ) {
                 $boundary = $m[1];
             } else {
                 $boundary = '=_' . md5(rand() . microtime());
             }
 
-            $this->_build_params['boundary'] = $boundary;
+            $this->build_params['boundary'] = $boundary;
             $headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
         }
 
@@ -1427,38 +1476,140 @@ class Mail_mime
      * Validate and set build parameters
      *
      * @return void
-     * @access private
      */
-    function _checkParams()
+    protected function checkParams()
     {
         $encodings = array('7bit', '8bit', 'base64', 'quoted-printable');
 
-        $this->_build_params['text_encoding']
-            = strtolower($this->_build_params['text_encoding']);
-        $this->_build_params['html_encoding']
-            = strtolower($this->_build_params['html_encoding']);
+        $this->build_params['text_encoding']
+            = strtolower($this->build_params['text_encoding']);
+        $this->build_params['html_encoding']
+            = strtolower($this->build_params['html_encoding']);
+        $this->build_params['calendar_encoding']
+            = strtolower($this->build_params['calendar_encoding']);
 
-        if (!in_array($this->_build_params['text_encoding'], $encodings)) {
-            $this->_build_params['text_encoding'] = '7bit';
+        if (!in_array($this->build_params['text_encoding'], $encodings)) {
+            $this->build_params['text_encoding'] = '7bit';
+        }
+        if (!in_array($this->build_params['html_encoding'], $encodings)) {
+            $this->build_params['html_encoding'] = '7bit';
         }
-        if (!in_array($this->_build_params['html_encoding'], $encodings)) {
-            $this->_build_params['html_encoding'] = '7bit';
+        if (!in_array($this->build_params['calendar_encoding'], $encodings)) {
+            $this->build_params['calendar_encoding'] = '7bit';
         }
 
         // text body
-        if ($this->_build_params['text_encoding'] == '7bit'
-            && !preg_match('/ascii/i', $this->_build_params['text_charset'])
-            && preg_match('/[^\x00-\x7F]/', $this->_txtbody)
+        if ($this->build_params['text_encoding'] == '7bit'
+            && !preg_match('/ascii/i', $this->build_params['text_charset'])
+            && preg_match('/[^\x00-\x7F]/', $this->txtbody)
         ) {
-            $this->_build_params['text_encoding'] = 'quoted-printable';
+            $this->build_params['text_encoding'] = 'quoted-printable';
         }
         // html body
-        if ($this->_build_params['html_encoding'] == '7bit'
-            && !preg_match('/ascii/i', $this->_build_params['html_charset'])
-            && preg_match('/[^\x00-\x7F]/', $this->_htmlbody)
+        if ($this->build_params['html_encoding'] == '7bit'
+            && !preg_match('/ascii/i', $this->build_params['html_charset'])
+            && preg_match('/[^\x00-\x7F]/', $this->htmlbody)
+        ) {
+            $this->build_params['html_encoding'] = 'quoted-printable';
+        }
+        // calendar body
+        if ($this->build_params['calendar_encoding'] == '7bit'
+            && !preg_match('/ascii/i', $this->build_params['calendar_charset'])
+            && preg_match('/[^\x00-\x7F]/', $this->calbody)
         ) {
-            $this->_build_params['html_encoding'] = 'quoted-printable';
+            $this->build_params['calendar_encoding'] = 'quoted-printable';
+        }
+    }
+
+    /**
+     * Set body of specified message part
+     *
+     * @param string $type   One of: txtbody, calbody, htmlbody
+     * @param string $data   Either a string or the file name with the contents
+     * @param bool   $isfile If true the first param should be treated
+     *                       as a file name, else as a string (default)
+     * @param bool   $append If true the text or file is appended to
+     *                       the existing body, else the old body is
+     *                       overwritten
+     *
+     * @return mixed True on success or PEAR_Error object
+     */
+    protected function setBody($type, $data, $isfile = false, $append = false)
+    {
+        if ($isfile) {
+            $data = $this->file2str($data);
+            if (self::isError($data)) {
+                return $data;
+            }
         }
+
+        if (!$append) {
+            $this->{$type} = $data;
+        } else {
+            $this->{$type} .= $data;
+        }
+
+        // wordwrap the txtbody to be 750 characters to comply with RFC 2821
+        // CRM-3133
+        $this->{$type} = wordwrap($this->{$type}, 750);
+        return true;
     }
 
-} // End of class
+    /**
+     * Adds a subpart to the mimePart object and
+     * returns it during the build process.
+     *
+     * @param mixed  $obj   The object to add the part to, or
+     *                      anything else if a new object is to be created.
+     * @param string $body  Part body
+     * @param string $ctype Part content type
+     * @param string $type  Internal part type
+     *
+     * @return object The mimePart object
+     */
+    protected function addBodyPart($obj, $body, $ctype, $type)
+    {
+        $params['content_type'] = $ctype;
+        $params['encoding']     = $this->build_params[$type . '_encoding'];
+        $params['charset']      = $this->build_params[$type . '_charset'];
+        $params['eol']          = $this->build_params['eol'];
+
+        if (is_object($obj)) {
+            $ret = $obj->addSubpart($body, $params);
+        } else {
+            $ret = new Mail_mimePart($body, $params);
+        }
+
+        return $ret;
+    }
+
+    /**
+     * PEAR::isError implementation
+     *
+     * @param mixed $data Object
+     *
+     * @return bool True if object is an instance of PEAR_Error
+     */
+    public static function isError($data)
+    {
+        // PEAR::isError() is not PHP 5.4 compatible (see Bug #19473)
+        if (is_a($data, 'PEAR_Error')) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * PEAR::raiseError implementation
+     *
+     * @param string $message A text error message
+     *
+     * @return PEAR_Error Instance of PEAR_Error
+     */
+    public static function raiseError($message)
+    {
+        // PEAR::raiseError() is not PHP 5.4 compatible
+        return new PEAR_Error($message);
+    }
+}
diff --git a/civicrm/packages/Mail/mimeDecode.php b/civicrm/packages/Mail/mimeDecode.php
index 4cf1ad7a59..1a12673c8a 100644
--- a/civicrm/packages/Mail/mimeDecode.php
+++ b/civicrm/packages/Mail/mimeDecode.php
@@ -28,8 +28,8 @@
  * - Redistributions in binary form must reproduce the above copyright
  *   notice, this list of conditions and the following disclaimer in the
  *   documentation and/or other materials provided with the distribution.
- * - Neither the name of the authors, nor the names of its contributors 
- *   may be used to endorse or promote products derived from this 
+ * - Neither the name of the authors, nor the names of its contributors
+ *   may be used to endorse or promote products derived from this
  *   software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
@@ -52,7 +52,7 @@
  * @author     Sean Coates <sean@php.net>
  * @copyright  2003-2006 PEAR <pear-group@php.net>
  * @license    http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version    CVS: $Id: mimeDecode.php 288500 2009-09-21 05:32:32Z alan_k $
+ * @version    CVS: $Id: mimeDecode.php 337165 2015-07-15 09:42:08Z alan_k $
  * @link       http://pear.php.net/package/Mail_mime
  */
 
@@ -141,12 +141,13 @@ class Mail_mimeDecode extends PEAR
 
     /**
      * Flag to determine whether to decode headers
-     *
-     * @var    boolean
+     * (set to UTF8 to iconv convert headers)
+     * @var    mixed
      * @access private
      */
     var $_decode_headers;
 
+
     /**
      * Flag to determine whether to include attached messages
      * as body in the returned object. Depends on $_include_bodies
@@ -176,6 +177,12 @@ class Mail_mimeDecode extends PEAR
         $this->_include_bodies = true;
         $this->_rfc822_bodies  = false;
     }
+    // BC
+    function Mail_mimeDecode($input)
+    {
+        $this->__construct($input);
+    }
+
 
     /**
      * Begins the decoding process. If called statically
@@ -188,7 +195,9 @@ class Mail_mimeDecode extends PEAR
      *                               object.
      *              decode_bodies  - Whether to decode the bodies
      *                               of the parts. (Transfer encoding)
-     *              decode_headers - Whether to decode headers
+     *              decode_headers - Whether to decode headers,
+     *                             - use "UTF8//IGNORE" to convert charset.
+     *
      *              input          - If called statically, this will be treated
      *                               as the input
      * @return object Decoded results
@@ -197,7 +206,7 @@ class Mail_mimeDecode extends PEAR
     function decode($params = null)
     {
         // determine if this method has been called statically
-        $isStatic = !(isset($this) && get_class($this) == __CLASS__);
+        $isStatic = empty($this) || !is_a($this, __CLASS__);
 
         // Have we been called statically?
 	// If so, create an object and pass details to that.
@@ -221,6 +230,10 @@ class Mail_mimeDecode extends PEAR
             $this->_rfc822_bodies  = isset($params['rfc_822bodies']) ?
 	                             $params['rfc_822bodies']  : false;
 
+            if (is_string($this->_decode_headers) && !function_exists('iconv')) {
+                 PEAR::raiseError('header decode conversion requested, however iconv is missing');
+            }
+
             $structure = $this->_decode($this->_header, $this->_body);
             if ($structure === false) {
                 $structure = $this->raiseError($this->_error);
@@ -247,6 +260,7 @@ class Mail_mimeDecode extends PEAR
         $headers = $this->_parseHeaders($headers);
 
         foreach ($headers as $value) {
+            $value['value'] =  $this->_decodeHeader($value['value']);
             if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
                 $return->headers[strtolower($value['name'])]   = array($return->headers[strtolower($value['name'])]);
                 $return->headers[strtolower($value['name'])][] = $value['value'];
@@ -259,8 +273,8 @@ class Mail_mimeDecode extends PEAR
             }
         }
 
-        reset($headers);
-        while (list($key, $value) = each($headers)) {
+
+        foreach ($headers as $key => $value) {
             $headers[$key]['name'] = strtolower($headers[$key]['name']);
             switch ($headers[$key]['name']) {
 
@@ -273,7 +287,7 @@ class Mail_mimeDecode extends PEAR
                     }
 
                     if (isset($content_type['other'])) {
-                        while (list($p_name, $p_value) = each($content_type['other'])) {
+                        foreach($content_type['other'] as $p_name => $p_value) {
                             $return->ctype_parameters[$p_name] = $p_value;
                         }
                     }
@@ -283,7 +297,7 @@ class Mail_mimeDecode extends PEAR
                     $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
                     $return->disposition   = $content_disposition['value'];
                     if (isset($content_disposition['other'])) {
-                        while (list($p_name, $p_value) = each($content_disposition['other'])) {
+                        foreach($content_disposition['other'] as $p_name => $p_value) {
                             $return->d_parameters[$p_name] = $p_value;
                         }
                     }
@@ -307,6 +321,14 @@ class Mail_mimeDecode extends PEAR
                     $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
                     break;
 
+                case 'multipart/signed': // PGP
+                    $parts = $this->_boundarySplit($body, $content_type['other']['boundary'], true);
+                    $return->parts['msg_body'] = $parts[0];
+                    list($part_header, $part_body) = $this->_splitBodyHeader($parts[1]);
+                    $return->parts['sig_hdr'] = $part_header;
+                    $return->parts['sig_body'] = $part_body;
+                    break;
+
                 case 'multipart/parallel':
                 case 'multipart/appledouble': // Appledouble mail
                 case 'multipart/report': // RFC1892
@@ -314,7 +336,9 @@ class Mail_mimeDecode extends PEAR
                 case 'multipart/digest':
                 case 'multipart/alternative':
                 case 'multipart/related':
+                case 'multipart/relative': //#20431 - android
                 case 'multipart/mixed':
+                case 'application/vnd.wap.multipart.related':
                     if(!isset($content_type['other']['boundary'])){
                         $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
                         return false;
@@ -333,6 +357,7 @@ class Mail_mimeDecode extends PEAR
                     break;
 
                 case 'message/rfc822':
+                case 'message/delivery-status': // #bug #18693
 					if ($this->_rfc822_bodies) {
 						$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
 						$return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body);
@@ -379,7 +404,7 @@ class Mail_mimeDecode extends PEAR
             }
             for ($i = 0; $i < count($structure->parts); $i++) {
 
-            
+
                 if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
                     $prepend      = $prepend . $mime_number . '.';
                     $_mime_number = '';
@@ -399,7 +424,7 @@ class Mail_mimeDecode extends PEAR
             $structure->mime_id = $prepend . $mime_number;
             $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
         }
-        
+
         return $return;
     }
 
@@ -417,6 +442,11 @@ class Mail_mimeDecode extends PEAR
         if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
             return array($match[1], $match[2]);
         }
+        // bug #17325 - empty bodies are allowed. - we just check that at least one line
+        // of headers exist..
+        if (count(explode("\n",$input))) {
+            return array($input, '');
+        }
         $this->_error = 'Could not split header and body';
         return false;
     }
@@ -435,18 +465,36 @@ class Mail_mimeDecode extends PEAR
         if ($input !== '') {
             // Unfold the input
             $input   = preg_replace("/\r?\n/", "\r\n", $input);
+            //#7065 - wrapping.. with encoded stuff.. - probably not needed,
+            // wrapping space should only get removed if the trailing item on previous line is a
+            // encoded character
+            $input   = preg_replace("/=\r\n(\t| )+/", '=', $input);
             $input   = preg_replace("/\r\n(\t| )+/", ' ', $input);
-            $headers = explode("\r\n", trim($input));
 
+            $headers = explode("\r\n", trim($input));
+            $got_start = false;
             foreach ($headers as $value) {
+                if (!$got_start) {
+                    // munge headers for mbox style from
+                    if ($value[0] == '>') {
+                        $value = substring($value, 1); // remove mbox >
+                    }
+                    if (substr($value,0,5) == 'From ') {
+                        $value = 'Return-Path: ' . substr($value, 5);
+                    } else {
+                        $got_start = true;
+                    }
+                }
+
                 $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
                 $hdr_value = substr($value, $pos+1);
-                if($hdr_value[0] == ' ')
+                if($hdr_value[0] == ' ') {
                     $hdr_value = substr($hdr_value, 1);
+                }
 
                 $return[] = array(
                                   'name'  => $hdr_name,
-                                  'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
+                                  'value' =>  $hdr_value
                                  );
             }
         } else {
@@ -463,46 +511,190 @@ class Mail_mimeDecode extends PEAR
      * robust as it could be. Eg. header comments
      * in the wrong place will probably break it.
      *
+     * Extra things this can handle
+     *   filename*0=......
+     *   filename*1=......
+     *
+     *  This is where lines are broken in, and need merging.
+     *
+     *   filename*0*=ENC'lang'urlencoded data.
+     *   filename*1*=ENC'lang'urlencoded data.
+     *
+     *
+     *
      * @param string Header value to parse
      * @return array Contains parsed result
      * @access private
      */
     function _parseHeaderValue($input)
     {
+         if (($pos = strpos($input, ';')) === false) {
+            $input = $this->_decodeHeader($input);
+            $return['value'] = trim($input);
+            return $return;
+        }
 
-        if (($pos = strpos($input, ';')) !== false) {
 
-            $return['value'] = trim(substr($input, 0, $pos));
-            $input = trim(substr($input, $pos+1));
 
-            if (strlen($input) > 0) {
+        $value = substr($input, 0, $pos);
+        $value = $this->_decodeHeader($value);
+        $return['value'] = trim($value);
+        $input = trim(substr($input, $pos+1));
 
-                // This splits on a semi-colon, if there's no preceeding backslash
-                // Now works with quoted values; had to glue the \; breaks in PHP
-                // the regex is already bordering on incomprehensible
-                $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
-                preg_match_all($splitRegex, $input, $matches);
-                $parameters = array();
-                for ($i=0; $i<count($matches[0]); $i++) {
-                    $param = $matches[0][$i];
-                    while (substr($param, -2) == '\;') {
-                        $param .= $matches[0][++$i];
-                    }
-                    $parameters[] = $param;
+        if (!strlen($input) > 0) {
+            return $return;
+        }
+        // at this point input contains xxxx=".....";zzzz="...."
+        // since we are dealing with quoted strings, we need to handle this properly..
+        $i = 0;
+        $l = strlen($input);
+        $key = '';
+        $val = false; // our string - including quotes..
+        $q = false; // in quote..
+        $lq = ''; // last quote..
+
+        while ($i < $l) {
+
+            $c = $input[$i];
+            //var_dump(array('i'=>$i,'c'=>$c,'q'=>$q, 'lq'=>$lq, 'key'=>$key, 'val' =>$val));
+
+            $escaped = false;
+            if ($c == '\\') {
+                $i++;
+                if ($i == $l-1) { // end of string.
+                    break;
                 }
+                $escaped = true;
+                $c = $input[$i];
+            }
+
 
-                for ($i = 0; $i < count($parameters); $i++) {
-                    $param_name  = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ ");
-                    $param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ ");
-                    if (!empty($param_value[0]) && $param_value[0] == '"') {
-                        $param_value = substr($param_value, 1, -1);
+            // state - in key..
+            if ($val === false) {
+                if (!$escaped && $c == '=') {
+                    $val = '';
+                    $key = trim($key);
+                    $i++;
+                    continue;
+                }
+                if (!$escaped && $c == ';') {
+                    if ($key) { // a key without a value..
+                        $key= trim($key);
+                        $return['other'][$key] = '';
                     }
-                    $return['other'][$param_name] = $param_value;
-                    $return['other'][strtolower($param_name)] = $param_value;
+                    $key = '';
                 }
+                $key .= $c;
+                $i++;
+                continue;
             }
-        } else {
-            $return['value'] = trim($input);
+
+            // state - in value.. (as $val is set..)
+
+            if ($q === false) {
+                // not in quote yet.
+                if ((!strlen($val) || $lq !== false) && $c == ' ' ||  $c == "\t") {
+                    $i++;
+                    continue; // skip leading spaces after '=' or after '"'
+                }
+
+                // do not de-quote 'xxx*= itesm..
+                $key_is_trans = $key[strlen($key)-1] == '*';
+
+                if (!$key_is_trans && !$escaped && ($c == '"' || $c == "'")) {
+                    // start quoted area..
+                    $q = $c;
+                    // in theory should not happen raw text in value part..
+                    // but we will handle it as a merged part of the string..
+                    $val = !strlen(trim($val)) ? '' : trim($val);
+                    $i++;
+                    continue;
+                }
+                // got end....
+                if (!$escaped && $c == ';') {
+
+                    $return['other'][$key] = trim($val);
+                    $val = false;
+                    $key = '';
+                    $lq = false;
+                    $i++;
+                    continue;
+                }
+
+                $val .= $c;
+                $i++;
+                continue;
+            }
+
+            // state - in quote..
+            if (!$escaped && $c == $q) {  // potential exit state..
+
+                // end of quoted string..
+                $lq = $q;
+                $q = false;
+                $i++;
+                continue;
+            }
+
+            // normal char inside of quoted string..
+            $val.= $c;
+            $i++;
+        }
+
+        // do we have anything left..
+        if (strlen(trim($key)) || $val !== false) {
+
+            $val = trim($val);
+
+            $return['other'][$key] = $val;
+        }
+
+
+        $clean_others = array();
+        // merge added values. eg. *1[*]
+        foreach($return['other'] as $key =>$val) {
+            if (preg_match('/\*[0-9]+\**$/', $key)) {
+                $key = preg_replace('/(.*)\*[0-9]+(\**)$/', '\1\2', $key);
+                if (isset($clean_others[$key])) {
+                    $clean_others[$key] .= $val;
+                    continue;
+                }
+
+            }
+            $clean_others[$key] = $val;
+
+        }
+
+        // handle language translation of '*' ending others.
+        foreach( $clean_others as $key =>$val) {
+            if ( $key[strlen($key)-1] != '*') {
+                $clean_others[strtolower($key)] = $val;
+                continue;
+            }
+            unset($clean_others[$key]);
+            $key = substr($key,0,-1);
+            //extended-initial-value := [charset] "'" [language] "'"
+            //              extended-other-values
+            $match = array();
+            $info = preg_match("/^([^']+)'([^']*)'(.*)$/", $val, $match);
+
+            $clean_others[$key] = urldecode($match[3]);
+            $clean_others[strtolower($key)] = $clean_others[$key];
+            $clean_others[strtolower($key).'-charset'] = $match[1];
+            $clean_others[strtolower($key).'-language'] = $match[2];
+
+
+        }
+
+
+        $return['other'] = $clean_others;
+
+        // decode values.
+        foreach($return['other'] as $key =>$val) {
+            $charset = isset($return['other'][$key . '-charset']) ?
+                $return['other'][$key . '-charset']  : false;
+
+            $return['other'][$key] = $this->_decodeHeader($val, $charset);
         }
 
         return $return;
@@ -516,7 +708,7 @@ class Mail_mimeDecode extends PEAR
      * @return array Contains array of resulting mime parts
      * @access private
      */
-    function _boundarySplit($input, $boundary)
+    function _boundarySplit($input, $boundary, $eatline = false)
     {
         $parts = array();
 
@@ -526,13 +718,22 @@ class Mail_mimeDecode extends PEAR
         if ($boundary == $bs_check) {
             $boundary = $bs_possible;
         }
-
-        $tmp = explode('--' . $boundary, $input);
-
-        for ($i = 1; $i < count($tmp) - 1; $i++) {
-            $parts[] = $tmp[$i];
+        // eatline is used by multipart/signed.
+        $tmp = $eatline ?
+            preg_split("/\r?\n--".preg_quote($boundary, '/')."(|--)\n/", $input) :
+            preg_split("/--".preg_quote($boundary, '/')."((?=\s)|--)/", $input);
+
+        $len = count($tmp) -1;
+        for ($i = 1; $i < $len; $i++) {
+            if (strlen(trim($tmp[$i]))) {
+                $parts[] = $tmp[$i];
+            }
         }
 
+        // add the last part on if it does not end with the 'closing indicator'
+        if (!empty($tmp[$len]) && strlen(trim($tmp[$len])) && $tmp[$len][0] != '-') {
+            $parts[] = $tmp[$len];
+        }
         return $parts;
     }
 
@@ -546,8 +747,11 @@ class Mail_mimeDecode extends PEAR
      * @return string Decoded header value
      * @access private
      */
-    function _decodeHeader($input)
+    function _decodeHeader($input, $default_charset=false)
     {
+        if (!$this->_decode_headers) {
+            return $input;
+        }
         // Remove white space between encoded-words
         $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
 
@@ -571,10 +775,18 @@ class Mail_mimeDecode extends PEAR
                         $text = str_replace('='.$value, chr(hexdec($value)), $text);
                     break;
             }
-
+            if (is_string($this->_decode_headers)) {
+                $conv = @iconv($charset, $this->_decode_headers, $text);
+                $text = ($conv === false) ? $text : $conv;
+            }
             $input = str_replace($encoded, $text, $input);
         }
 
+        if ($default_charset  && is_string($this->_decode_headers)) {
+            $conv = @iconv($charset, $this->_decode_headers, $input);
+            $input = ($conv === false) ? $input : $conv;
+        }
+
         return $input;
     }
 
@@ -621,9 +833,10 @@ class Mail_mimeDecode extends PEAR
         $input = preg_replace("/=\r?\n/", '', $input);
 
         // Replace encoded characters
-        $input = preg_replace_callback('/=([a-f0-9]{2})/i', function ($matches) {
-            return chr(hexdec($matches[1]));
-        }, $input);
+
+        $cb = create_function('$matches',  ' return chr(hexdec($matches[0]));');
+
+        $input = preg_replace_callback( '/=([a-f0-9]{2})/i', $cb, $input);
 
         return $input;
     }
@@ -632,7 +845,7 @@ class Mail_mimeDecode extends PEAR
      * Checks the input for uuencoded files and returns
      * an array of them. Can be called statically, eg:
      *
-     * $files = Mail_mimeDecode::uudecode($some_text);
+     * $files =& Mail_mimeDecode::uudecode($some_text);
      *
      * It will check for the begin 666 ... end syntax
      * however and won't just blindly decode whatever you
@@ -705,7 +918,7 @@ class Mail_mimeDecode extends PEAR
 
     /**
      * getSendArray() returns the arguments required for Mail::send()
-     * used to build the arguments for a mail::send() call 
+     * used to build the arguments for a mail::send() call
      *
      * Usage:
      * $mailtext = Full email (for example generated by a template)
@@ -747,7 +960,7 @@ class Mail_mimeDecode extends PEAR
         }
         $to = substr($to,1);
         return array($to,$header,$this->_body);
-    } 
+    }
 
     /**
      * Returns a xml copy of the output of
diff --git a/civicrm/packages/Mail/mimePart.php b/civicrm/packages/Mail/mimePart.php
index dcf3de53c4..187b1652da 100644
--- a/civicrm/packages/Mail/mimePart.php
+++ b/civicrm/packages/Mail/mimePart.php
@@ -8,7 +8,7 @@
  * of mime mail.
  * This class however allows full control over the email.
  *
- * Compatible with PHP versions 4 and 5
+ * Compatible with PHP version 5 and 7
  *
  * LICENSE: This LICENSE is in the BSD license style.
  * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
@@ -48,10 +48,16 @@
  * @author    Aleksander Machniak <alec@php.net>
  * @copyright 2003-2006 PEAR <pear-group@php.net>
  * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version   CVS: $Id: mimePart.php 301061 2010-07-07 18:20:17Z alec $
+ * @version   Release: @package_version@
  * @link      http://pear.php.net/package/Mail_mime
  */
 
+/**
+ * Require PEAR
+ *
+ * This package depends on PEAR to raise errors.
+ */
+require_once 'PEAR.php';
 
 /**
  * The Mail_mimePart class is used to create MIME E-mail messages
@@ -76,105 +82,108 @@
 class Mail_mimePart
 {
     /**
-    * The encoding type of this part
-    *
-    * @var string
-    * @access private
-    */
-    var $_encoding;
+     * The encoding type of this part
+     *
+     * @var string
+     */
+    protected $encoding;
+
+    /**
+     * An array of subparts
+     *
+     * @var array
+     */
+    protected $subparts = array();
 
     /**
-    * An array of subparts
-    *
-    * @var array
-    * @access private
-    */
-    var $_subparts;
+     * The output of this part after being built
+     *
+     * @var string
+     */
+    protected $encoded;
 
     /**
-    * The output of this part after being built
-    *
-    * @var string
-    * @access private
-    */
-    var $_encoded;
+     * Headers for this part
+     *
+     * @var array
+     */
+    protected $headers = array();
 
     /**
-    * Headers for this part
-    *
-    * @var array
-    * @access private
-    */
-    var $_headers;
+     * The body of this part (not encoded)
+     *
+     * @var string
+     */
+    protected $body;
 
     /**
-    * The body of this part (not encoded)
-    *
-    * @var string
-    * @access private
-    */
-    var $_body;
+     * The location of file with body of this part (not encoded)
+     *
+     * @var string
+     */
+    protected $body_file;
 
     /**
-    * The location of file with body of this part (not encoded)
-    *
-    * @var string
-    * @access private
-    */
-    var $_body_file;
+     * The short text of multipart part preamble (RFC2046 5.1.1)
+     *
+     * @var string
+     */
+    protected $preamble;
 
     /**
-    * The end-of-line sequence
-    *
-    * @var string
-    * @access private
-    */
-    var $_eol = "\r\n";
+     * The end-of-line sequence
+     *
+     * @var string
+     */
+    protected $eol = "\r\n";
+
 
     /**
-    * Constructor.
-    *
-    * Sets up the object.
-    *
-    * @param string $body   The body of the mime part if any.
-    * @param array  $params An associative array of optional parameters:
-    *     content_type      - The content type for this part eg multipart/mixed
-    *     encoding          - The encoding to use, 7bit, 8bit,
-    *                         base64, or quoted-printable
-    *     cid               - Content ID to apply
-    *     disposition       - Content disposition, inline or attachment
-    *     dfilename         - Filename parameter for content disposition
-    *     description       - Content description
-    *     charset           - Character set to use
-    *     name_encoding     - Encoding for attachment name (Content-Type)
-    *                         By default filenames are encoded using RFC2231
-    *                         Here you can set RFC2047 encoding (quoted-printable
-    *                         or base64) instead
-    *     filename_encoding - Encoding for attachment filename (Content-Disposition)
-    *                         See 'name_encoding'
-    *     eol               - End of line sequence. Default: "\r\n"
-    *     body_file         - Location of file with part's body (instead of $body)
-    *
-    * @access public
-    */
-    function __construct($body = '', $params = array())
+     * Constructor.
+     *
+     * Sets up the object.
+     *
+     * @param string $body   The body of the mime part if any.
+     * @param array  $params An associative array of optional parameters:
+     *     content_type      - The content type for this part eg multipart/mixed
+     *     encoding          - The encoding to use, 7bit, 8bit,
+     *                         base64, or quoted-printable
+     *     charset           - Content character set
+     *     cid               - Content ID to apply
+     *     disposition       - Content disposition, inline or attachment
+     *     filename          - Filename parameter for content disposition
+     *     description       - Content description
+     *     name_encoding     - Encoding of the attachment name (Content-Type)
+     *                         By default filenames are encoded using RFC2231
+     *                         Here you can set RFC2047 encoding (quoted-printable
+     *                         or base64) instead
+     *     filename_encoding - Encoding of the attachment filename (Content-Disposition)
+     *                         See 'name_encoding'
+     *     headers_charset   - Charset of the headers e.g. filename, description.
+     *                         If not set, 'charset' will be used
+     *     eol               - End of line sequence. Default: "\r\n"
+     *     headers           - Hash array with additional part headers. Array keys
+     *                         can be in form of <header_name>:<parameter_name>
+     *     body_file         - Location of file with part's body (instead of $body)
+     *     preamble          - short text of multipart part preamble (RFC2046 5.1.1)
+     */
+    public function __construct($body = '', $params = array())
     {
         if (!empty($params['eol'])) {
-            $this->_eol = $params['eol'];
+            $this->eol = $params['eol'];
         } else if (defined('MAIL_MIMEPART_CRLF')) { // backward-copat.
-            $this->_eol = MAIL_MIMEPART_CRLF;
+            $this->eol = MAIL_MIMEPART_CRLF;
+        }
+
+        // Additional part headers
+        if (!empty($params['headers']) && is_array($params['headers'])) {
+            $headers = $params['headers'];
         }
 
-        $c_type = array();
-        $c_disp = array();
         foreach ($params as $key => $value) {
             switch ($key) {
-            case 'content_type':
-                $c_type['type'] = $value;
-                break;
-
             case 'encoding':
-                $this->_encoding = $value;
+                $this->encoding = $value;
                 $headers['Content-Transfer-Encoding'] = $value;
                 break;
 
@@ -182,100 +191,110 @@ class Mail_mimePart
                 $headers['Content-ID'] = '<' . $value . '>';
                 break;
 
-            case 'disposition':
-                $c_disp['disp'] = $value;
-                break;
-
-            case 'dfilename':
-                $c_disp['filename'] = $value;
-                $c_type['name'] = $value;
-                break;
-
-            case 'description':
-                $headers['Content-Description'] = $value;
-                break;
-
-            case 'charset':
-                $c_type['charset'] = $value;
-                $c_disp['charset'] = $value;
+            case 'location':
+                $headers['Content-Location'] = $value;
                 break;
 
-            case 'language':
-                $c_type['language'] = $value;
-                $c_disp['language'] = $value;
+            case 'body_file':
+                $this->body_file = $value;
                 break;
 
-            case 'location':
-                $headers['Content-Location'] = $value;
+            case 'preamble':
+                $this->preamble = $value;
                 break;
 
-            case 'body_file':
-                $this->_body_file = $value;
+            // for backward compatibility
+            case 'dfilename':
+                $params['filename'] = $value;
                 break;
             }
         }
 
         // Default content-type
-        if (empty($c_type['type'])) {
-            $c_type['type'] = 'text/plain';
+        if (empty($params['content_type'])) {
+            $params['content_type'] = 'text/plain';
         }
 
         // Content-Type
-        if (!empty($c_type['type'])) {
-            $headers['Content-Type'] = $c_type['type'];
-            if (!empty($c_type['charset'])) {
-                $charset = "charset={$c_type['charset']}";
-                // place charset parameter in the same line, if possible
-                if ((strlen($headers['Content-Type']) + strlen($charset) + 16) <= 76) {
-                    $headers['Content-Type'] .= '; ';
-                } else {
-                    $headers['Content-Type'] .= ';' . $this->_eol . ' ';
-                }
-                $headers['Content-Type'] .= $charset;
+        $headers['Content-Type'] = $params['content_type'];
+        if (!empty($params['charset'])) {
+            $charset = "charset={$params['charset']}";
+            // place charset parameter in the same line, if possible
+            if ((strlen($headers['Content-Type']) + strlen($charset) + 16) <= 76) {
+                $headers['Content-Type'] .= '; ';
+            } else {
+                $headers['Content-Type'] .= ';' . $this->eol . ' ';
             }
-            if (!empty($c_type['name'])) {
-                $headers['Content-Type'] .= ';' . $this->_eol;
-                $headers['Content-Type'] .= $this->_buildHeaderParam(
-                    'name', $c_type['name'],
-                    isset($c_type['charset']) ? $c_type['charset'] : 'US-ASCII',
-                    isset($c_type['language']) ? $c_type['language'] : null,
-                    isset($params['name_encoding']) ?  $params['name_encoding'] : null
-                );
+            $headers['Content-Type'] .= $charset;
+
+            // Default headers charset
+            if (!isset($params['headers_charset'])) {
+                $params['headers_charset'] = $params['charset'];
             }
         }
 
+        // header values encoding parameters
+        $h_charset  = !empty($params['headers_charset']) ? $params['headers_charset'] : 'US-ASCII';
+        $h_language = !empty($params['language']) ? $params['language'] : null;
+        $h_encoding = !empty($params['name_encoding']) ? $params['name_encoding'] : null;
+
+        if (!empty($params['filename'])) {
+            $headers['Content-Type'] .= ';' . $this->eol;
+            $headers['Content-Type'] .= $this->buildHeaderParam(
+                'name', $params['filename'], $h_charset, $h_language, $h_encoding
+            );
+        }
+
         // Content-Disposition
-        if (!empty($c_disp['disp'])) {
-            $headers['Content-Disposition'] = $c_disp['disp'];
-            if (!empty($c_disp['filename'])) {
-                $headers['Content-Disposition'] .= ';' . $this->_eol;
-                $headers['Content-Disposition'] .= $this->_buildHeaderParam(
-                    'filename', $c_disp['filename'],
-                    isset($c_disp['charset']) ? $c_disp['charset'] : 'US-ASCII',
-                    isset($c_disp['language']) ? $c_disp['language'] : null,
-                    isset($params['filename_encoding']) ?  $params['filename_encoding'] : null
+        if (!empty($params['disposition'])) {
+            $headers['Content-Disposition'] = $params['disposition'];
+            if (!empty($params['filename'])) {
+                $headers['Content-Disposition'] .= ';' . $this->eol;
+                $headers['Content-Disposition'] .= $this->buildHeaderParam(
+                    'filename', $params['filename'], $h_charset, $h_language,
+                    !empty($params['filename_encoding']) ? $params['filename_encoding'] : null
                 );
             }
+
+            // add attachment size
+            $size = $this->body_file ? filesize($this->body_file) : strlen($body);
+            if ($size) {
+                $headers['Content-Disposition'] .= ';' . $this->eol . ' size=' . $size;
+            }
         }
 
-        if (!empty($headers['Content-Description'])) {
+        if (!empty($params['description'])) {
             $headers['Content-Description'] = $this->encodeHeader(
-                'Content-Description', $headers['Content-Description'],
-                isset($c_type['charset']) ? $c_type['charset'] : 'US-ASCII',
-                isset($params['name_encoding']) ?  $params['name_encoding'] : 'quoted-printable',
-                $this->_eol
+                'Content-Description', $params['description'], $h_charset, $h_encoding,
+                $this->eol
             );
         }
 
+        // Search and add existing headers' parameters
+        foreach ($headers as $key => $value) {
+            $items = explode(':', $key);
+            if (count($items) == 2) {
+                $header = $items[0];
+                $param  = $items[1];
+                if (isset($headers[$header])) {
+                    $headers[$header] .= ';' . $this->eol;
+                }
+                $headers[$header] .= $this->buildHeaderParam(
+                    $param, $value, $h_charset, $h_language, $h_encoding
+                );
+                unset($headers[$key]);
+            }
+        }
+
         // Default encoding
-        if (!isset($this->_encoding)) {
-            $this->_encoding = '7bit';
+        if (!isset($this->encoding)) {
+            $this->encoding = '7bit';
         }
 
         // Assign stuff to member variables
-        $this->_encoded  = array();
-        $this->_headers  = $headers;
-        $this->_body     = $body;
+        $this->encoded  = array();
+        $this->headers  = $headers;
+        $this->body     = $body;
     }
 
     /**
@@ -287,24 +306,27 @@ class Mail_mimePart
      * @return An associative array containing two elements,
      *         body and headers. The headers element is itself
      *         an indexed array. On error returns PEAR error object.
-     * @access public
      */
-    function encode($boundary=null)
+    public function encode($boundary=null)
     {
-        $encoded =& $this->_encoded;
+        $encoded =& $this->encoded;
 
-        if (count($this->_subparts)) {
+        if (count($this->subparts)) {
             $boundary = $boundary ? $boundary : '=_' . md5(rand() . microtime());
-            $eol = $this->_eol;
+            $eol = $this->eol;
 
-            $this->_headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
+            $this->headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
 
             $encoded['body'] = '';
 
-            for ($i = 0; $i < count($this->_subparts); $i++) {
+            if ($this->preamble) {
+                $encoded['body'] .= $this->preamble . $eol . $eol;
+            }
+
+            for ($i = 0; $i < count($this->subparts); $i++) {
                 $encoded['body'] .= '--' . $boundary . $eol;
-                $tmp = $this->_subparts[$i]->encode();
-                if (PEAR::isError($tmp)) {
+                $tmp = $this->subparts[$i]->encode();
+                if (is_a($tmp, 'PEAR_Error')) {
                     return $tmp;
                 }
                 foreach ($tmp['headers'] as $key => $value) {
@@ -314,20 +336,19 @@ class Mail_mimePart
             }
 
             $encoded['body'] .= '--' . $boundary . '--' . $eol;
-
-        } else if ($this->_body) {
-            $encoded['body'] = $this->_getEncodedData($this->_body, $this->_encoding);
-        } else if ($this->_body_file) {
+        } else if ($this->body) {
+            $encoded['body'] = $this->getEncodedData($this->body, $this->encoding);
+        } else if ($this->body_file) {
             // Temporarily reset magic_quotes_runtime for file reads and writes
             if ($magic_quote_setting = get_magic_quotes_runtime()) {
                 @ini_set('magic_quotes_runtime', 0);
             }
-            $body = $this->_getEncodedDataFromFile($this->_body_file, $this->_encoding);
+            $body = $this->getEncodedDataFromFile($this->body_file, $this->encoding);
             if ($magic_quote_setting) {
                 @ini_set('magic_quotes_runtime', $magic_quote_setting);
             }
 
-            if (PEAR::isError($body)) {
+            if (is_a($body, 'PEAR_Error')) {
                 return $body;
             }
             $encoded['body'] = $body;
@@ -336,34 +357,38 @@ class Mail_mimePart
         }
 
         // Add headers to $encoded
-        $encoded['headers'] =& $this->_headers;
+        $encoded['headers'] =& $this->headers;
 
         return $encoded;
     }
 
     /**
-     * Encodes and saves the email into file. File must exist.
-     * Data will be appended to the file.
+     * Encodes and saves the email into file or stream.
+     * Data will be appended to the file/stream.
      *
-     * @param string  $filename  Output file location
+     * @param mixed   $filename  Existing file location
+     *                           or file pointer resource
      * @param string  $boundary  Pre-defined boundary string
      * @param boolean $skip_head True if you don't want to save headers
      *
      * @return array An associative array containing message headers
      *               or PEAR error object
-     * @access public
-     * @since 1.6.0
+     * @since  1.6.0
      */
-    function encodeToFile($filename, $boundary=null, $skip_head=false)
+    public function encodeToFile($filename, $boundary = null, $skip_head = false)
     {
-        if (file_exists($filename) && !is_writable($filename)) {
-            $err = PEAR::raiseError('File is not writeable: ' . $filename);
-            return $err;
-        }
+        if (!is_resource($filename)) {
+            if (file_exists($filename) && !is_writable($filename)) {
+                $err = self::raiseError('File is not writeable: ' . $filename);
+                return $err;
+            }
 
-        if (!($fh = fopen($filename, 'ab'))) {
-            $err = PEAR::raiseError('Unable to open file: ' . $filename);
-            return $err;
+            if (!($fh = fopen($filename, 'ab'))) {
+                $err = self::raiseError('Unable to open file: ' . $filename);
+                return $err;
+            }
+        } else {
+            $fh = $filename;
         }
 
         // Temporarily reset magic_quotes_runtime for file reads and writes
@@ -371,15 +396,17 @@ class Mail_mimePart
             @ini_set('magic_quotes_runtime', 0);
         }
 
-        $res = $this->_encodePartToFile($fh, $boundary, $skip_head);
+        $res = $this->encodePartToFile($fh, $boundary, $skip_head);
 
-        fclose($fh);
+        if (!is_resource($filename)) {
+            fclose($fh);
+        }
 
         if ($magic_quote_setting) {
             @ini_set('magic_quotes_runtime', $magic_quote_setting);
         }
 
-        return PEAR::isError($res) ? $res : $this->_headers;
+        return is_a($res, 'PEAR_Error') ? $res : $this->headers;
     }
 
     /**
@@ -390,19 +417,18 @@ class Mail_mimePart
      * @param boolean $skip_head True if you don't want to save headers
      *
      * @return array True on sucess or PEAR error object
-     * @access private
      */
-    function _encodePartToFile($fh, $boundary=null, $skip_head=false)
+    protected function encodePartToFile($fh, $boundary = null, $skip_head = false)
     {
-        $eol = $this->_eol;
+        $eol = $this->eol;
 
-        if (count($this->_subparts)) {
+        if (count($this->subparts)) {
             $boundary = $boundary ? $boundary : '=_' . md5(rand() . microtime());
-            $this->_headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
+            $this->headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
         }
 
         if (!$skip_head) {
-            foreach ($this->_headers as $key => $value) {
+            foreach ($this->headers as $key => $value) {
                 fwrite($fh, $key . ': ' . $value . $eol);
             }
             $f_eol = $eol;
@@ -410,26 +436,31 @@ class Mail_mimePart
             $f_eol = '';
         }
 
-        if (count($this->_subparts)) {
-            for ($i = 0; $i < count($this->_subparts); $i++) {
+        if (count($this->subparts)) {
+            if ($this->preamble) {
+                fwrite($fh, $f_eol . $this->preamble . $eol);
+                $f_eol = $eol;
+            }
+
+            for ($i = 0; $i < count($this->subparts); $i++) {
                 fwrite($fh, $f_eol . '--' . $boundary . $eol);
-                $res = $this->_subparts[$i]->_encodePartToFile($fh);
-                if (PEAR::isError($res)) {
+                $res = $this->subparts[$i]->encodePartToFile($fh);
+                if (is_a($res, 'PEAR_Error')) {
                     return $res;
                 }
                 $f_eol = $eol;
             }
 
             fwrite($fh, $eol . '--' . $boundary . '--' . $eol);
-
-        } else if ($this->_body) {
-            fwrite($fh, $f_eol . $this->_getEncodedData($this->_body, $this->_encoding));
-        } else if ($this->_body_file) {
+        } else if ($this->body) {
+            fwrite($fh, $f_eol);
+            fwrite($fh, $this->getEncodedData($this->body, $this->encoding));
+        } else if ($this->body_file) {
             fwrite($fh, $f_eol);
-            $res = $this->_getEncodedDataFromFile(
-                $this->_body_file, $this->_encoding, $fh
+            $res = $this->getEncodedDataFromFile(
+                $this->body_file, $this->encoding, $fh
             );
-            if (PEAR::isError($res)) {
+            if (is_a($res, 'PEAR_Error')) {
                 return $res;
             }
         }
@@ -441,20 +472,23 @@ class Mail_mimePart
      * Adds a subpart to current mime part and returns
      * a reference to it
      *
-     * @param string $body   The body of the subpart, if any.
-     * @param array  $params The parameters for the subpart, same
-     *                       as the $params argument for constructor.
+     * @param mixed $body   The body of the subpart or Mail_mimePart object
+     * @param array $params The parameters for the subpart, same
+     *                      as the $params argument for constructor
      *
-     * @return Mail_mimePart A reference to the part you just added. It is
-     *                       crucial if using multipart/* in your subparts that
-     *                       you use =& in your script when calling this function,
-     *                       otherwise you will not be able to add further subparts.
-     * @access public
+     * @return Mail_mimePart A reference to the part you just added.
      */
-    function &addSubpart($body, $params)
+    public function addSubpart($body, $params = null)
     {
-        $this->_subparts[] = new Mail_mimePart($body, $params);
-        return $this->_subparts[count($this->_subparts) - 1];
+        if ($body instanceof Mail_mimePart) {
+            $part = $body;
+        } else {
+            $part = new Mail_mimePart($body, $params);
+        }
+
+        $this->subparts[] = $part;
+
+        return $part;
     }
 
     /**
@@ -464,18 +498,17 @@ class Mail_mimePart
      * @param string $encoding The encoding type to use, 7bit, base64,
      *                         or quoted-printable.
      *
-     * @return string
-     * @access private
+     * @return string Encoded data string
      */
-    function _getEncodedData($data, $encoding)
+    protected function getEncodedData($data, $encoding)
     {
         switch ($encoding) {
         case 'quoted-printable':
-            return $this->_quotedPrintableEncode($data);
+            return self::quotedPrintableEncode($data, 76, $this->eol);
             break;
 
         case 'base64':
-            return rtrim(chunk_split(base64_encode($data), 76, $this->_eol));
+            return rtrim(chunk_split(base64_encode($data), 76, $this->eol));
             break;
 
         case '8bit':
@@ -495,17 +528,16 @@ class Mail_mimePart
      *                           stored into it instead of returning it
      *
      * @return string Encoded data or PEAR error object
-     * @access private
      */
-    function _getEncodedDataFromFile($filename, $encoding, $fh=null)
+    protected function getEncodedDataFromFile($filename, $encoding, $fh = null)
     {
         if (!is_readable($filename)) {
-            $err = PEAR::raiseError('Unable to read file: ' . $filename);
+            $err = self::raiseError('Unable to read file: ' . $filename);
             return $err;
         }
 
         if (!($fd = fopen($filename, 'rb'))) {
-            $err = PEAR::raiseError('Could not open file: ' . $filename);
+            $err = self::raiseError('Could not open file: ' . $filename);
             return $err;
         }
 
@@ -514,7 +546,7 @@ class Mail_mimePart
         switch ($encoding) {
         case 'quoted-printable':
             while (!feof($fd)) {
-                $buffer = $this->_quotedPrintableEncode(fgets($fd));
+                $buffer = self::quotedPrintableEncode(fgets($fd), 76, $this->eol);
                 if ($fh) {
                     fwrite($fh, $buffer);
                 } else {
@@ -530,7 +562,7 @@ class Mail_mimePart
                 // because base64 encoding is memory expensive
                 $buffer = fread($fd, 57 * 9198); // ca. 0.5 MB
                 $buffer = base64_encode($buffer);
-                $buffer = chunk_split($buffer, 76, $this->_eol);
+                $buffer = chunk_split($buffer, 76, $this->eol);
                 if (feof($fd)) {
                     $buffer = rtrim($buffer);
                 }
@@ -569,14 +601,12 @@ class Mail_mimePart
      * @param string $input    The data to encode
      * @param int    $line_max Optional max line length. Should
      *                         not be more than 76 chars
+     * @param string $eol      End-of-line sequence. Default: "\r\n"
      *
      * @return string Encoded data
-     *
-     * @access private
      */
-    function _quotedPrintableEncode($input , $line_max = 76)
+    public static function quotedPrintableEncode($input , $line_max = 76, $eol = "\r\n")
     {
-        $eol = $this->_eol;
         /*
         // imap_8bit() is extremely fast, but doesn't handle properly some characters
         if (function_exists('imap_8bit') && $line_max == 76) {
@@ -592,7 +622,7 @@ class Mail_mimePart
         $escape = '=';
         $output = '';
 
-        while (list($idx, $line) = each($lines)) {
+        foreach ($lines as $idx => $line) {
             $newline = '';
             $i = 0;
 
@@ -626,18 +656,22 @@ class Mail_mimePart
                     $output  .= $newline . $escape . $eol;
                     $newline  = '';
                 }
+
                 $newline .= $char;
             } // end of for
+
             $output .= $newline . $eol;
             unset($lines[$idx]);
         }
+
         // Don't want last crlf
         $output = substr($output, 0, -1 * strlen($eol));
+
         return $output;
     }
 
     /**
-     * Encodes the paramater of a header.
+     * Encodes the parameter of a header.
      *
      * @param string $name      The name of the header-parameter
      * @param string $value     The value of the paramter
@@ -648,17 +682,15 @@ class Mail_mimePart
      * @param int    $maxLength The maximum length of a line. Defauls to 75
      *
      * @return string
-     *
-     * @access private
      */
-    function _buildHeaderParam($name, $value, $charset=null, $language=null,
-        $encoding=null, $maxLength=75
+    protected function buildHeaderParam($name, $value, $charset = null,
+        $language = null, $encoding = null, $maxLength = 75
     ) {
         // RFC 2045:
         // value needs encoding if contains non-ASCII chars or is longer than 78 chars
         if (!preg_match('#[^\x20-\x7E]#', $value)) {
-            $token_regexp = '#([^\x21,\x23-\x27,\x2A,\x2B,\x2D'
-                . ',\x2E,\x30-\x39,\x41-\x5A,\x5E-\x7E])#';
+            $token_regexp = '#([^\x21\x23-\x27\x2A\x2B\x2D'
+                . '\x2E\x30-\x39\x41-\x5A\x5E-\x7E])#';
             if (!preg_match($token_regexp, $value)) {
                 // token
                 if (strlen($name) + strlen($value) + 3 <= $maxLength) {
@@ -675,13 +707,13 @@ class Mail_mimePart
 
         // RFC2047: use quoted-printable/base64 encoding
         if ($encoding == 'quoted-printable' || $encoding == 'base64') {
-            return $this->_buildRFC2047Param($name, $value, $charset, $encoding);
+            return $this->buildRFC2047Param($name, $value, $charset, $encoding);
         }
 
         // RFC2231:
         $encValue = preg_replace_callback(
-            '/([^\x21,\x23,\x24,\x26,\x2B,\x2D,\x2E,\x30-\x39,\x41-\x5A,\x5E-\x7E])/',
-            array($this, '_encodeReplaceCallback'), $value
+            '/([^\x21\x23\x24\x26\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E])/',
+            array($this, 'encodeReplaceCallback'), $value
         );
         $value = "$charset'$language'$encValue";
 
@@ -709,7 +741,7 @@ class Mail_mimePart
             $headCount++;
         }
 
-        $headers = implode(';' . $this->_eol, $headers);
+        $headers = implode(';' . $this->eol, $headers);
         return $headers;
     }
 
@@ -723,10 +755,9 @@ class Mail_mimePart
      * @param int    $maxLength Encoded parameter max length. Default: 76
      *
      * @return string Parameter line
-     * @access private
      */
-    function _buildRFC2047Param($name, $value, $charset,
-        $encoding='quoted-printable', $maxLength=76
+    protected function buildRFC2047Param($name, $value, $charset,
+        $encoding = 'quoted-printable', $maxLength = 76
     ) {
         // WARNING: RFC 2047 says: "An 'encoded-word' MUST NOT be used in
         // parameter of a MIME Content-Type or Content-Disposition field",
@@ -748,7 +779,7 @@ class Mail_mimePart
                 $_quote = substr($value, 0, $real_len);
                 $value = substr($value, $real_len);
 
-                $quoted .= $prefix . $_quote . $suffix . $this->_eol . ' ';
+                $quoted .= $prefix . $_quote . $suffix . $this->eol . ' ';
                 $add_len = strlen($prefix . $suffix) + 4; // 2 x SPACE, '"', ';'
                 $len = strlen($value) + $add_len;
             }
@@ -771,7 +802,7 @@ class Mail_mimePart
                     $_quote = $matches[1];
                 }
 
-                $quoted .= $prefix . $_quote . $suffix . $this->_eol . ' ';
+                $quoted .= $prefix . $_quote . $suffix . $this->eol . ' ';
                 $value = substr($value, strlen($_quote));
                 $add_len = strlen($prefix . $suffix) + 4; // 2 x SPACE, '"', ';'
                 $len = strlen($value) + $add_len;
@@ -792,18 +823,18 @@ class Mail_mimePart
      * @param string $encoding Encoding name (base64 or quoted-printable)
      * @param string $eol      End-of-line sequence. Default: "\r\n"
      *
-     * @return string          Encoded header data (without a name)
-     * @access public
-     * @since 1.6.1
+     * @return string Encoded header data (without a name)
+     * @since  1.6.1
      */
-    static function encodeHeader($name, $value, $charset='ISO-8859-1',
-        $encoding='quoted-printable', $eol="\r\n"
+    public static function encodeHeader($name, $value, $charset = 'ISO-8859-1',
+        $encoding = 'quoted-printable', $eol = "\r\n"
     ) {
         // Structured headers
         $comma_headers = array(
             'from', 'to', 'cc', 'bcc', 'sender', 'reply-to',
             'resent-from', 'resent-to', 'resent-cc', 'resent-bcc',
             'resent-sender', 'resent-reply-to',
+            'mail-reply-to', 'mail-followup-to',
             'return-receipt-to', 'disposition-notification-to',
         );
         $other_headers = array(
@@ -822,9 +853,20 @@ class Mail_mimePart
             $charset = 'ISO-8859-1';
         }
 
+        // exploding quoted strings as well as some regexes below do not
+        // work properly with some charset e.g. ISO-2022-JP, we'll use UTF-8
+        $mb = $charset != 'UTF-8' && function_exists('mb_convert_encoding');
+
         // Structured header (make sure addr-spec inside is not encoded)
         if (!empty($separator)) {
-            $parts = Mail_mimePart::_explodeQuotedString($separator, $value);
+            // Simple e-mail address regexp
+            $email_regexp = '([^\s<]+|("[^\r\n"]+"))@\S+';
+
+            if ($mb) {
+                $value = mb_convert_encoding($value, 'UTF-8', $charset);
+            }
+
+            $parts = Mail_mimePart::explodeQuotedString("[\t$separator]", $value);
             $value = '';
 
             foreach ($parts as $part) {
@@ -835,40 +877,44 @@ class Mail_mimePart
                     continue;
                 }
                 if ($value) {
-                    $value .= $separator==',' ? $separator.' ' : ' ';
+                    $value .= $separator == ',' ? $separator . ' ' : ' ';
                 } else {
                     $value = $name . ': ';
                 }
 
                 // let's find phrase (name) and/or addr-spec
-                if (preg_match('/^<\S+@\S+>$/', $part)) {
+                if (preg_match('/^<' . $email_regexp . '>$/', $part)) {
                     $value .= $part;
-                } else if (preg_match('/^\S+@\S+$/', $part)) {
+                } else if (preg_match('/^' . $email_regexp . '$/', $part)) {
                     // address without brackets and without name
                     $value .= $part;
-                } else if (preg_match('/<*\S+@\S+>*$/', $part, $matches)) {
+                } else if (preg_match('/<*' . $email_regexp . '>*$/', $part, $matches)) {
                     // address with name (handle name)
                     $address = $matches[0];
-                    $word = str_replace($address, '', $part);
-                    $word = trim($word);
+                    $word    = str_replace($address, '', $part);
+                    $word    = trim($word);
+
                     // check if phrase requires quoting
                     if ($word) {
                         // non-ASCII: require encoding
-                        if (preg_match('#([\x80-\xFF]){1}#', $word)) {
+                        if (preg_match('#([^\s\x21-\x7E]){1}#', $word)) {
                             if ($word[0] == '"' && $word[strlen($word)-1] == '"') {
                                 // de-quote quoted-string, encoding changes
                                 // string to atom
-                                $search = array("\\\"", "\\\\");
-                                $replace = array("\"", "\\");
-                                $word = str_replace($search, $replace, $word);
                                 $word = substr($word, 1, -1);
+                                $word = preg_replace('/\\\\([\\\\"])/', '$1', $word);
+                            }
+                            if ($mb) {
+                                $word = mb_convert_encoding($word, $charset, 'UTF-8');
                             }
+
                             // find length of last line
                             if (($pos = strrpos($value, $eol)) !== false) {
                                 $last_len = strlen($value) - $pos;
                             } else {
                                 $last_len = strlen($value);
                             }
+
                             $word = Mail_mimePart::encodeHeaderValue(
                                 $word, $charset, $encoding, $last_len, $eol
                             );
@@ -879,8 +925,12 @@ class Mail_mimePart
                             $word = '"'.addcslashes($word, '\\"').'"';
                         }
                     }
+
                     $value .= $word.' '.$address;
                 } else {
+                    if ($mb) {
+                        $part = mb_convert_encoding($part, $charset, 'UTF-8');
+                    }
                     // addr-spec not found, don't encode (?)
                     $value .= $part;
                 }
@@ -893,27 +943,31 @@ class Mail_mimePart
             $value = preg_replace(
                 '/^'.$name.':('.preg_quote($eol, '/').')* /', '', $value
             );
-
         } else {
             // Unstructured header
             // non-ASCII: require encoding
-            if (preg_match('#([\x80-\xFF]){1}#', $value)) {
+            if (preg_match('#([^\s\x21-\x7E]){1}#', $value)) {
                 if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
+                    if ($mb) {
+                        $value = mb_convert_encoding($value, 'UTF-8', $charset);
+                    }
                     // de-quote quoted-string, encoding changes
                     // string to atom
-                    $search = array("\\\"", "\\\\");
-                    $replace = array("\"", "\\");
-                    $value = str_replace($search, $replace, $value);
                     $value = substr($value, 1, -1);
+                    $value = preg_replace('/\\\\([\\\\"])/', '$1', $value);
+                    if ($mb) {
+                        $value = mb_convert_encoding($value, $charset, 'UTF-8');
+                    }
                 }
+
                 $value = Mail_mimePart::encodeHeaderValue(
                     $value, $charset, $encoding, strlen($name) + 2, $eol
                 );
             } else if (strlen($name.': '.$value) > 78) {
                 // ASCII: check if header line isn't too long and use folding
                 $value = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $value);
-                $tmp = wordwrap($name.': '.$value, 78, $eol . ' ');
-                $value = preg_replace('/^'.$name.':\s*/', '', $tmp);
+                $tmp   = wordwrap($name . ': ' . $value, 78, $eol . ' ');
+                $value = preg_replace('/^' . $name . ':\s*/', '', $tmp);
                 // hard limit 998 (RFC2822)
                 $value = wordwrap($value, 998, $eol . ' ', true);
             }
@@ -928,25 +982,26 @@ class Mail_mimePart
      * @param string $delimiter Delimiter expression string for preg_match()
      * @param string $string    Input string
      *
-     * @return array            String tokens array
-     * @access private
+     * @return array String tokens array
      */
-    static function _explodeQuotedString($delimiter, $string)
+    protected static function explodeQuotedString($delimiter, $string)
     {
         $result = array();
         $strlen = strlen($string);
+        $quoted_string = '"(?:[^"\\\\]|\\\\.)*"';
 
-        for ($q=$p=$i=0; $i < $strlen; $i++) {
-            if ($string[$i] == "\""
-                && (empty($string[$i-1]) || $string[$i-1] != "\\")
-            ) {
-                $q = $q ? false : true;
-            } else if (!$q && preg_match("/$delimiter/", $string[$i])) {
+        for ($p=$i=0; $i < $strlen; $i++) {
+            if ($string[$i] === '"') {
+                $r = preg_match("/$quoted_string/", $string, $matches, 0, $i);
+                if (!$r || empty($matches[0])) {
+                    break;
+                }
+                $i += strlen($matches[0]) - 1;
+            } else if (preg_match("/$delimiter/", $string[$i])) {
                 $result[] = substr($string, $p, $i - $p);
                 $p = $i + 1;
             }
         }
-
         $result[] = substr($string, $p);
         return $result;
     }
@@ -960,11 +1015,10 @@ class Mail_mimePart
      * @param int    $prefix_len Prefix length. Default: 0
      * @param string $eol        End-of-line sequence. Default: "\r\n"
      *
-     * @return string            Encoded header data
-     * @access public
-     * @since 1.6.1
+     * @return string Encoded header data
+     * @since  1.6.1
      */
-    static function encodeHeaderValue($value, $charset, $encoding, $prefix_len=0, $eol="\r\n")
+    public static function encodeHeaderValue($value, $charset, $encoding, $prefix_len = 0, $eol = "\r\n")
     {
         // #17311: Use multibyte aware method (requires mbstring extension)
         if ($result = Mail_mimePart::encodeMB($value, $charset, $encoding, $prefix_len, $eol)) {
@@ -998,7 +1052,7 @@ class Mail_mimePart
                 $value = substr($value, $cutpoint);
                 $cutpoint = $maxLength;
                 // RFC 2047 specifies that any split header should
-                // be seperated by a CRLF SPACE.
+                // be separated by a CRLF SPACE.
                 if ($output) {
                     $output .= $eol . ' ';
                 }
@@ -1040,7 +1094,7 @@ class Mail_mimePart
                     }
 
                     // RFC 2047 specifies that any split header should
-                    // be seperated by a CRLF SPACE
+                    // be separated by a CRLF SPACE
                     if ($output) {
                         $output .= $eol . ' ';
                     }
@@ -1060,11 +1114,10 @@ class Mail_mimePart
      *
      * @param string $str String to encode
      *
-     * @return string     Encoded string
-     * @access public
-     * @since 1.6.0
+     * @return string Encoded string
+     * @since  1.6.0
      */
-    function encodeQP($str)
+    public static function encodeQP($str)
     {
         // Bug #17226 RFC 2047 restricts some characters
         // if the word is inside a phrase, permitted chars are only:
@@ -1073,7 +1126,7 @@ class Mail_mimePart
         // "=",  "_",  "?" must be encoded
         $regexp = '/([\x22-\x29\x2C\x2E\x3A-\x40\x5B-\x60\x7B-\x7E\x80-\xFF])/';
         $str = preg_replace_callback(
-            $regexp, array('Mail_mimePart', '_qpReplaceCallback'), $str
+            $regexp, array('Mail_mimePart', 'qpReplaceCallback'), $str
         );
 
         return str_replace(' ', '_', $str);
@@ -1090,11 +1143,10 @@ class Mail_mimePart
      * @param int    $prefix_len Prefix length. Default: 0
      * @param string $eol        End-of-line sequence. Default: "\r\n"
      *
-     * @return string     Encoded string
-     * @access public
-     * @since 1.8.0
+     * @return string Encoded string
+     * @since  1.8.0
      */
-    static function encodeMB($str, $charset, $encoding, $prefix_len=0, $eol="\r\n")
+    public static function encodeMB($str, $charset, $encoding, $prefix_len=0, $eol="\r\n")
     {
         if (!function_exists('mb_substr') || !function_exists('mb_strlen')) {
             return;
@@ -1158,7 +1210,7 @@ class Mail_mimePart
                     $char_len = 1;
                 } else {
                     $char = preg_replace_callback(
-                        $regexp, array('Mail_mimePart', '_qpReplaceCallback'), $char
+                        $regexp, array('Mail_mimePart', 'qpReplaceCallback'), $char
                     );
                     $char_len = strlen($char);
                 }
@@ -1189,10 +1241,9 @@ class Mail_mimePart
      *
      * @param array $matches Preg_replace's matches array
      *
-     * @return string        Encoded character string
-     * @access private
+     * @return string Encoded character string
      */
-    static function _qpReplaceCallback($matches)
+    protected static function qpReplaceCallback($matches)
     {
         return sprintf('=%02X', ord($matches[1]));
     }
@@ -1203,12 +1254,23 @@ class Mail_mimePart
      *
      * @param array $matches Preg_replace's matches array
      *
-     * @return string        Encoded character string
-     * @access private
+     * @return string Encoded character string
      */
-    static function _encodeReplaceCallback($matches)
+    protected static function encodeReplaceCallback($matches)
     {
         return sprintf('%%%02X', ord($matches[1]));
     }
 
-} // End of class
+    /**
+     * PEAR::raiseError implementation
+     *
+     * @param string $message A text error message
+     *
+     * @return PEAR_Error Instance of PEAR_Error
+     */
+    public static function raiseError($message)
+    {
+        // PEAR::raiseError() is not PHP 5.4 compatible
+        return new PEAR_Error($message);
+    }
+}
diff --git a/civicrm/release-notes.md b/civicrm/release-notes.md
index b9971abd35..f32d882167 100644
--- a/civicrm/release-notes.md
+++ b/civicrm/release-notes.md
@@ -14,6 +14,36 @@ Other resources for identifying changes are:
     * https://github.com/civicrm/civicrm-joomla
     * https://github.com/civicrm/civicrm-wordpress
 
+## CiviCRM 5.4.0
+
+Released August 1, 2018
+
+- **[Synopsis](release-notes/5.4.0.md#synopsis)**
+- **[Features](release-notes/5.4.0.md#features)**
+- **[Bugs resolved](release-notes/5.4.0.md#bugs)**
+- **[Miscellany](release-notes/5.4.0.md#misc)**
+- **[Credits](release-notes/5.4.0.md#credits)**
+- **[Feedback](release-notes/5.4.0.md#feedback)**
+
+## CiviCRM 5.3.2
+
+Released July 25, 2018
+
+- **[Synopsis](release-notes/5.3.2.md#synopsis)**
+- **[Bugs resolved](release-notes/5.3.2.md#bugs)**
+- **[Credits](release-notes/5.3.2.md#credits)**
+- **[Feedback](release-notes/5.3.2.md#feedback)**
+
+## CiviCRM 5.3.1
+
+Released July 18, 2018
+
+- **[Security advisories](release-notes/5.3.1.md#security)**
+- **[Features](release-notes/5.3.1.md#features)**
+- **[Bugs resolved](release-notes/5.3.1.md#bugs)**
+- **[Miscellany](release-notes/5.3.1.md#misc)**
+- **[Credits](release-notes/5.3.1.md#credits)**
+
 ## CiviCRM 5.3.0
 
 Released July 3, 2018
diff --git a/civicrm/release-notes/5.4.0.md b/civicrm/release-notes/5.4.0.md
new file mode 100644
index 0000000000..c24fe77291
--- /dev/null
+++ b/civicrm/release-notes/5.4.0.md
@@ -0,0 +1,667 @@
+# CiviCRM 5.4.0
+
+Released August 1, 2018
+
+- **[Synopsis](#synopsis)**
+- **[Features](#features)**
+- **[Bugs resolved](#bugs)**
+- **[Miscellany](#misc)**
+- **[Credits](#credits)**
+- **[Feedback](#feedback)**
+
+## <a name="synopsis"></a>Synopsis
+
+| *Does this version...?*                                         |         |
+|:--------------------------------------------------------------- |:-------:|
+| Fix security vulnerabilities?                                   |   no    |
+| **Change the database schema?**                                 | **yes** |
+| **Alter the API?**                                              | **yes** |
+| **Require attention to configuration options?**                 | **yes** |
+| **Fix problems installing or upgrading to a previous version?** | **yes** |
+| **Introduce features?**                                         | **yes** |
+| **Fix bugs?**                                                   | **yes** |
+
+## <a name="features"></a>Features
+
+### Core CiviCRM
+
+- **[CRM-20430](https://issues.civicrm.org/jira/browse/CRM-20430) A permission
+  'save Report Criteria'
+  ([12107](https://github.com/civicrm/civicrm-core/pull/12107))**
+
+  Permission to edit the criteria for saved reports is now separate from the
+  permission to access and preview modifications of report criteria.  This will
+  prevent non-privileged users from modifying the criteria of an organization's
+  standard reports.  However when upgrading, site administrators will need to
+  grant the Save Report Criteria permission to roles that need that capability.
+
+- **[dev/core#174](https://lab.civicrm.org/dev/core/issues/174) Consistently use
+  swappable cache interfaces
+  ([12321](https://github.com/civicrm/civicrm-core/pull/12321),
+  [12322](https://github.com/civicrm/civicrm-core/pull/12322),
+  [12323](https://github.com/civicrm/civicrm-core/pull/12323),
+  [12330](https://github.com/civicrm/civicrm-core/pull/12330),
+  [12331](https://github.com/civicrm/civicrm-core/pull/12331),
+  [12336](https://github.com/civicrm/civicrm-core/pull/12336),
+  [12342](https://github.com/civicrm/civicrm-core/pull/12342),
+  [12348](https://github.com/civicrm/civicrm-core/pull/12348),
+  [12354](https://github.com/civicrm/civicrm-core/pull/12354),
+  [12362](https://github.com/civicrm/civicrm-core/pull/12362),
+  [12368](https://github.com/civicrm/civicrm-core/pull/12368),
+  [12376](https://github.com/civicrm/civicrm-core/pull/12376),
+  [12378](https://github.com/civicrm/civicrm-core/pull/12378),
+  [12379](https://github.com/civicrm/civicrm-core/pull/12379),
+  [12380](https://github.com/civicrm/civicrm-core/pull/12380),
+  [12381](https://github.com/civicrm/civicrm-core/pull/12381),
+  [12389](https://github.com/civicrm/civicrm-core/pull/12389), and
+  [215](https://github.com/civicrm/civicrm-packages/pull/215))**
+
+  Sites using Redis or Memcache for caches and sessions will now use those
+  facilities exclusively rather than retaining the `civicrm_cache` table as
+  a backstop.  This improves performance for these sites, especially those
+  using multiple MySQL servers.
+
+  As a precaution, Redis or Memcache administrators with very active sites
+  may wish to double-check that they have ample capacity for their caches and
+  sessions.
+
+  For developers, the `CRM_Utils_Cache` APIs now support expiration/TTL
+  times and [PSR-16 CacheInterface](https://www.php-fig.org/psr/psr-16/).
+  The Civi implementation is slightly more permissive than the PSR-16
+  specification; for full compliance, enable `CIVICRM_PSR16_STRICT`.
+
+- **[CRM-21574](https://issues.civicrm.org/jira/browse/CRM-21574) Allow to
+  disable sending of email from source contact for tell a friend on pcp
+  ([12355](https://github.com/civicrm/civicrm-core/pull/12355)) (preliminary
+  work)**
+
+  Emails from the "tell a friend" feature are sent as if they are from the
+  donor's address.  This can cause delivery failures as the site may not be a
+  valid source for the donor's email domain.  Changes here are preliminary
+  cleanup.
+
+- **[dev/core#32](https://lab.civicrm.org/dev/core/issues/32) Add option to
+  disable cancel button on a profile form
+  ([11883](https://github.com/civicrm/civicrm-core/pull/11883))**
+
+  The Cancel button is now optional on profile forms.
+
+- **[CRM-21392](https://issues.civicrm.org/jira/browse/CRM-21392) Allow viewing
+  groups/smartgroups as as other entities (eg. memberships, events)
+  ([11241](https://github.com/civicrm/civicrm-core/pull/11241))**
+
+  The Find Groups page now has a similar "View Results As" feature as the
+  Advanced Search: you can choose to view memberships, events, activities, etc.
+  of group subscribers rather than the contacts themselves.
+
+- **[CRM-21622](https://issues.civicrm.org/jira/browse/CRM-21622) Make the Add
+  Parent Groups on Group Settings be a multiselect
+  ([11481](https://github.com/civicrm/civicrm-core/pull/11481))**
+
+  The New Group form now allows you to set multiple parent groups for a single
+  group.
+
+- **[CRM-21460](https://issues.civicrm.org/jira/browse/CRM-21460) Programmatic
+  access to job execution status
+  ([11337](https://github.com/civicrm/civicrm-core/pull/11337))**
+
+  Extension developers can implement two new hooks, `hook_civicrm_preJob` and
+  `hook_civicrm_postJob`, which are invoked before and after executing a
+  scheduled job.
+
+- **Change default for `CIVICRM_CONTAINER_CACHE` to simplify admin/developer
+  experience ([12426](https://github.com/civicrm/civicrm-core/pull/12426))**
+
+- **[dev/core#223](https://lab.civicrm.org/dev/core/issues/223) Expose prefix
+  and suffix in reports
+  ([12394](https://github.com/civicrm/civicrm-core/pull/12394))**
+
+  Individuals' prefix and suffix fields are now available as columns and filters
+  among the contact fields in most reports.
+
+- **[dev/core#101](https://lab.civicrm.org/dev/core/issues/101) Allow further
+  customization of search form in hooks
+  ([12078](https://github.com/civicrm/civicrm-core/pull/12078))**
+
+  Fields are no longer explicitly included in the search form template since
+  that makes it hard to add or remove fields.  Instead, the fields are listed in
+  template variables, and the template iterates over the list to render the
+  field elements.
+
+- **[dev/core#183](https://lab.civicrm.org/dev/core/issues/183) Temporary tables
+  should follow consistent naming convention
+  ([12311](https://github.com/civicrm/civicrm-core/pull/12311)) (partially
+  completed)**
+
+  This moves towards naming temporary tables--both true temporary tables in
+  MySQL and regular tables created for temporary use--in a consistent manner in
+  order to help database administrators employ replication policies based upon
+  table names.
+
+- **[dev/core#144](https://lab.civicrm.org/dev/core/issues/144)
+  `getCustomFieldID` switch to API, add caching, add full string return option
+  ([12218](https://github.com/civicrm/civicrm-core/pull/12218) and
+  [12283](https://github.com/civicrm/civicrm-core/pull/12283))**
+
+  The function to retrieve a custom field ID now relies upon APIv3 rather than
+  an *ad hoc* SQL query.  It also contains an option to return the ID preceded
+  by `custom_`.  Finally, the function caches the result and attempts to return
+  the cached value rather than looking it up each time.
+
+- **[dev/core#47](https://lab.civicrm.org/dev/core/issues/47) Add "clone"
+  functionality to scheduled jobs
+  ([11945](https://github.com/civicrm/civicrm-core/pull/11945))**
+
+  Scheduled jobs can now be copied, including their parameters, both from the
+  Scheduled Jobs page and through a new `Job.clone` API method.
+
+### CiviCase
+
+- **[dev/core#107](https://lab.civicrm.org/dev/core/issues/107) After creating a
+  new case, the assignee for each activity must be selected manually
+  ([11998](https://github.com/civicrm/civicrm-core/pull/11998))**
+
+  You can now select the default assignee for each activity in the standard
+  timeline for a case type.  This may be defined according to a relationship
+  with the client, a single specific contact, or the contact who created the
+  case.
+
+- **CiviCase, added reference of activities from different timelines.
+  ([12236](https://github.com/civicrm/civicrm-core/pull/12236))**
+
+  From an activity on a case timeline, you can now reference any activity
+  available to the case--not just those on that case timeline.
+
+### CiviContribute
+
+- **Add support for hidden fields in general (and 'token' specifically) on the
+  payment form. ([12332](https://github.com/civicrm/civicrm-core/pull/12332)
+  and [12391](https://github.com/civicrm/civicrm-core/pull/12391))**
+
+  A new authorization token field is added to the payment form to support
+  token-based payment processors.
+
+- **[CRM-21808](https://issues.civicrm.org/jira/browse/CRM-21808) Install custom
+  group for Contribution or ContributioRecur subtypes based on XML file
+  ([11726](https://github.com/civicrm/civicrm-core/pull/11726))**
+
+  Extensions created custom data sets for contributions can now specify the
+  custom data to apply only to a single financial type.
+
+- **(dev/core/50) and
+  [dev/core#195](https://lab.civicrm.org/dev/core/issues/195) Add Separate
+  Sub-tabs for Contributions and Recurring  Contributions
+  ([11956](https://github.com/civicrm/civicrm-core/pull/11956) and
+  [12345](https://github.com/civicrm/civicrm-core/pull/12345))**
+
+  The Contributions tab on a contact now contains two sub-tabs--one for single
+  contributions, and the other for recurring contribution series--instead of
+  displaying one after the other.
+
+- **[dev/core#96](https://lab.civicrm.org/dev/core/issues/96) Expose source
+  column in booking report
+  ([12374](https://github.com/civicrm/civicrm-core/pull/12374))**
+
+  The contribution Source field is now available as a column and filter on the
+  Bookkeeping Transactions report.
+
+- **Use html5 color input for contribution page widget
+  ([12339](https://github.com/civicrm/civicrm-core/pull/12339))**
+
+### CiviMail
+
+- **[dev/mail#1](https://lab.civicrm.org/dev/mail/issues/1) Add pause/resume
+  functionality to civicrm bulk mailing.
+  ([11803](https://github.com/civicrm/civicrm-core/pull/11803) and
+  [12284](https://github.com/civicrm/civicrm-core/pull/12284))**
+
+  A CiviMail mailing that is scheduled or in progress can now be paused in order
+  to allow an urgent message to go out first.
+
+- **Add in bootstrap button classes to CiviMail interface
+  ([12013](https://github.com/civicrm/civicrm-core/pull/12013) and
+  [12338](https://github.com/civicrm/civicrm-core/pull/12338))**
+
+  This allows the interfaces to be themed more easily by Bootstrap-based themes.
+
+## <a name="bugs"></a>Bugs resolved
+
+### Core CiviCRM
+
+- **[dev/core#222](https://lab.civicrm.org/dev/core/issues/222) Importing
+  contacts with dedupe rule does not use the rule
+  ([12393](https://github.com/civicrm/civicrm-core/pull/12393))**
+
+- **[dev/core#284](https://lab.civicrm.org/dev/core/issues/284) Aggressive cache
+  clearing significantly increases test time
+  ([12590](https://github.com/civicrm/civicrm-core/pull/12590))**
+
+  Settings, compiled Javascript, extension, and other caches are now only
+  flushed along with the `default` CiviCRM cache if an external cache like Redis or
+  Memcache is in use. (This reproduces the `5.3` behavior.)
+
+- **[dev/core#279](https://lab.civicrm.org/dev/core/issues/279) When Merging two
+  contacts, Flip between original and duplicate link not working in WordPress
+  ([12568](https://github.com/civicrm/civicrm-core/pull/12568))**
+
+- **Handle 'Check for Matching Contact(s)' button with ajax
+  ([12552](https://github.com/civicrm/civicrm-core/pull/12552))**
+
+  This resolves a bug where the "Check for Matching Contact(s)" button would not
+  work if AJAX Deduping is enabled.
+
+- **[dev/core#131](https://lab.civicrm.org/dev/core/issues/131) Add in missing
+  UK shire Monmouthshire
+  ([12538](https://github.com/civicrm/civicrm-core/pull/12538)) (follow-up on
+  previous work)**
+
+  Many individual sites had manually added the county of Monmouthshire in Wales
+  to the `civicrm_state_province` table, and upgrading to 5.3.0, which added the
+  missing record, would cause them a database error.
+
+- **[dev/core#258](https://lab.civicrm.org/dev/core/issues/258) Possible
+  unreleased regression - message template update altered customised template
+  ([12492](https://github.com/civicrm/civicrm-core/pull/12492))**
+
+- **[dev/core#249](https://lab.civicrm.org/dev/core/issues/249) Contact Export
+  fails in 5.4 when trying to export using the All radio button
+  ([12447](https://github.com/civicrm/civicrm-core/pull/12447))**
+
+- **[dev/core#234](https://lab.civicrm.org/dev/core/issues/234) Upgrade Steps in
+  5.3.alpha1 may not have been run
+  ([12419](https://github.com/civicrm/civicrm-core/pull/12419),
+  [12420](https://github.com/civicrm/civicrm-core/pull/12420), and
+  [12425](https://github.com/civicrm/civicrm-core/pull/12425))**
+
+- **[dev/core#155](https://lab.civicrm.org/dev/core/issues/155) Improvements and
+  bugfixes to Option Groups UI
+  ([12233](https://github.com/civicrm/civicrm-core/pull/12233) and
+  [12411](https://github.com/civicrm/civicrm-core/pull/12411))**
+
+  The Option Groups page now displays whether each option group is reserved or
+  enabled.  In addition, sites using sample data no longer have the sample
+  custom fields reserved.
+
+- **[CRM-20184](https://issues.civicrm.org/jira/browse/CRM-20184) Some System
+  Workflow templates still miss {contact.email_greeting}
+  ([12296](https://github.com/civicrm/civicrm-core/pull/12296))**
+
+  This makes receipt templates more consistent in using the contact's email
+  greeting when sent to individuals.
+
+- **[dev/core#41](https://lab.civicrm.org/dev/core/issues/41) Search Builder:
+  Not empty with date or integer custom fields gives a sql warning
+  ([12351](https://github.com/civicrm/civicrm-core/pull/12351) and
+  [12363](https://github.com/civicrm/civicrm-core/pull/12363))**
+
+- **Make all form tasks inherit from `CRM_Core_Form_Task`
+  ([12318](https://github.com/civicrm/civicrm-core/pull/12318))**
+
+  This addresses some export problems caused by tasks lacking certain properties
+  because their classes did not extend `CRM_Core_Form_Task`.
+
+- **[dev/core#178](https://lab.civicrm.org/dev/core/issues/178) Redis driver -
+  Error messages are invisible
+  ([12303](https://github.com/civicrm/civicrm-core/pull/12303))**
+
+  The log now contains error messages passed from Redis.
+
+- **[dev/core#180](https://lab.civicrm.org/dev/core/issues/180) Deleting custom
+  values and custom_hook parameter `$entity_id` empty
+  ([12309](https://github.com/civicrm/civicrm-core/pull/12309))**
+
+  The ID of the affected entity is now passed to `hook_civicrm_custom` when a
+  value is deleted from a custom field.
+
+- **Fix access to scheduled reminders for admins without event access
+  ([11733](https://github.com/civicrm/civicrm-core/pull/11733))**
+
+  Users with Administer CiviCRM permission but lacking Manage Events permission
+  would get an error when trying to create a non-event scheduled reminder.
+
+- **Only treat a request as a get request (and hence use GET) if it starts with
+  get ([12308](https://github.com/civicrm/civicrm-core/pull/12308))**
+
+- **[dev/core#179](https://lab.civicrm.org/dev/core/issues/179) Redis driver -
+  Allow anonymous connections
+  ([12304](https://github.com/civicrm/civicrm-core/pull/12304))**
+
+  Redis no longer attempts to authenticate when no password is set, such as on
+  local development environments.
+
+- **[dev/core#177](https://lab.civicrm.org/dev/core/issues/177) Redis driver -
+  Reports incorrect value for cache-miss
+  ([12302](https://github.com/civicrm/civicrm-core/pull/12302))**
+
+  When a cache key is undefined, Redis now returns `NULL` rather than `FALSE`.
+
+- **[dev/core#175](https://lab.civicrm.org/dev/core/issues/175) Smart group
+  involving relationship type filter display incorrect results.
+  ([12301](https://github.com/civicrm/civicrm-core/pull/12301))**
+
+- **Make Send SMS permission independent of Edit Contact permission
+  ([12067](https://github.com/civicrm/civicrm-core/pull/12067))**
+
+  You no longer need the permission to edit a contact (in addition to the Send
+  SMS permission) to send the contact a SMS message.
+
+- **[dev/core#134](https://lab.civicrm.org/dev/core/issues/134) Search Builder
+  broken filter for Source Contact ID
+  ([12181](https://github.com/civicrm/civicrm-core/pull/12181))**
+
+- **[dev/core#140](https://lab.civicrm.org/dev/core/issues/140) add missing
+  pseudoconstant for `option_group_id` in CustomField.xml
+  ([12195](https://github.com/civicrm/civicrm-core/pull/12195))**
+
+  Extensions creating custom fields can now refer to option groups by name
+  rather than ID.
+
+- **[dev/accessiblity#3](https://lab.civicrm.org/dev/accessibility/issues/3) Add
+  aria-label (and label?) to form elements missing them
+  ([12209](https://github.com/civicrm/civicrm-core/pull/12209) and
+  [213](https://github.com/civicrm/civicrm-packages/pull/213)) (partial work)**
+
+  Besides adding `aria-label` attributes to sub-elements of the Quickform
+  date field, event info pages no longer wrap basic information labels in
+  `<label>` tags.
+
+- **[CRM-20711](https://issues.civicrm.org/jira/browse/CRM-20711) Error - DB
+  Constraint Violation - GroupContact, get API
+  ([12408](https://github.com/civicrm/civicrm-core/pull/12408))**
+
+  This adds the `location_id` field to the `civicrm_group_contact` table on
+  older sites that lack it.
+
+- **[dev/core#152](https://lab.civicrm.org/dev/core/issues/152) -
+  AdvMulti-Select cleanup
+  ([531](https://github.com/civicrm/civicrm-drupal/pull/531)) (follow-up work)**
+
+  This removes a couple of references to the now-removed "Advanced Multi-select"
+  custom field type
+
+- **Upgrade Mime_mail to fix issues with PHP7.2
+  ([205](https://github.com/civicrm/civicrm-packages/pull/205))**
+
+- **Apply fixes to have DB package support PHP7.2
+  ([207](https://github.com/civicrm/civicrm-packages/pull/207))**
+
+- **[dev/core#117](https://lab.civicrm.org/dev/core/issues/117) Remove usage of
+  `each()` This is deprecated in php7.2
+  ([211](https://github.com/civicrm/civicrm-packages/pull/211)) (follow-up
+  work)**
+
+- **Declare Subparts as array in mimePart to support PHP7.2
+  ([210](https://github.com/civicrm/civicrm-packages/pull/210))**
+
+- **[CRM-19798](https://issues.civicrm.org/jira/browse/CRM-19798) Memory leak in
+  API3 EntityTag get operations
+  ([12276](https://github.com/civicrm/civicrm-core/pull/12276))**
+
+### CiviCase
+
+- **Fix regression on case export from recent export fix
+  ([12588](https://github.com/civicrm/civicrm-core/pull/12588))**
+
+  This resolves undefined property PHP notices when exporting cases.
+
+- **[dev/core#24](https://lab.civicrm.org/dev/core/issues/24) Passing an array
+  for contact_id/client_id to Case.Create API when updating an existing case
+  causes case to be "reassigned"
+  ([11830](https://github.com/civicrm/civicrm-core/pull/11830))**
+
+- **[CRM-21815](https://issues.civicrm.org/jira/browse/CRM-21815) On re-opening
+  a civicase - Case Coordinator (and other roles) are not reinstated
+  ([11736](https://github.com/civicrm/civicrm-core/pull/11736))**
+
+- **[dev/core#165](https://lab.civicrm.org/dev/core/issues/165) Bad link in
+  civicrm/case/activity/view breadcrumb
+  ([12279](https://github.com/civicrm/civicrm-core/pull/12279))**
+
+### CiviContribute
+
+- **[CRM-21637](https://issues.civicrm.org/jira/browse/CRM-21637) Search
+  Criteria for Card Type ID and Card Number not respected in Batch
+  ([11495](https://github.com/civicrm/civicrm-core/pull/11495))**
+
+  The card type and card number filters for financial transactions in the
+  accounting batch interface now work as expected.
+
+- **[CRM-21854](https://issues.civicrm.org/jira/browse/CRM-21854) Contribution
+  start date and end dates are not respected
+  ([11881](https://github.com/civicrm/civicrm-core/pull/11881) and
+  [12504](https://github.com/civicrm/civicrm-core/pull/12504))**
+
+- **[dev/core#264](https://lab.civicrm.org/dev/core/issues/264) Fatal Error on
+  editing Financial Transaction
+  ([12502](https://github.com/civicrm/civicrm-core/pull/12502))**
+
+- **[dev/core#220](https://lab.civicrm.org/dev/core/issues/220) State
+  province/country doesn't show properly in the report
+  ([12390](https://github.com/civicrm/civicrm-core/pull/12390))**
+
+  This resolves a bug in the Contribution History by Relationship report where
+  strange values would appear for the State/Province and Country fields.
+
+- **[dev/core#202](https://lab.civicrm.org/dev/core/issues/202) Empty row under
+  currency drop down
+  ([12356](https://github.com/civicrm/civicrm-core/pull/12356))**
+
+  A warning now appears when a site has a disabled currency that is set as
+  default.
+
+- **[dev/core#88](https://lab.civicrm.org/dev/core/issues/88) Make sure
+  `financial_type_id` is set when a contribution is created
+  ([11907](https://github.com/civicrm/civicrm-core/pull/11907))**
+
+  This resolves a bug where the financial type would not be set on a one-time
+  contribution made through a contribution page.
+
+- **[dev/core#170](https://lab.civicrm.org/dev/core/issues/170) contribution
+  detail report errors when soft credits column is exposed
+  ([12281](https://github.com/civicrm/civicrm-core/pull/12281) and
+  [12282](https://github.com/civicrm/civicrm-core/pull/12282))**
+
+- **Fix e-notice, remove legacy code pattern
+  ([12298](https://github.com/civicrm/civicrm-core/pull/12298))**
+
+  This resolves a PHP notice when printing contribution receipts in bulk.
+
+- **[dev/core#56](https://lab.civicrm.org/dev/core/issues/56) Cancel Recurring
+  Contribution activity should have a source record id
+  ([11964](https://github.com/civicrm/civicrm-core/pull/11964))**
+
+  The activity created when cancelling a recurring contribution now has the
+  recurring contribution ID as the `source_record_id`.
+
+- **[dev/financial#14](https://lab.civicrm.org/dev/financial/issues/14) PayPal
+  Express recurring payment causes warning messages
+  ([12091](https://github.com/civicrm/civicrm-core/pull/12091))**
+
+- **[CRM-20697](https://issues.civicrm.org/jira/browse/CRM-20697) Online pay now
+  anomalies (contribution transfer to new contact)
+  ([11578](https://github.com/civicrm/civicrm-core/pull/11578))**
+
+  The Pay Now form now uses contact ID and checksum to validate the
+  contribution's contact to avoid creating new contacts when an anonymous
+  visitor fulfills a pending contribution.
+
+### CiviEvent
+
+- **[dev/core#272](https://lab.civicrm.org/dev/core/issues/272) Fatal Error
+  (Regression) on PCP pages associated with Events
+  ([12533](https://github.com/civicrm/civicrm-core/pull/12533))**
+
+- **[dev/core#124](https://lab.civicrm.org/dev/core/issues/124) Registration
+  approval issues ([12160](https://github.com/civicrm/civicrm-core/pull/12160)
+  and [12417](https://github.com/civicrm/civicrm-core/pull/12417))**
+
+  This resolves a PHP warning and some confusing aspects of handling event
+  registration approvals.
+
+### CiviGrant
+
+- **[dev/core#187](https://lab.civicrm.org/dev/core/issues/187) Fix typo in
+  `CRM_Grant_Form_Task` that prevents retrieving session key from URL
+  ([12317](https://github.com/civicrm/civicrm-core/pull/12317))**
+
+### CiviMail
+
+- **[dev/core#133](https://lab.civicrm.org/dev/core/issues/133) Reply-to field
+  with empty string get saved in DB as `NULL`
+  ([12176](https://github.com/civicrm/civicrm-core/pull/12176))**
+
+- **[CRM-20320](https://issues.civicrm.org/jira/browse/CRM-20320) Error in PEAR
+  SMTP causes mailing job to fail when an error occurs during one connection
+  ([11840](https://github.com/civicrm/civicrm-core/pull/11840))**
+
+  CiviMail will now disconnect the SMTP connection when a connection error is
+  detected and record the group of messages as not delivered.  This allows it to
+  reconnect later and send the messages.
+
+- **[CRM-17753](https://issues.civicrm.org/jira/browse/CRM-17753) Newsletter
+  confirmation emails shouldn't use do-not-reply@yourdomain.org
+  ([12270](https://github.com/civicrm/civicrm-core/pull/12270)) (preliminary
+  work)**
+
+- **[CRM-21779](https://issues.civicrm.org/jira/browse/CRM-21779) Civimail
+  allows adding current draft mailing recipients to recipients field
+  ([11724](https://github.com/civicrm/civicrm-core/pull/11724))**
+
+### CiviMember
+
+- **[dev/membership#4](https://lab.civicrm.org/dev/membership/issues/4) Admin
+  Membership type is displayed on Public contribution page.
+  ([12178](https://github.com/civicrm/civicrm-core/pull/12178))**
+
+- **[dev/core#276](https://lab.civicrm.org/dev/core/issues/276) Undefined index:
+  is_override on submit credit card membership
+  ([12563](https://github.com/civicrm/civicrm-core/pull/12563))**
+
+- **[dev/core#151](https://lab.civicrm.org/dev/core/issues/151) Action to Update
+  Recurring Contributions From Membership View is Never Shown
+  ([12228](https://github.com/civicrm/civicrm-core/pull/12228))**
+
+  The table to view recurring contributions on a membership now displays the
+  Edit action where appropriate.
+
+- **[CRM-21682](https://issues.civicrm.org/jira/browse/CRM-21682) Automatic
+  membership renewal fixes
+  ([12313](https://github.com/civicrm/civicrm-core/pull/12313) and
+  [12314](https://github.com/civicrm/civicrm-core/pull/12314)) (preliminary
+  work)**
+
+- **[CRM-21177](https://issues.civicrm.org/jira/browse/CRM-21177) Wrong interval
+  of recurring payment for auto-renewing membership
+  ([12289](https://github.com/civicrm/civicrm-core/pull/12289)) (preliminary
+  work)**
+
+### Joomla Integration
+
+- **[dev/joomla#1](https://lab.civicrm.org/dev/joomla/issues/1) CiviEvent menu
+  listing showing old events
+  ([46](https://github.com/civicrm/civicrm-joomla/pull/46))**
+
+### WordPress Integration
+
+- **[dev/wordpress#1](https://lab.civicrm.org/dev/wordpress/issues/1)
+  CiviCRM-WordPress - Gutenberg Compatibility: Do not parse shortcodes in REST
+  context ([130](https://github.com/civicrm/civicrm-wordpress/pull/130))**
+
+- **[CRM-21565](https://issues.civicrm.org/jira/browse/CRM-21565) Change mkdir
+  to use correct and more secure mode numbers
+  ([120](https://github.com/civicrm/civicrm-wordpress/pull/120))**
+
+## <a name="misc"></a>Miscellany
+
+- **(NFC) Convert files to use linux line endings not windows
+  ([12531](https://github.com/civicrm/civicrm-core/pull/12531))**
+
+- **Move DAO function to DAO class, call it from Merge class
+  ([12340](https://github.com/civicrm/civicrm-core/pull/12340))**
+
+- **NFC code cleanup for AuthNet, Paypal, PaypalPro IPNs
+  ([12386](https://github.com/civicrm/civicrm-core/pull/12386))**
+
+- **[dev/core#203](https://lab.civicrm.org/dev/core/issues/203) Cruft code in
+  `CRM_Core_BAO_OptionGroup::add()`
+  ([12357](https://github.com/civicrm/civicrm-core/pull/12357))**
+
+- **Remove unused instances of usedTable
+  ([12341](https://github.com/civicrm/civicrm-core/pull/12341) and
+  [12349](https://github.com/civicrm/civicrm-core/pull/12349))**
+
+- **(NFC) CiviUnitTestCase - Fix edge-case for mis-reported error
+  ([12347](https://github.com/civicrm/civicrm-core/pull/12347))**
+
+- **NFC Code cleanup to core task class
+  ([12316](https://github.com/civicrm/civicrm-core/pull/12316))**
+
+- **[NFC] Stop passing no-longer-part-of-signature variable
+  ([12329](https://github.com/civicrm/civicrm-core/pull/12329))**
+
+- **Fix typo in api explorer sample code
+  ([12324](https://github.com/civicrm/civicrm-core/pull/12324))**
+
+- **Remove unused code variable
+  ([12299](https://github.com/civicrm/civicrm-core/pull/12299))**
+
+- **Confirm & lock in group.get handling of is_active
+  ([12295](https://github.com/civicrm/civicrm-core/pull/12295))**
+
+- **Remove unused function getRecuringTransactionStatus from PayflowPro c…
+  ([12294](https://github.com/civicrm/civicrm-core/pull/12294))**
+
+- **Add upgrade function for message templates that does not involve copying the
+  whole template ([12224](https://github.com/civicrm/civicrm-core/pull/12224))**
+
+- **Function extraction BAO_Export class
+  ([12288](https://github.com/civicrm/civicrm-core/pull/12288))**
+
+- **Partial refactor of completeMembershipFromContribution
+  ([12271](https://github.com/civicrm/civicrm-core/pull/12271))**
+
+- **[NFC] Fix line endings (Jenkins where were you)
+  ([12280](https://github.com/civicrm/civicrm-core/pull/12280))**
+
+- **Towards fixing household merge export, extract function, add test, fix prev
+  ([12272](https://github.com/civicrm/civicrm-core/pull/12272))**
+
+- **Standardise line endings away from windows line endings to linux
+  ([212](https://github.com/civicrm/civicrm-packages/pull/212))**
+
+## <a name="credits"></a>Credits
+
+This release was developed by the following code authors:
+
+AGH Strategies - Alice Frumin, Andrew Hunt; Agileware - Alok Patel; Australian
+Greens - Seamus Lee; Bastien Ho; Caltha - Tomasz Pietrzkowski; CiviCoop - Jaap
+Jansma; CiviCRM - Coleman Watts, Tim Otten; CiviDesk - Yashodha Chaku; CompuCorp -
+Camilo Rodriguez, Michael Devery, René Olivo, Vinu Varshith Sekar; Electronic
+Frontier Foundation - Mark Burdett; Fuzion - Jitendra Purohit; Ginkgo Street
+Labs - Frank Gómez; JMA Consulting - Monish Deb; Johan Vervloet; John
+Kingsnorth; Kanzu Code - Carl Andrew Lema; Left Join Labs - Sean Madsen; MJW
+Consulting - Matthew Wire; Pradeep Nayak; Romain Thouvenin; Squiffle Consulting -
+Aidan Saunders; Tadpole Collective - Kevin Cristiano; Third Sector Design -
+Michael McAndrew; Tom Bloor; Wikimedia Foundation - Eileen McNaughton
+
+Most authors also reviewed code for this release; in addition, the following
+reviewers contributed their comments:
+
+Agileware - Justin Freeman; baernm; Blackfly Solutions - Alan Dixon; Bob
+Silvern; Borislav Zlatanov; Caltha - Michal Mach; cartbar; Christian Wach;
+Circle Interactive - Dave Jenkins; CiviCoop - Erik Hommel; Community Builders -
+Andrew Perry; CompuCorp - Guanhuan Chen, Omar Abu Hussein; Coop SymbioTIC -
+Mathieu Lutfy; Drupal Association - Lizz Trudeau; Evan Chute; Forest CRM
+Consulting - Tamar Meir; Freeform Solutions - Herb van den Dool; Ginkgo Street
+Labs - Dan O'Brien; Hugo do Carmo; JMA Consulting - Joe Murray; Lemniscus - Noah
+Miller; Lighthouse Design and Consulting - Brian Shaughnessy; Megaphone
+Technology Consulting - Jon Goldberg; MJCO - Mikey O'Toole; Oxfam Germany -
+Thomas Schüttler; Richard van Oosterhout; Robin Fenwick; Semper IT - Karin
+Gerritsen; Stephen Palmstrom; Tech To The People - Xavier Dutoit; Timbsoft
+Technologies - Tunbola Ogunwande
+
+## <a name="feedback"></a>Feedback
+
+These release notes are edited by Andrew Hunt.  If you'd like to provide
+feedback on them, please log in to https://chat.civicrm.org/civicrm and contact
+`@agh1`.
diff --git a/civicrm/settings/Core.setting.php b/civicrm/settings/Core.setting.php
index b95baf8d0e..c95ed3d018 100644
--- a/civicrm/settings/Core.setting.php
+++ b/civicrm/settings/Core.setting.php
@@ -883,21 +883,6 @@ return array(
     'description' => NULL,
     'help_text' => NULL,
   ),
-  'systemStatusCheckResult' => array(
-    'group_name' => 'CiviCRM Preferences',
-    'group' => 'core',
-    'name' => 'systemStatusCheckResult',
-    'type' => 'Integer',
-    'quick_form_type' => 'Element',
-    'html_type' => 'text',
-    'default' => 0,
-    'add' => '4.7',
-    'title' => 'systemStatusCheckResult',
-    'is_domain' => 1,
-    'is_contact' => 0,
-    'description' => NULL,
-    'help_text' => NULL,
-  ),
   'recentItemsMaxCount' => array(
     'group_name' => 'CiviCRM Preferences',
     'group' => 'core',
diff --git a/civicrm/sql/civicrm.mysql b/civicrm/sql/civicrm.mysql
index 6e62877dd1..3702ee53c9 100644
--- a/civicrm/sql/civicrm.mysql
+++ b/civicrm/sql/civicrm.mysql
@@ -3506,7 +3506,8 @@ CREATE TABLE `civicrm_uf_group` (
      `created_date` datetime    COMMENT 'Date and time this UF group was created.',
      `is_proximity_search` tinyint   DEFAULT 0 COMMENT 'Should we include proximity search feature in this profile search form?',
      `cancel_button_text` varchar(64)   DEFAULT NULL COMMENT 'Custom Text to display on the Cancel button when used in create or edit mode',
-     `submit_button_text` varchar(64)   DEFAULT NULL COMMENT 'Custom Text to display on the submit button on profile edit/create screens' 
+     `submit_button_text` varchar(64)   DEFAULT NULL COMMENT 'Custom Text to display on the submit button on profile edit/create screens',
+     `add_cancel_button` tinyint   DEFAULT 1 COMMENT 'Should a Cancel button be included in this Profile form.' 
 ,
         PRIMARY KEY (`id`)
  
diff --git a/civicrm/sql/civicrm_data.mysql b/civicrm/sql/civicrm_data.mysql
index 6a787382a2..07f1af5c9b 100644
--- a/civicrm/sql/civicrm_data.mysql
+++ b/civicrm/sql/civicrm_data.mysql
@@ -7937,7 +7937,8 @@ INSERT INTO civicrm_msg_template
       
       
       ('Contributions - Receipt (on-line)', '{if $is_pay_later}{ts}Invoice{/ts}{else}{ts}Receipt{/ts}{/if} - {$title}
-', '{if $receipt_text}
+', '{assign var="greeting" value="{contact.email_greeting}"}{if $greeting}{$greeting},{/if}
+{if $receipt_text}
 {$receipt_text}
 {/if}
 {if $is_pay_later}
@@ -8145,7 +8146,8 @@ INSERT INTO civicrm_msg_template
  {$customName}: {$customValue}
 {/if}
 {/foreach}
-{/if}', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+{/if}
+', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
@@ -8168,7 +8170,7 @@ INSERT INTO civicrm_msg_template
 
   <tr>
    <td>
-
+     {assign var="greeting" value="{contact.email_greeting}"}{if $greeting}<p>{$greeting},</p>{/if}
     {if $receipt_text}
      <p>{$receipt_text|htmlize}</p>
     {/if}
@@ -8620,7 +8622,8 @@ INSERT INTO civicrm_msg_template
 </html>
 ', @tpl_ovid_contribution_online_receipt, 1,          0),
       ('Contributions - Receipt (on-line)', '{if $is_pay_later}{ts}Invoice{/ts}{else}{ts}Receipt{/ts}{/if} - {$title}
-', '{if $receipt_text}
+', '{assign var="greeting" value="{contact.email_greeting}"}{if $greeting}{$greeting},{/if}
+{if $receipt_text}
 {$receipt_text}
 {/if}
 {if $is_pay_later}
@@ -8828,7 +8831,8 @@ INSERT INTO civicrm_msg_template
  {$customName}: {$customValue}
 {/if}
 {/foreach}
-{/if}', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+{/if}
+', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
@@ -8851,7 +8855,7 @@ INSERT INTO civicrm_msg_template
 
   <tr>
    <td>
-
+     {assign var="greeting" value="{contact.email_greeting}"}{if $greeting}<p>{$greeting},</p>{/if}
     {if $receipt_text}
      <p>{$receipt_text|htmlize}</p>
     {/if}
@@ -14160,8 +14164,8 @@ INSERT INTO civicrm_msg_template
 ', @tpl_ovid_event_offline_receipt, 0,          1) ,      
       
       
-      ('Events - Registration Confirmation and Receipt (on-line)', '{if $isOnWaitlist}{ts}Wait List Confirmation{/ts}{else}{ts}Registration Confirmation{/ts}{/if} - {$event.event_title}', '{contact.email_greeting},
-
+      ('Events - Registration Confirmation and Receipt (on-line)', '{if $isOnWaitlist}{ts}Wait List Confirmation{/ts}{elseif $isRequireApproval}{ts}Registration Request Confirmation{/ts}{else}{ts}Registration Confirmation{/ts}{/if} - {$event.event_title}
+', '{assign var="greeting" value="{contact.email_greeting}"}{if $greeting}{$greeting},{/if}
 {if $event.confirm_email_text AND (not $isOnWaitlist AND not $isRequireApproval)}
 {$event.confirm_email_text}
 
@@ -14263,7 +14267,7 @@ INSERT INTO civicrm_msg_template
 {if $payer.name}
 You were registered by: {$payer.name}
 {/if}
-{if $event.is_monetary} {* This section for Paid events only.*}
+{if $event.is_monetary and not $isRequireApproval} {* This section for Paid events only.*}
 
 ==========================================================={if $pricesetFieldsCount }===================={/if}
 
@@ -14489,7 +14493,7 @@ You were registered by: {$payer.name}
 
   <tr>
    <td>
-  <p>{contact.email_greeting},</p>
+     {assign var="greeting" value="{contact.email_greeting}"}{if $greeting}<p>{$greeting},</p>{/if}
 
     {if $event.confirm_email_text AND (not $isOnWaitlist AND not $isRequireApproval)}
      <p>{$event.confirm_email_text|htmlize}</p>
@@ -14641,7 +14645,7 @@ You were registered by: {$payer.name}
        </td>
      </tr>
     {/if}
-    {if $event.is_monetary}
+    {if $event.is_monetary and not $isRequireApproval}
 
       <tr>
        <th {$headerStyle}>
@@ -14977,8 +14981,8 @@ You were registered by: {$payer.name}
 </body>
 </html>
 ', @tpl_ovid_event_online_receipt, 1,          0),
-      ('Events - Registration Confirmation and Receipt (on-line)', '{if $isOnWaitlist}{ts}Wait List Confirmation{/ts}{else}{ts}Registration Confirmation{/ts}{/if} - {$event.event_title}', '{contact.email_greeting},
-
+      ('Events - Registration Confirmation and Receipt (on-line)', '{if $isOnWaitlist}{ts}Wait List Confirmation{/ts}{elseif $isRequireApproval}{ts}Registration Request Confirmation{/ts}{else}{ts}Registration Confirmation{/ts}{/if} - {$event.event_title}
+', '{assign var="greeting" value="{contact.email_greeting}"}{if $greeting}{$greeting},{/if}
 {if $event.confirm_email_text AND (not $isOnWaitlist AND not $isRequireApproval)}
 {$event.confirm_email_text}
 
@@ -15080,7 +15084,7 @@ You were registered by: {$payer.name}
 {if $payer.name}
 You were registered by: {$payer.name}
 {/if}
-{if $event.is_monetary} {* This section for Paid events only.*}
+{if $event.is_monetary and not $isRequireApproval} {* This section for Paid events only.*}
 
 ==========================================================={if $pricesetFieldsCount }===================={/if}
 
@@ -15306,7 +15310,7 @@ You were registered by: {$payer.name}
 
   <tr>
    <td>
-  <p>{contact.email_greeting},</p>
+     {assign var="greeting" value="{contact.email_greeting}"}{if $greeting}<p>{$greeting},</p>{/if}
 
     {if $event.confirm_email_text AND (not $isOnWaitlist AND not $isRequireApproval)}
      <p>{$event.confirm_email_text|htmlize}</p>
@@ -15458,7 +15462,7 @@ You were registered by: {$payer.name}
        </td>
      </tr>
     {/if}
-    {if $event.is_monetary}
+    {if $event.is_monetary and not $isRequireApproval}
 
       <tr>
        <th {$headerStyle}>
@@ -18881,7 +18885,8 @@ or want to inquire about reinstating your registration for this event.{/ts}</p>
       
       
       ('Memberships - Receipt (on-line)', '{if $is_pay_later}{ts}Invoice{/ts}{else}{ts}Receipt{/ts}{/if} - {$title}
-', '{if $receipt_text}
+', '{assign var="greeting" value="{contact.email_greeting}"}{if $greeting}{$greeting},{/if}
+{if $receipt_text}
 {$receipt_text}
 {/if}
 {if $is_pay_later}
@@ -19145,7 +19150,7 @@ or want to inquire about reinstating your registration for this event.{/ts}</p>
 
   <tr>
    <td>
-
+     {assign var="greeting" value="{contact.email_greeting}"}{if $greeting}<p>{$greeting},</p>{/if}
     {if $receipt_text}
      <p>{$receipt_text|htmlize}</p>
     {/if}
@@ -19680,7 +19685,8 @@ or want to inquire about reinstating your registration for this event.{/ts}</p>
 </html>
 ', @tpl_ovid_membership_online_receipt, 1,          0),
       ('Memberships - Receipt (on-line)', '{if $is_pay_later}{ts}Invoice{/ts}{else}{ts}Receipt{/ts}{/if} - {$title}
-', '{if $receipt_text}
+', '{assign var="greeting" value="{contact.email_greeting}"}{if $greeting}{$greeting},{/if}
+{if $receipt_text}
 {$receipt_text}
 {/if}
 {if $is_pay_later}
@@ -19944,7 +19950,7 @@ or want to inquire about reinstating your registration for this event.{/ts}</p>
 
   <tr>
    <td>
-
+     {assign var="greeting" value="{contact.email_greeting}"}{if $greeting}<p>{$greeting},</p>{/if}
     {if $receipt_text}
      <p>{$receipt_text|htmlize}</p>
     {/if}
@@ -23964,4 +23970,4 @@ INSERT INTO `civicrm_report_instance`
     ( `domain_id`, `title`, `report_id`, `description`, `permission`, `form_values`)
 VALUES
     (  @domainID, 'Survey Details', 'survey/detail', 'Detailed report for canvassing, phone-banking, walk lists or other surveys.', 'access CiviReport', 'a:39:{s:6:"fields";a:2:{s:9:"sort_name";s:1:"1";s:6:"result";s:1:"1";}s:22:"assignee_contact_id_op";s:2:"eq";s:25:"assignee_contact_id_value";s:0:"";s:12:"sort_name_op";s:3:"has";s:15:"sort_name_value";s:0:"";s:17:"street_number_min";s:0:"";s:17:"street_number_max";s:0:"";s:16:"street_number_op";s:3:"lte";s:19:"street_number_value";s:0:"";s:14:"street_name_op";s:3:"has";s:17:"street_name_value";s:0:"";s:15:"postal_code_min";s:0:"";s:15:"postal_code_max";s:0:"";s:14:"postal_code_op";s:3:"lte";s:17:"postal_code_value";s:0:"";s:7:"city_op";s:3:"has";s:10:"city_value";s:0:"";s:20:"state_province_id_op";s:2:"in";s:23:"state_province_id_value";a:0:{}s:13:"country_id_op";s:2:"in";s:16:"country_id_value";a:0:{}s:12:"survey_id_op";s:2:"in";s:15:"survey_id_value";a:0:{}s:12:"status_id_op";s:2:"eq";s:15:"status_id_value";s:1:"1";s:11:"custom_1_op";s:2:"in";s:14:"custom_1_value";a:0:{}s:11:"custom_2_op";s:2:"in";s:14:"custom_2_value";a:0:{}s:17:"custom_3_relative";s:1:"0";s:13:"custom_3_from";s:0:"";s:11:"custom_3_to";s:0:"";s:11:"description";s:75:"Detailed report for canvassing, phone-banking, walk lists or other surveys.";s:13:"email_subject";s:0:"";s:8:"email_to";s:0:"";s:8:"email_cc";s:0:"";s:10:"permission";s:17:"access CiviReport";s:6:"groups";s:0:"";s:9:"domain_id";i:1;}');
-UPDATE civicrm_domain SET version = '5.3.2';
+UPDATE civicrm_domain SET version = '5.4.0';
diff --git a/civicrm/sql/civicrm_generated.mysql b/civicrm/sql/civicrm_generated.mysql
index 7cb8891e14..81b14ab734 100644
--- a/civicrm/sql/civicrm_generated.mysql
+++ b/civicrm/sql/civicrm_generated.mysql
@@ -399,7 +399,7 @@ UNLOCK TABLES;
 
 LOCK TABLES `civicrm_domain` WRITE;
 /*!40000 ALTER TABLE `civicrm_domain` DISABLE KEYS */;
-INSERT INTO `civicrm_domain` (`id`, `name`, `description`, `config_backend`, `version`, `contact_id`, `locales`, `locale_custom_strings`) VALUES (1,'Default Domain Name',NULL,NULL,'5.3.2',1,NULL,'a:1:{s:5:\"en_US\";a:0:{}}');
+INSERT INTO `civicrm_domain` (`id`, `name`, `description`, `config_backend`, `version`, `contact_id`, `locales`, `locale_custom_strings`) VALUES (1,'Default Domain Name',NULL,NULL,'5.4.0',1,NULL,'a:1:{s:5:\"en_US\";a:0:{}}');
 /*!40000 ALTER TABLE `civicrm_domain` ENABLE KEYS */;
 UNLOCK TABLES;
 
diff --git a/civicrm/sql/civicrm_sample_custom_data.mysql b/civicrm/sql/civicrm_sample_custom_data.mysql
index e12103b43b..960e5952d3 100644
--- a/civicrm/sql/civicrm_sample_custom_data.mysql
+++ b/civicrm/sql/civicrm_sample_custom_data.mysql
@@ -43,7 +43,7 @@ INSERT INTO `civicrm_custom_group` (`name`, `title`, `extends`, `style`, `collap
 -- * create option group for storing custom options for custom fields
 -- *
 -- *******************************************************/
-INSERT INTO  `civicrm_option_group` (`name`, `title`, `is_reserved`, `is_active`) VALUES ('custom_most_important_issue', 'Most Important Issue', 1, 1), ( 'custom_marital_status', 'Marital Status', 1, 1);
+INSERT INTO  `civicrm_option_group` (`name`, `title`, `is_reserved`, `is_active`) VALUES ('custom_most_important_issue', 'Most Important Issue', 0, 1), ( 'custom_marital_status', 'Marital Status', 1, 1);
 
 SELECT @option_most_id    := max(id) from civicrm_option_group where name = 'custom_most_important_issue';
 SELECT @option_marital_id := max(id) from civicrm_option_group where name = 'custom_marital_status';
@@ -78,7 +78,7 @@ CREATE TABLE `civicrm_value_constituent_information_1` (`id` int(10) unsigned NO
 
 -- Fall Fundraiser Dinner participants’ custom data
 
-INSERT INTO civicrm_option_group  ( name, title, is_active, is_reserved ) VALUES  ('soup_selection', 'Soup Selection', 1, 1);
+INSERT INTO  `civicrm_option_group` (`name`, `title`, `is_reserved`, `is_active`) VALUES  ('soup_selection', 'Soup Selection', 0, 1);
 SELECT @ogid := MAX(id) FROM civicrm_option_group;
 
 INSERT INTO civicrm_custom_group ( name, title,  extends, extends_entity_column_id, extends_entity_column_value, style, is_active, table_name) VALUES ('Food_Preference', 'Food Preference', 'Participant', 2, '1', 'Inline', 1, 'civicrm_value_food_preference_2');
@@ -97,7 +97,7 @@ INSERT INTO `civicrm_custom_group` (`name`, `title`, `extends`, `extends_entity_
 
 SELECT @cgid_contribution := MAX(id) FROM civicrm_custom_group;
 
-INSERT INTO civicrm_option_group  ( name, title, is_active ) VALUES ('how_long_have_you_been_a_donor', 'How long have you been a donor?', 1);
+INSERT INTO  `civicrm_option_group` (`name`, `title`, `is_reserved`, `is_active`) VALUES ('how_long_have_you_been_a_donor', 'How long have you been a donor?', 0, 1);
 
 SELECT @ogid_contribution := MAX(id) FROM civicrm_option_group;
 
diff --git a/civicrm/templates/CRM/Admin/Page/APIExplorer.js b/civicrm/templates/CRM/Admin/Page/APIExplorer.js
index 0951b924aa..7d85e83586 100644
--- a/civicrm/templates/CRM/Admin/Page/APIExplorer.js
+++ b/civicrm/templates/CRM/Admin/Page/APIExplorer.js
@@ -576,7 +576,6 @@
 
   /**
    * Format value to look like php code
-   * TODO: Use short array syntax when we drop support for php 5.3
    * @param val
    */
   function phpFormat(val) {
@@ -736,7 +735,7 @@
       q.php += "]";
       q.json += "\n}";
     }
-    q.php += "];";
+    q.php += ");";
     q.json += ").done(function(result) {\n  // do something\n});";
     q.smarty += "}\n{foreach from=$result.values item=" + entity.toLowerCase() + "}\n  {$" + entity.toLowerCase() + ".some_field}\n{/foreach}";
     if (!_.includes(action, 'get')) {
diff --git a/civicrm/templates/CRM/Admin/Page/OptionGroup.tpl b/civicrm/templates/CRM/Admin/Page/OptionGroup.tpl
index f33ba6ed42..e8c60e31d8 100644
--- a/civicrm/templates/CRM/Admin/Page/OptionGroup.tpl
+++ b/civicrm/templates/CRM/Admin/Page/OptionGroup.tpl
@@ -36,20 +36,31 @@
 {if $rows}
 
 <div id="browseValues">
-    {strip}
+  {if $action ne 1 and $action ne 2}
+    <div class="action-link">
+      {crmButton q="action=add&reset=1" id="newOptionGroup"  icon="plus-circle"}{ts}Add Option Group{/ts}{/crmButton}
+      {crmButton p="civicrm/admin" q="reset=1" class="cancel" icon="times"}{ts}Done{/ts}{/crmButton}
+    </div>
+  {/if}
+
+  {strip}
   {* handle enable/disable actions*}
    {include file="CRM/common/enableDisableApi.tpl"}
     <table cellpadding="0" cellspacing="0" border="0">
         <tr class="columnheader">
-        <th>{ts}Title{/ts}</th>
-        <th>{ts}Name{/ts}</th>
-        <th></th>
+          <th>{ts}Title{/ts}</th>
+          <th>{ts}Name{/ts}</th>
+          <th>{ts}Reserved{/ts}</th>
+          <th>{ts}Enabled?{/ts}</th>
+          <th></th>
         </tr>
         {foreach from=$rows item=row}
-      <tr id="optionGroup-{$row.id}" class="crm-entity {cycle values="odd-row,even-row"} {$row.class}{if NOT $row.is_active} disabled{/if}">
-            <td class="crm-admin-optionGroup-title">{if $row.title}{$row.title}{else}( {ts}none{/ts} ){/if}</td>
-            <td class="crm-admin-optionGroup-name">{$row.name}</td>
-            <td><a href="{crmURL p="civicrm/admin/options" q="gid=`$row.id`&reset=1"}" title="{ts}View and Edit Options{/ts}">{ts}Options{/ts}</a></td>
+        <tr id="optionGroup-{$row.id}" class="crm-entity {cycle values="odd-row,even-row"} {$row.class}{if NOT $row.is_active} disabled{/if}">
+          <td class="crm-admin-optionGroup-title">{if $row.title}{$row.title}{else}( {ts}none{/ts} ){/if}</td>
+          <td class="crm-admin-optionGroup-name">{$row.name}</td>
+          <td class="crm-admin-optionGroup-is_reserved">{if $row.is_reserved eq 1} {ts}Yes{/ts} {else} {ts}No{/ts} {/if}</td>
+          <td class="crm-admin-optionGroup-is_active" id="row_{$row.id}_status">{if $row.is_active eq 1} {ts}Yes{/ts} {else} {ts}No{/ts} {/if}</td>
+          <td><a href="{crmURL p="civicrm/admin/options" q="gid=`$row.id`&reset=1"}" title="{ts}View and Edit Options{/ts}">{ts}Options{/ts}</a></td>
         </tr>
         {/foreach}
     </table>
@@ -59,10 +70,10 @@
       <div class="action-link">
           {crmButton q="action=add&reset=1" id="newOptionGroup"  icon="plus-circle"}{ts}Add Option Group{/ts}{/crmButton}
           {crmButton p="civicrm/admin" q="reset=1" class="cancel" icon="times"}{ts}Done{/ts}{/crmButton}
-        </div>
+      </div>
     {/if}
 </div>
-{elseif $action NEQ 1 && $action NEQ 2}
+{elseif $action ne 1 and $action ne 2}
     <div class="messages status no-popup">
         <img src="{$config->resourceBase}i/Inform.gif" alt="{ts}status{/ts}"/>
         {capture assign=crmURL}{crmURL p='civicrm/admin/optionGroup' q="action=add&reset=1"}{/capture}
diff --git a/civicrm/templates/CRM/Contact/Form/Search/Builder.js b/civicrm/templates/CRM/Contact/Form/Search/Builder.js
index 019add712d..904e7766c8 100644
--- a/civicrm/templates/CRM/Contact/Form/Search/Builder.js
+++ b/civicrm/templates/CRM/Contact/Form/Search/Builder.js
@@ -19,15 +19,16 @@
     var patt = /_1$/; // pattern to check if the change event came from field name
     if (field !== null && patt.test(this.id)) {
       // based on data type remove invalid operators e.g. IS EMPTY doesn't work with Boolean type column
+      var operators = CRM.searchBuilder.generalOperators;
       if ((field in CRM.searchBuilder.fieldTypes) === true) {
-        if (CRM.searchBuilder.fieldTypes[field] == 'Boolean') {
-          CRM.searchBuilder.generalOperators = _.omit(CRM.searchBuilder.generalOperators, ['IS NOT EMPTY', 'IS EMPTY']);
+        if ($.inArray(CRM.searchBuilder.fieldTypes[field], ['Boolean', 'Int']) > -1) {
+          operators = _.omit(operators, ['IS NOT EMPTY', 'IS EMPTY']);
         }
         else if (CRM.searchBuilder.fieldTypes[field] == 'String') {
-          CRM.searchBuilder.generalOperators = _.omit(CRM.searchBuilder.generalOperators, ['>', '<', '>=', '<=']);
+          operators = _.omit(operators, ['>', '<', '>=', '<=']);
         }
       }
-      buildOperator(operator, CRM.searchBuilder.generalOperators);
+      buildOperator(operator, operators);
     }
 
     // These Ops don't get any input field.
diff --git a/civicrm/templates/CRM/Contact/Form/Search/Criteria/Basic.tpl b/civicrm/templates/CRM/Contact/Form/Search/Criteria/Basic.tpl
index ba59ec4468..52f34e135d 100644
--- a/civicrm/templates/CRM/Contact/Form/Search/Criteria/Basic.tpl
+++ b/civicrm/templates/CRM/Contact/Form/Search/Criteria/Basic.tpl
@@ -23,170 +23,31 @@
  | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
  +--------------------------------------------------------------------+
 *}
-<table class="form-layout">
-  <tr>
-    <td><label>{ts}Complete OR Partial Name{/ts}</label><br />
-      {$form.sort_name.html}
-    </td>
-    <td>
-      <label>{ts}Complete OR Partial Email{/ts}</label><br />
-      {$form.email.html}
-    </td>
-    {if $form.contact_type}
-      <td><label>{ts}Contact Type(s){/ts}</label><br />
-        {$form.contact_type.html}
-      </td>
-    {else}
-      <td>&nbsp;</td>
-    {/if}
-  </tr>
-  <tr>
-  {if $form.group}
-    <td>
-      <div id='groupselect'><label>{ts}Group(s){/ts} <span class="description">(<a href="#" id='searchbygrouptype'>{ts}search by group type{/ts}</a>)</span></label>
-        <br />
-        {$form.group.html}
-    </div>
-    <div id='grouptypeselect'>
-      <label>{ts}Group Type(s){/ts} <span class="description"> (<a href="#" id='searchbygroup'>{ts}search by group{/ts}</a>)</span></label>
-      <br />
-      {$form.group_type.html}
-        {literal}
-        <script type="text/javascript">
-        CRM.$(function($) {
-          function showGroupSearch() {
-            $('#grouptypeselect').hide();
-            $('#groupselect').show();
-            $('#group_type').select2('val', '');
-            return false;
-          }
-          function showGroupTypeSearch() {
-            $('#groupselect').hide();
-            $('#grouptypeselect').show();
-            $('#group').select2('val', '');
-            return false;
-          }
-          $('#searchbygrouptype').click(showGroupTypeSearch);
-          $('#searchbygroup').click(showGroupSearch);
-
-          if ($('#group_type').val() ) {
-            showGroupTypeSearch();
-          }
-          else {
-            showGroupSearch();
-          }
-
-        });
-        </script>
-        {/literal}
-    </div>
-    </td>
-  {else}
-    <td>&nbsp;</td>
-  {/if}
-    {if $form.contact_tags}
-      <td><label>{ts}Select Tag(s){/ts}</label>
-        {$form.contact_tags.html}
-      </td>
-    {else}
-      <td>&nbsp;</td>
-    {/if}
-    {if $isTagset}
-      <td colspan="2">{include file="CRM/common/Tagset.tpl"}</td>
-    {/if}
-    <td>{$form.tag_search.label}  {help id="id-all-tags"}<br />{$form.tag_search.html}</td>
-    {if ! $isTagset}
-      <td colspan="2">&nbsp;</td>
-    {/if}
-    <td>&nbsp;</td>
-  </tr>
-  {if $form.all_tag_types}
-    <tr>
-      <td colspan="5">
-          {$form.all_tag_types.html} {$form.all_tag_types.label} {help id="id-all-tag-types"}
-      </td>
-    </tr>
-  {/if}
-  <tr>
-    <td>
-      <div>
-        {$form.phone_numeric.label}<br />{$form.phone_numeric.html}
-      </div>
-      <div class="description font-italic">
-        {ts}Punctuation and spaces are ignored.{/ts}
+<div class="advanced-search-fields basic-fields form-layout">
+  {foreach from=$basicSearchFields item=fieldSpec}
+    {assign var=field value=$form[$fieldSpec.name]}
+    {if $field}
+      <div class="search-field {$fieldSpec.class|escape}">
+        {if $fieldSpec.template}
+          {include file=$fieldSpec.template}
+        {else}
+          {$field.label}
+          {if $fieldSpec.help}
+            {assign var=help value=$fieldSpec.help}
+            {capture assign=helpFile}{if $fieldSpec.help}{$fieldSpec.help}{else}''{/if}{/capture}
+            {help id=$help.id file=$help.file}
+          {/if}
+          <br />
+          {$field.html}
+          {if $fieldSpec.description}
+            <div class="description font-italic">
+              {$fieldSpec.description}
+            </div>
+          {/if}
+        {/if}
       </div>
-    </td>
-    <td>{$form.phone_location_type_id.label}<br />{$form.phone_location_type_id.html}</td>
-    <td>{$form.phone_phone_type_id.label}<br />{$form.phone_phone_type_id.html}</td>
-  </tr>
-  <tr>
-    <td colspan="2">
-      <table class="form-layout-compressed">
-      <tr>
-        <td colspan="2">
-            {$form.privacy_toggle.html} {help id="id-privacy"}
-        </td>
-      </tr>
-      <tr>
-        <td>
-            {$form.privacy_options.html}
-        </td>
-        <td style="vertical-align:middle">
-            <div id="privacy-operator-wrapper">{$form.privacy_operator.html} {help id="privacy-operator"}</div>
-        </td>
-      </tr>
-      </table>
-      {literal}
-        <script type="text/javascript">
-          cj("select#privacy_options").change(function() {
-            if (cj(this).val() && cj(this).val().length > 1) {
-              cj('#privacy-operator-wrapper').show();
-            } else {
-              cj('#privacy-operator-wrapper').hide();
-            }
-          }).change();
-        </script>
-      {/literal}
-    </td>
-    <td colspan="3">
-      {$form.preferred_communication_method.label}<br />
-      {$form.preferred_communication_method.html}<br />
-      <div class="spacer"></div>
-      {$form.email_on_hold.html} {$form.email_on_hold.label}
-    </td>
-  </tr>
-  <tr>
-    <td>
-      {$form.contact_source.label} {help id="id-source" file="CRM/Contact/Form/Contact"}<br />
-      {$form.contact_source.html}
-    </td>
-    <td>
-      {$form.job_title.label}<br />
-      {$form.job_title.html}
-    </td>
-    <td colspan="3">
-      {$form.preferred_language.label}<br />
-      {$form.preferred_language.html}
-    </td>
-  </tr>
-  <tr>
-    <td>
-       {$form.contact_id.label} {help id="id-internal-id" file="CRM/Contact/Form/Contact"}<br />
-       {$form.contact_id.html}
-    </td>
-    <td>
-       {$form.external_identifier.label} {help id="id-external-id" file="CRM/Contact/Form/Contact"}<br />
-       {$form.external_identifier.html}
-    </td>
-    <td>
-      {if $form.uf_user}
-        {$form.uf_user.label} {$form.uf_user.html}
-        <div class="description font-italic">
-          {ts 1=$config->userFramework}Does the contact have a %1 Account?{/ts}
-        </div>
-      {else}
-        &nbsp;
-      {/if}
-    </td>
-  </tr>
-</table>
+    {elseif $fieldSpec.is_custom}
+      {include file=$fieldSpec.template}
+    {/if}
+  {/foreach}
+</div>
diff --git a/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/group.tpl b/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/group.tpl
new file mode 100644
index 0000000000..bcc83d76f6
--- /dev/null
+++ b/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/group.tpl
@@ -0,0 +1,49 @@
+<div id='groupselect'>
+  <label>{ts}Group(s){/ts}
+    <span class="description">
+      (<a href="#" id='searchbygrouptype'>{ts}search by group type{/ts}</a>)
+    </span>
+  </label>
+  <br/>
+  {$form.group.html}
+</div>
+<div id='grouptypeselect'>
+  <label>
+    {ts}Group Type(s){/ts}
+    <span class="description">
+      (<a href="#" id='searchbygroup'>{ts}search by group{/ts}</a>)
+    </span>
+  </label>
+  <br/>
+  {$form.group_type.html}
+  {literal}
+    <script type="text/javascript">
+      CRM.$(function ($) {
+        function showGroupSearch() {
+          $('#grouptypeselect').hide();
+          $('#groupselect').show();
+          $('#group_type').select2('val', '');
+          return false;
+        }
+
+        function showGroupTypeSearch() {
+          $('#groupselect').hide();
+          $('#grouptypeselect').show();
+          $('#group').select2('val', '');
+          return false;
+        }
+
+        $('#searchbygrouptype').click(showGroupTypeSearch);
+        $('#searchbygroup').click(showGroupSearch);
+
+        if ($('#group_type').val()) {
+          showGroupTypeSearch();
+        }
+        else {
+          showGroupSearch();
+        }
+
+      });
+    </script>
+  {/literal}
+</div>
diff --git a/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/preferred_communication_method.tpl b/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/preferred_communication_method.tpl
new file mode 100644
index 0000000000..3e94b37b2d
--- /dev/null
+++ b/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/preferred_communication_method.tpl
@@ -0,0 +1,10 @@
+{$form.preferred_communication_method.label}
+<br/>
+{$form.preferred_communication_method.html}
+<br/>
+
+{if $form.email_on_hold}
+  <div class="spacer"></div>
+  {$form.email_on_hold.html}
+  {$form.email_on_hold.label}
+{/if}
diff --git a/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/privacy_toggle.tpl b/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/privacy_toggle.tpl
new file mode 100644
index 0000000000..71e3c7642b
--- /dev/null
+++ b/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/privacy_toggle.tpl
@@ -0,0 +1,28 @@
+<table class="form-layout-compressed">
+  <tr>
+    <td colspan="2">
+      {$form.privacy_toggle.html} {help id="id-privacy"}
+    </td>
+  </tr>
+  <tr>
+    <td>
+      {$form.privacy_options.html}
+    </td>
+    <td style="vertical-align:middle">
+      <div id="privacy-operator-wrapper">
+        {$form.privacy_operator.html} {help id="privacy-operator"}
+      </div>
+    </td>
+  </tr>
+</table>
+{literal}
+  <script type="text/javascript">
+    cj("select#privacy_options").change(function () {
+      if (cj(this).val() && cj(this).val().length > 1) {
+        cj('#privacy-operator-wrapper').show();
+      } else {
+        cj('#privacy-operator-wrapper').hide();
+      }
+    }).change();
+  </script>
+{/literal}
diff --git a/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/tag_set.tpl b/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/tag_set.tpl
new file mode 100644
index 0000000000..48d97d5abd
--- /dev/null
+++ b/civicrm/templates/CRM/Contact/Form/Search/Criteria/Fields/tag_set.tpl
@@ -0,0 +1,5 @@
+{if $isTagset}
+  <div class="search-field search-field__span-2">
+    {include file="CRM/common/Tagset.tpl"}
+  </div>
+{/if}
diff --git a/civicrm/templates/CRM/Contribute/Form/ContributionPage/Settings.tpl b/civicrm/templates/CRM/Contribute/Form/ContributionPage/Settings.tpl
index b869e14eb9..c159c56e94 100644
--- a/civicrm/templates/CRM/Contribute/Form/ContributionPage/Settings.tpl
+++ b/civicrm/templates/CRM/Contribute/Form/ContributionPage/Settings.tpl
@@ -95,15 +95,11 @@
   </tr>
   <tr class="crm-contribution-contributionpage-settings-form-block-start_date">
       <td class ="label">{$form.start_date.label} {help id="id-start_date"}</td>
-      <td>
-          {include file="CRM/common/jcalendar.tpl" elementName=start_date}
-      </td>
+      <td>{$form.start_date.html}</td>
     </tr>
   <tr class="crm-contribution-contributionpage-settings-form-block-end_date">
       <td class ="label">{$form.end_date.label}</td>
-      <td>
-          {include file="CRM/common/jcalendar.tpl" elementName=end_date}
-      </td>
+      <td>{$form.end_date.html}</td>
     </tr>
   <tr class="crm-contribution-contributionpage-settings-form-block-honor_block_is_active">
       <td>&nbsp;</td><td>{$form.honor_block_is_active.html}{$form.honor_block_is_active.label} {help id="id-honoree_section"}</td>
diff --git a/civicrm/templates/CRM/Contribute/Form/ContributionPage/Widget.tpl b/civicrm/templates/CRM/Contribute/Form/ContributionPage/Widget.tpl
index d32391961f..ff1c9f73d5 100644
--- a/civicrm/templates/CRM/Contribute/Form/ContributionPage/Widget.tpl
+++ b/civicrm/templates/CRM/Contribute/Form/ContributionPage/Widget.tpl
@@ -92,9 +92,6 @@
           {ts}Edit Widget Colors{/ts}
          </div><!-- /.crm-accordion-header -->
          <div class="crm-accordion-body">
-            <div class="description">
-                {ts}Enter colors in hexadecimal format prefixed with <em>#</em>. EXAMPLE: <em>#FF0000</em> = Red. You can do a web search on 'hexadecimal colors' to find a chart of color codes.{/ts}
-            </div>
             <table class="form-layout-compressed">
             {foreach from=$colorFields item=field key=fieldName}
               <tr><td class="label">{$form.$fieldName.label}<span class="crm-marker"> *</span></td><td>{$form.$fieldName.html}</td></tr>
@@ -138,5 +135,5 @@
 </script>
 {/literal}
 {/crmRegion}
-{crmRegion name="contribute-form-contributionpage-widget-post}
+{crmRegion name="contribute-form-contributionpage-widget-post"}
 {/crmRegion}
diff --git a/civicrm/templates/CRM/Contribute/Page/Tab.tpl b/civicrm/templates/CRM/Contribute/Page/Tab.tpl
index 6d910f9399..f38a200afd 100644
--- a/civicrm/templates/CRM/Contribute/Page/Tab.tpl
+++ b/civicrm/templates/CRM/Contribute/Page/Tab.tpl
@@ -29,55 +29,91 @@
     {include file="CRM/Contribute/Form/ContributionView.tpl"}
 {else}
     <div class="contact-summary-contribute-tab view-content">
-        <div class="help">
+
+      <div id="secondaryTabContainer" class="ui-tabs ui-widget ui-widget-content ui-corner-all">
+        {* Tab management *}
+        <script type="text/javascript">
+          var selectedTab  = 'contributions';
+
+          {literal}
+          CRM.$(function($) {
+            var tabIndex = $('#tab_' + selectedTab).prevAll().length;
+            $("#secondaryTabContainer").tabs({active: tabIndex});
+            $(".crm-tab-button").addClass("ui-corner-bottom");
+          });
+          {/literal}
+        </script>
+        <ul class="ui-tabs-nav ui-corner-all ui-helper-reset ui-helper-clearfix ui-widget-header">
+          <li id="tab_contributions" class="crm-tab-button ui-corner-all ui-tabs-tab ui-corner-top ui-state-default ui-tab ui-tabs-active ui-state-active">
+            <a href="#contributions-subtab" title="{ts}Contributions{/ts}">
+              {ts}Contributions{/ts} <em>{$rows|@count}</em>
+            </a>
+          </li>
+          <li id="tab_recurring" class="crm-tab-button ui-corner-all ui-tabs-tab ui-corner-top ui-state-default ui-tab">
+            <a href="#recurring-subtab" title="{ts}Recurring Contributions{/ts}">
+              {ts}Recurring Contributions{/ts} <em>{$activeRecurRows|@count}</em>
+            </a>
+          </li>
+        </ul>
+
+        <div id="contributions-subtab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">
+          <div class="help">
             {if $permission EQ 'edit'}
               {capture assign=newContribURL}{crmURL p="civicrm/contact/view/contribution" q="reset=1&action=add&cid=`$contactId`&context=contribution"}{/capture}
               {capture assign=link}class="action-item" href="{$newContribURL}"{/capture}
               {ts 1=$link}Click <a %1>Record Contribution</a> to record a new contribution received from this contact.{/ts}
-                {if $newCredit}
-                  {capture assign=newCreditURL}{crmURL p="civicrm/contact/view/contribution" q="reset=1&action=add&cid=`$contactId`&context=contribution&mode=live"}{/capture}
-                  {capture assign=link}class="action-item" href="{$newCreditURL}"{/capture}
-                  {ts 1=$link}Click <a %1>Submit Credit Card Contribution</a> to process a new contribution on behalf of the contributor using their credit card.{/ts}
-                {/if}
+              {if $newCredit}
+                {capture assign=newCreditURL}{crmURL p="civicrm/contact/view/contribution" q="reset=1&action=add&cid=`$contactId`&context=contribution&mode=live"}{/capture}
+                {capture assign=link}class="action-item" href="{$newCreditURL}"{/capture}
+                {ts 1=$link}Click <a %1>Submit Credit Card Contribution</a> to process a new contribution on behalf of the contributor using their credit card.{/ts}
+              {/if}
             {else}
-                {ts 1=$displayName}Contributions received from %1 since inception.{/ts}
+              {ts 1=$displayName}Contributions received from %1 since inception.{/ts}
             {/if}
-        </div>
+          </div>
 
-        {if $action eq 16 and $permission EQ 'edit'}
+          {if $action eq 16 and $permission EQ 'edit'}
             <div class="action-link">
-                <a accesskey="N" href="{$newContribURL}" class="button"><span><i class="crm-i fa-plus-circle"></i> {ts}Record Contribution (Check, Cash, EFT ...){/ts}</span></a>
-                {if $newCredit}
-                    <a accesskey="N" href="{$newCreditURL}" class="button"><span><i class="crm-i fa-credit-card"></i> {ts}Submit Credit Card Contribution{/ts}</span></a>
-                {/if}
-                <br /><br />
+              <a accesskey="N" href="{$newContribURL}" class="button"><span><i class="crm-i fa-plus-circle"></i> {ts}Record Contribution (Check, Cash, EFT ...){/ts}</span></a>
+              {if $newCredit}
+                <a accesskey="N" href="{$newCreditURL}" class="button"><span><i class="crm-i fa-credit-card"></i> {ts}Submit Credit Card Contribution{/ts}</span></a>
+              {/if}
+              <br /><br />
             </div>
-          <div class='clear'></div>
-        {/if}
+            <div class='clear'></div>
+          {/if}
 
-        {if $rows}
+          {if $rows}
             {include file="CRM/Contribute/Page/ContributionTotals.tpl" mode="view"}
-          <div class='clear'></div>
+            <div class='clear'></div>
             {include file="CRM/Contribute/Form/Selector.tpl"}
-        {else}
+          {else}
             <div class="messages status no-popup">
-                    <div class="icon inform-icon"></div>
-                    {ts}No contributions have been recorded from this contact.{/ts}
+              <div class="icon inform-icon"></div>
+              {ts}No contributions have been recorded from this contact.{/ts}
             </div>
-        {/if}
-
-        {if $recur}
-          <div class="crm-block crm-contact-contribute-recur">
-            <h3>{ts}Recurring Contributions{/ts}</h3>
-            {include file="CRM/Contribute/Page/ContributionRecur.tpl"}
-          </div>
-        {/if}
+          {/if}
 
-        {if $softCredit}
-          <div class="crm-block crm-contact-contribute-softcredit">
-            <h3>{ts}Soft credits{/ts} {help id="id-soft_credit"}</h3>
-            {include file="CRM/Contribute/Page/ContributionSoft.tpl"}
-          </div>
-        {/if}
+          {if $softCredit}
+            <div class="crm-block crm-contact-contribute-softcredit">
+              <h3>{ts}Soft credits{/ts} {help id="id-soft_credit"}</h3>
+              {include file="CRM/Contribute/Page/ContributionSoft.tpl"}
+            </div>
+          {/if}
+        </div>
+        <div id="recurring-subtab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">
+          {if $recur}
+            <div class="crm-block crm-contact-contribute-recur crm-contact-contribute-recur-active">
+              <h3>{ts}Active Recurring Contributions{/ts}</h3>
+              {include file="CRM/Contribute/Page/ContributionRecur.tpl" recurRows=$activeRecurRows}
+            </div>
+            <div class="crm-block crm-contact-contribute-recur crm-contact-contribute-recur-inactive">
+              <h3>{ts}Inactive Recurring Contributions{/ts}</h3>
+              {include file="CRM/Contribute/Page/ContributionRecur.tpl" recurRows=$inactiveRecurRows}
+            </div>
+          {/if}
+        </div>
+        <div class="clear"></div>
+      </div>
     </div>
 {/if}
diff --git a/civicrm/templates/CRM/Event/Form/Registration/EventInfoBlock.tpl b/civicrm/templates/CRM/Event/Form/Registration/EventInfoBlock.tpl
index 8241fe3f51..4bd052b9a5 100644
--- a/civicrm/templates/CRM/Event/Form/Registration/EventInfoBlock.tpl
+++ b/civicrm/templates/CRM/Event/Form/Registration/EventInfoBlock.tpl
@@ -34,7 +34,7 @@
     {/if}
     </td>
   </tr>
-  <tr><td><label>{ts}When{/ts}</label></td>
+  <tr><td>{ts}When{/ts}</td>
       <td width="90%">
         {$event.event_start_date|crmDate}
         {if $event.event_end_date}
@@ -51,7 +51,7 @@
 
   {if $isShowLocation}
     {if $location.address.1}
-      <tr><td><label>{ts}Location{/ts}</label></td>
+      <tr><td>{ts}Location{/ts}</td>
           <td>
             {$location.address.1.display|nl2br}
             {if ( $event.is_map &&
@@ -66,7 +66,7 @@
   {/if}{*End of isShowLocation condition*}
 
   {if $location.phone.1.phone || $location.email.1.email}
-    <tr><td><label>{ts}Contact{/ts}</label></td>
+    <tr><td>{ts}Contact{/ts}</td>
         <td>
         {* loop on any phones and emails for this event *}
            {foreach from=$location.phone item=phone}
diff --git a/civicrm/templates/CRM/Event/Page/EventInfo.tpl b/civicrm/templates/CRM/Event/Page/EventInfo.tpl
index 23d1b9e314..5f0137a418 100644
--- a/civicrm/templates/CRM/Event/Page/EventInfo.tpl
+++ b/civicrm/templates/CRM/Event/Page/EventInfo.tpl
@@ -115,7 +115,7 @@
   {/if}
   <div class="clear"></div>
   <div class="crm-section event_date_time-section">
-      <div class="label"><label>{ts}When{/ts}</label></div>
+      <div class="label">{ts}When{/ts}</div>
       <div class="content">
             <abbr class="dtstart" title="{$event.event_start_date|crmDate}">
             {$event.event_start_date|crmDate}</abbr>
@@ -140,7 +140,7 @@
 
         {if $location.address.1}
             <div class="crm-section event_address-section">
-                <div class="label"><label>{ts}Location{/ts}</label></div>
+                <div class="label">{ts}Location{/ts}</div>
                 <div class="content">{$location.address.1.display|nl2br}</div>
                 <div class="clear"></div>
             </div>
@@ -164,7 +164,7 @@
 
   {if $location.phone.1.phone || $location.email.1.email}
       <div class="crm-section event_contact-section">
-          <div class="label"><label>{ts}Contact{/ts}</label></div>
+          <div class="label">{ts}Contact{/ts}</div>
           <div class="content">
               {* loop on any phones and emails for this event *}
               {foreach from=$location.phone item=phone}
@@ -186,7 +186,7 @@
 
   {if $event.is_monetary eq 1 && $feeBlock.value}
       <div class="crm-section event_fees-section">
-          <div class="label"><label>{$event.fee_label}</label></div>
+          <div class="label">{$event.fee_label}</div>
           <div class="content">
               <table class="form-layout-compressed fee_block-table">
                   {foreach from=$feeBlock.value name=fees item=value}
diff --git a/civicrm/templates/CRM/Group/Form/Search.tpl b/civicrm/templates/CRM/Group/Form/Search.tpl
index 6fe311cd28..99f7f48f80 100644
--- a/civicrm/templates/CRM/Group/Form/Search.tpl
+++ b/civicrm/templates/CRM/Group/Form/Search.tpl
@@ -24,45 +24,57 @@
  +--------------------------------------------------------------------+
 *}
 <div class="crm-block crm-form-block crm-group-search-form-block">
-
-<h3>{ts}Find Groups{/ts}</h3>
-<table class="form-layout">
-  <tr>
-    <td>
-      {$form.title.label}<br />
-      {$form.title.html}<br />
-      <span class="description font-italic">
+  <div class="crm-accordion-wrapper crm-search_builder-accordion {if $rows and !$showSearchForm}collapsed{/if}">
+    <div class="crm-accordion-header crm-master-accordion-header">
+      {ts}Find Groups{/ts}
+    </div>
+    <div class="crm-accordion-body">
+      <div id="searchForm">
+        <table class="form-layout">
+          <tr>
+            <td>
+              {$form.title.label}<br />
+              {$form.title.html}<br />
+              <span class="description font-italic">
           {ts}Complete OR partial group name.{/ts}
       </span>
-    </td>
-    <td>
-      {$form.created_by.label}<br />
-      {$form.created_by.html}<br />
-      <span class="description font-italic">
+            </td>
+            <td>
+              {$form.created_by.label}<br />
+              {$form.created_by.html}<br />
+              <span class="description font-italic">
           {ts}Complete OR partial creator name.{/ts}
       </span>
-    </td>
-    <td id="group_type-block">
-      {$form.group_type_search.label}<br />
-      {$form.group_type_search.html}<br />
-      <span class="description font-italic">
-          {ts}Filter search by group type(s).{/ts}
-      </span>
-    </td>
-    <td>
-      {$form.visibility.label}<br />
-      {$form.visibility.html}<br />
-      <span class="description font-italic">
+            </td>
+            <td>
+              {$form.visibility.label}<br />
+              {$form.visibility.html}<br />
+              <span class="description font-italic">
           {ts}Filter search by visibility.{/ts}
       </span>
-    </td>
-    <td>
-      {$form.group_status.label}<br />
-      {$form.group_status.html}
-    </td>
-  </tr>
-</table>
-</div>
+            </td>
+          </tr>
+          <tr>
+            <td id="group_type-block">
+              {$form.group_type_search.label}<br />
+              {$form.group_type_search.html}<br />
+              <span class="description font-italic">
+          {ts}Filter search by group type(s).{/ts}
+      </span>
+            </td>
+            <td>
+              {$form.group_status.label}<br />
+              {$form.group_status.html}
+            </td>
+            <td>
+              {$form.component_mode.label}<br />
+              {$form.component_mode.html}
+            </td>
+          </tr>
+        </table>
+      </div>
+    </div>
+  </div>
 <div class="css_right">
   <a class="crm-hover-button action-item" href="{crmURL q="reset=1&update_smart_groups=1"}">{ts}Update Smart Group Counts{/ts}</a> {help id="update_smart_groups"}
 </div>
@@ -119,6 +131,7 @@
           d.group_type = groupTypes,
           d.visibility = $(".crm-group-search-form-block select#visibility").val(),
           d.status = groupStatus,
+          d.component_mode = $(".crm-group-search-form-block select#component_mode").val(),
           d.showOrgInfo = {/literal}"{$showOrgInfo}"{literal},
           d.parentsOnly = parentsOnly
         }
diff --git a/civicrm/templates/CRM/Member/Form/MembershipType.tpl b/civicrm/templates/CRM/Member/Form/MembershipType.tpl
index 3c6bc9a3d0..1e679fcc9f 100644
--- a/civicrm/templates/CRM/Member/Form/MembershipType.tpl
+++ b/civicrm/templates/CRM/Member/Form/MembershipType.tpl
@@ -128,7 +128,7 @@
       <tr class="crm-membership-type-form-block-visibility">
         <td class="label">{$form.visibility.label}</td>
         <td>{$form.visibility.html}<br />
-          <span class="description">{ts}Is this membership type available for self-service signups ('Public') or assigned by CiviCRM 'staff' users only ('Admin'){/ts}</span>
+          <span class="description">{ts}Can this membership type be used for self-service signups ('Public'), or is it only for CiviCRM users with 'Edit Contributions' permission ('Admin').{/ts}</span>
         </td>
       </tr>
       <tr class="crm-membership-type-form-block-weight">
diff --git a/civicrm/templates/CRM/Profile/Form/Dynamic.tpl b/civicrm/templates/CRM/Profile/Form/Dynamic.tpl
index ffb5047aeb..fe559aafce 100644
--- a/civicrm/templates/CRM/Profile/Form/Dynamic.tpl
+++ b/civicrm/templates/CRM/Profile/Form/Dynamic.tpl
@@ -36,7 +36,9 @@
 
   <div class="crm-submit-buttons">
     <span class="crm-button">{$form._qf_Edit_upload_delete.html}</span>
-    <a class="button cancel" href="{$cancelURL}">{$cancelButtonText}</a>
+    {if $includeCancelButton}
+      <a class="button cancel" href="{$cancelURL}">{$cancelButtonText}</a>
+    {/if}
   </div>
 {else}
 {if ! empty( $fields )}
@@ -218,12 +220,14 @@
       {/if}
       <div class="crm-submit-buttons" style='{$floatStyle}'>
         {include file="CRM/common/formButtons.tpl"}{if $isDuplicate}<span class="crm-button">{$form._qf_Edit_upload_duplicate.html}</span>{/if}
-        <a class="button cancel" href="{$cancelURL}">
-          <span>
-            <i class="crm-i fa-times"></i>
-            {$cancelButtonText}
-          </span>
-        </a>
+        {if $includeCancelButton}
+          <a class="button cancel" href="{$cancelURL}">
+            <span>
+              <i class="crm-i fa-times"></i>
+              {$cancelButtonText}
+            </span>
+          </a>
+        {/if}
       </div>
     {/if}
     {if $help_post && $action neq 4}<br /><div class="messages help">{$help_post}</div>{/if}
diff --git a/civicrm/templates/CRM/UF/Form/AdvanceSetting.tpl b/civicrm/templates/CRM/UF/Form/AdvanceSetting.tpl
index 34c1f65ac2..018ee8cb48 100644
--- a/civicrm/templates/CRM/UF/Form/AdvanceSetting.tpl
+++ b/civicrm/templates/CRM/UF/Form/AdvanceSetting.tpl
@@ -50,12 +50,17 @@
             <td>{$form.post_URL.html} {help id='id-post_URL' file="CRM/UF/Form/Group.hlp"}</td>
         </tr>
 
-        <tr class="crm-uf-advancesetting-form-block-cancel_URL">
+        <tr class="crm-uf-advancesetting-form-block-add_cancel_button">
+            <td class="label"></td>
+            <td>{$form.add_cancel_button.html} {$form.add_cancel_button.label} {help id='id-add_cancel_button' file="CRM/UF/Form/Group.hlp"}</td>
+        </tr>
+
+        <tr class="cancel_button_section crm-uf-advancesetting-form-block-cancel_URL">
             <td class="label">{$form.cancel_URL.label}</td>
             <td>{$form.cancel_URL.html} {help id='id-cancel_URL' file="CRM/UF/Form/Group.hlp"}</td>
         </tr>
 
-        <tr class="crm-uf-advancesetting-form-block-cancel_button_text">
+        <tr class="cancel_button_section crm-uf-advancesetting-form-block-cancel_button_text">
             <td class="label">{$form.cancel_button_text.label}</td>
             <td>{$form.cancel_button_text.html} {help id='id-cancel_button_text' file="CRM/UF/Form/Group.hlp"}</td>
         </tr>
@@ -102,3 +107,13 @@
     </div><!-- / .crm-block -->
   </div><!-- /.crm-accordion-body -->
 </div><!-- /.crm-accordion-wrapper -->
+{literal}
+  <script type="text/javascript">
+  CRM.$(function($) {
+    $('.cancel_button_section').toggle($('#add_cancel_button').is(":checked"));
+    $('#add_cancel_button').click(function() {
+      $('.cancel_button_section').toggle($(this).is(":checked"));
+    });
+  });
+  </script>
+{/literal}
diff --git a/civicrm/templates/CRM/UF/Form/Group.hlp b/civicrm/templates/CRM/UF/Form/Group.hlp
index c1f72b76fa..9f12c705b5 100644
--- a/civicrm/templates/CRM/UF/Form/Group.hlp
+++ b/civicrm/templates/CRM/UF/Form/Group.hlp
@@ -36,7 +36,7 @@
 {htxt id='id-used_for'}
 {ts}Profiles can be used in many different ways (and a single profile can be used in multiple contexts){/ts}:<br />
     <ul class="left-alignment">
-    <li>{ts}You can collect additional information about a donor or event participant by including profiles directly in Online Contribution Pages and Event Registration forms. When using a profile in this manner you do not need to check any of the "Used For" checkboxes.{/ts}</li> 
+    <li>{ts}You can collect additional information about a donor or event participant by including profiles directly in Online Contribution Pages and Event Registration forms. When using a profile in this manner you do not need to check any of the "Used For" checkboxes.{/ts}</li>
     <li>{ts}Check <strong>Standalone Form or Directory</strong> if you want it to use this profile for custom forms, or contact listing and view screens (from the civicrm/profile path).{/ts}</li>
     <li>{ts}Check <strong>Search Results</strong> to use this profile to display an alternate set of results columns for CiviCRM Basic and Advanced Search.{/ts}</li>
     {if $config->userSystem->supports_form_extensions EQ '1'}
@@ -96,6 +96,13 @@
 {ts}If you are using this profile as a contact signup or edit form, and want to redirect the user to a static URL after they've submitted the form, you can also use contact tokens in URL - enter the complete URL here. If this field is left blank, the built-in Profile form will be redisplayed with a generic status message - 'Your contact information has been saved.'{/ts}
 {/htxt}
 
+{htxt id='id-add_cancel_button-title'}
+  {ts}Add Cancel Button{/ts}
+{/htxt}
+{htxt id='id-add_cancel_button'}
+{ts}Enable/Disable this checkbox to add/remove cancel button on the profile form.{/ts}
+{/htxt}
+
 {htxt id='id-cancel_URL-title'}
   {ts}Cancel Redirect{/ts}
 {/htxt}
@@ -125,7 +132,7 @@
 <p>{ts}When reCAPTCHA is enabled for a profile form, anonymous users are required to read an image with letters and numbers and enter the value in a field. This helps prevent abuse by automated scripts.{/ts}</p>
 <p>{ts 1="https://www.google.com/recaptcha" 2=$miscURL}To use reCAPTCHA you must sign up at <a href="%1" target="_blank">Google's reCaptcha site</a> to get your public and private keys. Then enter both keys in <a href="%2">Administer CiviCRM &raquo; System Settings &raquo; Misc (Undelete, PDFs, Limits, Logging, Captcha, etc.)</a>.{/ts}</p>
 <p><strong>{ts}Do not enable this feature if you are using this profile as an HTML Form Snippet embedded in a non-CiviCRM web page. reCAPTCHA requires dynamic page generation. Submitting a stand-alone form with reCAPTCHA included will always result in a reCAPTCHA validation error.{/ts}</strong></p>
-{if $config->userSystem->supports_form_extensions EQ '1'} 
+{if $config->userSystem->supports_form_extensions EQ '1'}
   <p><strong>{ts}reCAPTCHA is also not available when a profile is used inside the User Registration and My Account screens.{/ts}</strong></p>
 {/if}
 {/htxt}
diff --git a/civicrm/templates/CRM/common/civicrm.settings.php.template b/civicrm/templates/CRM/common/civicrm.settings.php.template
index ae4d6e269f..2895fed38c 100644
--- a/civicrm/templates/CRM/common/civicrm.settings.php.template
+++ b/civicrm/templates/CRM/common/civicrm.settings.php.template
@@ -399,6 +399,14 @@ if (!defined('CIVICRM_DB_CACHE_PREFIX')) {
   define('CIVICRM_DB_CACHE_PREFIX', '');
 }
 
+/**
+ * The cache system traditionally allowed a wide range of cache-keys, but some
+ * cache-keys are prohibited by PSR-16.
+ */
+if (!defined('CIVICRM_PSR16_STRICT')) {
+  define('CIVICRM_PSR16_STRICT', FALSE);
+}
+
 /**
  * If you have multilingual site and you are using the "inherit CMS language"
  * configuration option, but wish to, for example, use fr_CA instead of the
diff --git a/civicrm/vendor/autoload.php b/civicrm/vendor/autoload.php
index c18f803af6..af2f5cf4b0 100644
--- a/civicrm/vendor/autoload.php
+++ b/civicrm/vendor/autoload.php
@@ -4,4 +4,4 @@
 
 require_once __DIR__ . '/composer/autoload_real.php';
 
-return ComposerAutoloaderInit99d4995414f340fcb5325a17584ee78c::getLoader();
+return ComposerAutoloaderInitd31b8d6552946245209cda886e0ea284::getLoader();
diff --git a/civicrm/vendor/composer/autoload_psr4.php b/civicrm/vendor/composer/autoload_psr4.php
index 51d1856808..1db2a6d77c 100644
--- a/civicrm/vendor/composer/autoload_psr4.php
+++ b/civicrm/vendor/composer/autoload_psr4.php
@@ -9,6 +9,7 @@ return array(
     'Zend\\Validator\\' => array($vendorDir . '/zendframework/zend-validator/src'),
     'Zend\\Stdlib\\' => array($vendorDir . '/zendframework/zend-stdlib/src'),
     'Zend\\Escaper\\' => array($vendorDir . '/zendframework/zend-escaper/src'),
+    'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
     'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
     'PhpOffice\\PhpWord\\' => array($vendorDir . '/phpoffice/phpword/src/PhpWord'),
     'PhpOffice\\Common\\' => array($vendorDir . '/phpoffice/common/src/Common'),
diff --git a/civicrm/vendor/composer/autoload_real.php b/civicrm/vendor/composer/autoload_real.php
index 3d4fb21e76..a13baea823 100644
--- a/civicrm/vendor/composer/autoload_real.php
+++ b/civicrm/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@
 
 // autoload_real.php @generated by Composer
 
-class ComposerAutoloaderInit99d4995414f340fcb5325a17584ee78c
+class ComposerAutoloaderInitd31b8d6552946245209cda886e0ea284
 {
     private static $loader;
 
@@ -19,9 +19,9 @@ class ComposerAutoloaderInit99d4995414f340fcb5325a17584ee78c
             return self::$loader;
         }
 
-        spl_autoload_register(array('ComposerAutoloaderInit99d4995414f340fcb5325a17584ee78c', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInitd31b8d6552946245209cda886e0ea284', 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader();
-        spl_autoload_unregister(array('ComposerAutoloaderInit99d4995414f340fcb5325a17584ee78c', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInitd31b8d6552946245209cda886e0ea284', 'loadClassLoader'));
 
         $includePaths = require __DIR__ . '/include_paths.php';
         $includePaths[] = get_include_path();
@@ -31,7 +31,7 @@ class ComposerAutoloaderInit99d4995414f340fcb5325a17584ee78c
         if ($useStaticLoader) {
             require_once __DIR__ . '/autoload_static.php';
 
-            call_user_func(\Composer\Autoload\ComposerStaticInit99d4995414f340fcb5325a17584ee78c::getInitializer($loader));
+            call_user_func(\Composer\Autoload\ComposerStaticInitd31b8d6552946245209cda886e0ea284::getInitializer($loader));
         } else {
             $map = require __DIR__ . '/autoload_namespaces.php';
             foreach ($map as $namespace => $path) {
@@ -52,19 +52,19 @@ class ComposerAutoloaderInit99d4995414f340fcb5325a17584ee78c
         $loader->register(true);
 
         if ($useStaticLoader) {
-            $includeFiles = Composer\Autoload\ComposerStaticInit99d4995414f340fcb5325a17584ee78c::$files;
+            $includeFiles = Composer\Autoload\ComposerStaticInitd31b8d6552946245209cda886e0ea284::$files;
         } else {
             $includeFiles = require __DIR__ . '/autoload_files.php';
         }
         foreach ($includeFiles as $fileIdentifier => $file) {
-            composerRequire99d4995414f340fcb5325a17584ee78c($fileIdentifier, $file);
+            composerRequired31b8d6552946245209cda886e0ea284($fileIdentifier, $file);
         }
 
         return $loader;
     }
 }
 
-function composerRequire99d4995414f340fcb5325a17584ee78c($fileIdentifier, $file)
+function composerRequired31b8d6552946245209cda886e0ea284($fileIdentifier, $file)
 {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
         require $file;
diff --git a/civicrm/vendor/composer/autoload_static.php b/civicrm/vendor/composer/autoload_static.php
index 2266cbf316..479cffc2e2 100644
--- a/civicrm/vendor/composer/autoload_static.php
+++ b/civicrm/vendor/composer/autoload_static.php
@@ -4,7 +4,7 @@
 
 namespace Composer\Autoload;
 
-class ComposerStaticInit99d4995414f340fcb5325a17584ee78c
+class ComposerStaticInitd31b8d6552946245209cda886e0ea284
 {
     public static $files = array (
         'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
@@ -24,6 +24,7 @@ class ComposerStaticInit99d4995414f340fcb5325a17584ee78c
         ),
         'P' => 
         array (
+            'Psr\\SimpleCache\\' => 16,
             'Psr\\Http\\Message\\' => 17,
             'PhpOffice\\PhpWord\\' => 18,
             'PhpOffice\\Common\\' => 17,
@@ -66,6 +67,10 @@ class ComposerStaticInit99d4995414f340fcb5325a17584ee78c
         array (
             0 => __DIR__ . '/..' . '/zendframework/zend-escaper/src',
         ),
+        'Psr\\SimpleCache\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/simple-cache/src',
+        ),
         'Psr\\Http\\Message\\' => 
         array (
             0 => __DIR__ . '/..' . '/psr/http-message/src',
@@ -371,10 +376,10 @@ class ComposerStaticInit99d4995414f340fcb5325a17584ee78c
     public static function getInitializer(ClassLoader $loader)
     {
         return \Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInit99d4995414f340fcb5325a17584ee78c::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInit99d4995414f340fcb5325a17584ee78c::$prefixDirsPsr4;
-            $loader->prefixesPsr0 = ComposerStaticInit99d4995414f340fcb5325a17584ee78c::$prefixesPsr0;
-            $loader->classMap = ComposerStaticInit99d4995414f340fcb5325a17584ee78c::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInitd31b8d6552946245209cda886e0ea284::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInitd31b8d6552946245209cda886e0ea284::$prefixDirsPsr4;
+            $loader->prefixesPsr0 = ComposerStaticInitd31b8d6552946245209cda886e0ea284::$prefixesPsr0;
+            $loader->classMap = ComposerStaticInitd31b8d6552946245209cda886e0ea284::$classMap;
 
         }, null, ClassLoader::class);
     }
diff --git a/civicrm/vendor/composer/installed.json b/civicrm/vendor/composer/installed.json
index b7c16788d4..8bae0c3aa1 100644
--- a/civicrm/vendor/composer/installed.json
+++ b/civicrm/vendor/composer/installed.json
@@ -1166,6 +1166,56 @@
             "psr-3"
         ]
     },
+    {
+        "name": "psr/simple-cache",
+        "version": "1.0.1",
+        "version_normalized": "1.0.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-fig/simple-cache.git",
+            "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+            "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "time": "2017-10-23T01:57:42+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.0.x-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Psr\\SimpleCache\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "PHP-FIG",
+                "homepage": "http://www.php-fig.org/"
+            }
+        ],
+        "description": "Common interfaces for simple caching",
+        "keywords": [
+            "cache",
+            "caching",
+            "psr",
+            "psr-16",
+            "simple-cache"
+        ]
+    },
     {
         "name": "sabberworm/php-css-parser",
         "version": "6.0.1",
diff --git a/civicrm/vendor/psr/simple-cache/.editorconfig b/civicrm/vendor/psr/simple-cache/.editorconfig
new file mode 100644
index 0000000000..48542cbb41
--- /dev/null
+++ b/civicrm/vendor/psr/simple-cache/.editorconfig
@@ -0,0 +1,12 @@
+; This file is for unifying the coding style for different editors and IDEs.
+; More information at http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+indent_size = 4
+indent_style = space
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/civicrm/vendor/psr/simple-cache/LICENSE.md b/civicrm/vendor/psr/simple-cache/LICENSE.md
new file mode 100644
index 0000000000..e49a7c85a1
--- /dev/null
+++ b/civicrm/vendor/psr/simple-cache/LICENSE.md
@@ -0,0 +1,21 @@
+# The MIT License (MIT)
+
+Copyright (c) 2016 PHP Framework Interoperability Group
+
+> Permission is hereby granted, free of charge, to any person obtaining a copy
+> of this software and associated documentation files (the "Software"), to deal
+> in the Software without restriction, including without limitation the rights
+> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+> copies of the Software, and to permit persons to whom the Software is
+> furnished to do so, subject to the following conditions:
+>
+> The above copyright notice and this permission notice shall be included in
+> all copies or substantial portions of the Software.
+>
+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+> THE SOFTWARE.
diff --git a/civicrm/vendor/psr/simple-cache/README.md b/civicrm/vendor/psr/simple-cache/README.md
new file mode 100644
index 0000000000..43641d175c
--- /dev/null
+++ b/civicrm/vendor/psr/simple-cache/README.md
@@ -0,0 +1,8 @@
+PHP FIG Simple Cache PSR
+========================
+
+This repository holds all interfaces related to PSR-16.
+
+Note that this is not a cache implementation of its own. It is merely an interface that describes a cache implementation. See [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) for more details.
+
+You can find implementations of the specification by looking for packages providing the [psr/simple-cache-implementation](https://packagist.org/providers/psr/simple-cache-implementation) virtual package.
diff --git a/civicrm/vendor/psr/simple-cache/composer.json b/civicrm/vendor/psr/simple-cache/composer.json
new file mode 100644
index 0000000000..2978fa559a
--- /dev/null
+++ b/civicrm/vendor/psr/simple-cache/composer.json
@@ -0,0 +1,25 @@
+{
+    "name": "psr/simple-cache",
+    "description": "Common interfaces for simple caching",
+    "keywords": ["psr", "psr-16", "cache", "simple-cache", "caching"],
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "PHP-FIG",
+            "homepage": "http://www.php-fig.org/"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Psr\\SimpleCache\\": "src/"
+        }
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.0.x-dev"
+        }
+    }
+}
diff --git a/civicrm/vendor/psr/simple-cache/src/CacheException.php b/civicrm/vendor/psr/simple-cache/src/CacheException.php
new file mode 100644
index 0000000000..eba53815c0
--- /dev/null
+++ b/civicrm/vendor/psr/simple-cache/src/CacheException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Psr\SimpleCache;
+
+/**
+ * Interface used for all types of exceptions thrown by the implementing library.
+ */
+interface CacheException
+{
+}
diff --git a/civicrm/vendor/psr/simple-cache/src/CacheInterface.php b/civicrm/vendor/psr/simple-cache/src/CacheInterface.php
new file mode 100644
index 0000000000..99e8d95748
--- /dev/null
+++ b/civicrm/vendor/psr/simple-cache/src/CacheInterface.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Psr\SimpleCache;
+
+interface CacheInterface
+{
+    /**
+     * Fetches a value from the cache.
+     *
+     * @param string $key     The unique key of this item in the cache.
+     * @param mixed  $default Default value to return if the key does not exist.
+     *
+     * @return mixed The value of the item from the cache, or $default in case of cache miss.
+     *
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     *   MUST be thrown if the $key string is not a legal value.
+     */
+    public function get($key, $default = null);
+
+    /**
+     * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
+     *
+     * @param string                 $key   The key of the item to store.
+     * @param mixed                  $value The value of the item to store, must be serializable.
+     * @param null|int|\DateInterval $ttl   Optional. The TTL value of this item. If no value is sent and
+     *                                      the driver supports TTL then the library may set a default value
+     *                                      for it or let the driver take care of that.
+     *
+     * @return bool True on success and false on failure.
+     *
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     *   MUST be thrown if the $key string is not a legal value.
+     */
+    public function set($key, $value, $ttl = null);
+
+    /**
+     * Delete an item from the cache by its unique key.
+     *
+     * @param string $key The unique cache key of the item to delete.
+     *
+     * @return bool True if the item was successfully removed. False if there was an error.
+     *
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     *   MUST be thrown if the $key string is not a legal value.
+     */
+    public function delete($key);
+
+    /**
+     * Wipes clean the entire cache's keys.
+     *
+     * @return bool True on success and false on failure.
+     */
+    public function clear();
+
+    /**
+     * Obtains multiple cache items by their unique keys.
+     *
+     * @param iterable $keys    A list of keys that can obtained in a single operation.
+     * @param mixed    $default Default value to return for keys that do not exist.
+     *
+     * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
+     *
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     *   MUST be thrown if $keys is neither an array nor a Traversable,
+     *   or if any of the $keys are not a legal value.
+     */
+    public function getMultiple($keys, $default = null);
+
+    /**
+     * Persists a set of key => value pairs in the cache, with an optional TTL.
+     *
+     * @param iterable               $values A list of key => value pairs for a multiple-set operation.
+     * @param null|int|\DateInterval $ttl    Optional. The TTL value of this item. If no value is sent and
+     *                                       the driver supports TTL then the library may set a default value
+     *                                       for it or let the driver take care of that.
+     *
+     * @return bool True on success and false on failure.
+     *
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     *   MUST be thrown if $values is neither an array nor a Traversable,
+     *   or if any of the $values are not a legal value.
+     */
+    public function setMultiple($values, $ttl = null);
+
+    /**
+     * Deletes multiple cache items in a single operation.
+     *
+     * @param iterable $keys A list of string-based keys to be deleted.
+     *
+     * @return bool True if the items were successfully removed. False if there was an error.
+     *
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     *   MUST be thrown if $keys is neither an array nor a Traversable,
+     *   or if any of the $keys are not a legal value.
+     */
+    public function deleteMultiple($keys);
+
+    /**
+     * Determines whether an item is present in the cache.
+     *
+     * NOTE: It is recommended that has() is only to be used for cache warming type purposes
+     * and not to be used within your live applications operations for get/set, as this method
+     * is subject to a race condition where your has() will return true and immediately after,
+     * another script can remove it making the state of your app out of date.
+     *
+     * @param string $key The cache item key.
+     *
+     * @return bool
+     *
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     *   MUST be thrown if the $key string is not a legal value.
+     */
+    public function has($key);
+}
diff --git a/civicrm/vendor/psr/simple-cache/src/InvalidArgumentException.php b/civicrm/vendor/psr/simple-cache/src/InvalidArgumentException.php
new file mode 100644
index 0000000000..6a9524a20c
--- /dev/null
+++ b/civicrm/vendor/psr/simple-cache/src/InvalidArgumentException.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Psr\SimpleCache;
+
+/**
+ * Exception interface for invalid cache arguments.
+ *
+ * When an invalid argument is passed it must throw an exception which implements
+ * this interface
+ */
+interface InvalidArgumentException extends CacheException
+{
+}
diff --git a/civicrm/xml/schema/Core/CustomField.xml b/civicrm/xml/schema/Core/CustomField.xml
index 0effe4046d..8d81309544 100644
--- a/civicrm/xml/schema/Core/CustomField.xml
+++ b/civicrm/xml/schema/Core/CustomField.xml
@@ -262,6 +262,11 @@
     <title>Field Option Group</title>
     <comment>For elements with options, the option group id that is used</comment>
     <add>1.4</add>
+    <pseudoconstant>
+      <table>civicrm_option_group</table>
+      <keyColumn>id</keyColumn>
+      <labelColumn>title</labelColumn>
+    </pseudoconstant>
   </field>
   <field>
     <name>filter</name>
diff --git a/civicrm/xml/schema/Core/UFGroup.xml b/civicrm/xml/schema/Core/UFGroup.xml
index 22faceb79c..6339d9c6cf 100644
--- a/civicrm/xml/schema/Core/UFGroup.xml
+++ b/civicrm/xml/schema/Core/UFGroup.xml
@@ -281,4 +281,12 @@
     <localizable>true</localizable>
     <add>4.7</add>
   </field>
+  <field>
+    <name>add_cancel_button</name>
+    <title>Include Cancel Button</title>
+    <type>boolean</type>
+    <default>1</default>
+    <comment>Should a Cancel button be included in this Profile form.</comment>
+    <add>5.0</add>
+  </field>
 </table>
diff --git a/civicrm/xml/templates/message_templates/contribution_online_receipt_html.tpl b/civicrm/xml/templates/message_templates/contribution_online_receipt_html.tpl
index ec976ed0e5..5f3c65aa17 100644
--- a/civicrm/xml/templates/message_templates/contribution_online_receipt_html.tpl
+++ b/civicrm/xml/templates/message_templates/contribution_online_receipt_html.tpl
@@ -21,7 +21,7 @@
 
   <tr>
    <td>
-
+     {assign var="greeting" value="{contact.email_greeting}"}{if $greeting}<p>{$greeting},</p>{/if}
     {if $receipt_text}
      <p>{$receipt_text|htmlize}</p>
     {/if}
diff --git a/civicrm/xml/templates/message_templates/contribution_online_receipt_text.tpl b/civicrm/xml/templates/message_templates/contribution_online_receipt_text.tpl
index c6328bc46e..7f016a6d2a 100644
--- a/civicrm/xml/templates/message_templates/contribution_online_receipt_text.tpl
+++ b/civicrm/xml/templates/message_templates/contribution_online_receipt_text.tpl
@@ -1,3 +1,4 @@
+{assign var="greeting" value="{contact.email_greeting}"}{if $greeting}{$greeting},{/if}
 {if $receipt_text}
 {$receipt_text}
 {/if}
@@ -206,4 +207,4 @@
  {$customName}: {$customValue}
 {/if}
 {/foreach}
-{/if}
\ No newline at end of file
+{/if}
diff --git a/civicrm/xml/templates/message_templates/event_online_receipt_html.tpl b/civicrm/xml/templates/message_templates/event_online_receipt_html.tpl
index f7ebf3663e..f6fe878351 100644
--- a/civicrm/xml/templates/message_templates/event_online_receipt_html.tpl
+++ b/civicrm/xml/templates/message_templates/event_online_receipt_html.tpl
@@ -25,7 +25,7 @@
 
   <tr>
    <td>
-  <p>{contact.email_greeting},</p>
+     {assign var="greeting" value="{contact.email_greeting}"}{if $greeting}<p>{$greeting},</p>{/if}
 
     {if $event.confirm_email_text AND (not $isOnWaitlist AND not $isRequireApproval)}
      <p>{$event.confirm_email_text|htmlize}</p>
@@ -177,7 +177,7 @@
        </td>
      </tr>
     {/if}
-    {if $event.is_monetary}
+    {if $event.is_monetary and not $isRequireApproval}
 
       <tr>
        <th {$headerStyle}>
diff --git a/civicrm/xml/templates/message_templates/event_online_receipt_subject.tpl b/civicrm/xml/templates/message_templates/event_online_receipt_subject.tpl
index c847397dbb..709fb37aad 100644
--- a/civicrm/xml/templates/message_templates/event_online_receipt_subject.tpl
+++ b/civicrm/xml/templates/message_templates/event_online_receipt_subject.tpl
@@ -1 +1 @@
-{if $isOnWaitlist}{ts}Wait List Confirmation{/ts}{else}{ts}Registration Confirmation{/ts}{/if} - {$event.event_title}
\ No newline at end of file
+{if $isOnWaitlist}{ts}Wait List Confirmation{/ts}{elseif $isRequireApproval}{ts}Registration Request Confirmation{/ts}{else}{ts}Registration Confirmation{/ts}{/if} - {$event.event_title}
diff --git a/civicrm/xml/templates/message_templates/event_online_receipt_text.tpl b/civicrm/xml/templates/message_templates/event_online_receipt_text.tpl
index b0233b41ea..be1e020d70 100644
--- a/civicrm/xml/templates/message_templates/event_online_receipt_text.tpl
+++ b/civicrm/xml/templates/message_templates/event_online_receipt_text.tpl
@@ -1,5 +1,4 @@
-{contact.email_greeting},
-
+{assign var="greeting" value="{contact.email_greeting}"}{if $greeting}{$greeting},{/if}
 {if $event.confirm_email_text AND (not $isOnWaitlist AND not $isRequireApproval)}
 {$event.confirm_email_text}
 
@@ -101,7 +100,7 @@
 {if $payer.name}
 You were registered by: {$payer.name}
 {/if}
-{if $event.is_monetary} {* This section for Paid events only.*}
+{if $event.is_monetary and not $isRequireApproval} {* This section for Paid events only.*}
 
 ==========================================================={if $pricesetFieldsCount }===================={/if}
 
diff --git a/civicrm/xml/templates/message_templates/membership_online_receipt_html.tpl b/civicrm/xml/templates/message_templates/membership_online_receipt_html.tpl
index 2e6136414f..fbf9fdd460 100644
--- a/civicrm/xml/templates/message_templates/membership_online_receipt_html.tpl
+++ b/civicrm/xml/templates/message_templates/membership_online_receipt_html.tpl
@@ -21,7 +21,7 @@
 
   <tr>
    <td>
-
+     {assign var="greeting" value="{contact.email_greeting}"}{if $greeting}<p>{$greeting},</p>{/if}
     {if $receipt_text}
      <p>{$receipt_text|htmlize}</p>
     {/if}
diff --git a/civicrm/xml/templates/message_templates/membership_online_receipt_text.tpl b/civicrm/xml/templates/message_templates/membership_online_receipt_text.tpl
index aebd6d484e..aa61b62a6c 100644
--- a/civicrm/xml/templates/message_templates/membership_online_receipt_text.tpl
+++ b/civicrm/xml/templates/message_templates/membership_online_receipt_text.tpl
@@ -1,3 +1,4 @@
+{assign var="greeting" value="{contact.email_greeting}"}{if $greeting}{$greeting},{/if}
 {if $receipt_text}
 {$receipt_text}
 {/if}
diff --git a/civicrm/xml/version.xml b/civicrm/xml/version.xml
index 1cd4403134..b760ae838d 100644
--- a/civicrm/xml/version.xml
+++ b/civicrm/xml/version.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="iso-8859-1" ?>
 <version>
-  <version_no>5.3.2</version_no>
+  <version_no>5.4.0</version_no>
 </version>
diff --git a/includes/civicrm.basepage.php b/includes/civicrm.basepage.php
index a9d0b525de..78762ba068 100644
--- a/includes/civicrm.basepage.php
+++ b/includes/civicrm.basepage.php
@@ -1,7 +1,7 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 5                                                  |
+ | CiviCRM version 4.7                                                |
  +--------------------------------------------------------------------+
  | Copyright CiviCRM LLC (c) 2004-2018                                |
  +--------------------------------------------------------------------+
diff --git a/includes/civicrm.shortcodes.modal.php b/includes/civicrm.shortcodes.modal.php
index 6f249e1e30..d606a8a0b4 100644
--- a/includes/civicrm.shortcodes.modal.php
+++ b/includes/civicrm.shortcodes.modal.php
@@ -1,7 +1,7 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 5                                                  |
+ | CiviCRM version 4.7                                                |
  +--------------------------------------------------------------------+
  | Copyright CiviCRM LLC (c) 2004-2018                                |
  +--------------------------------------------------------------------+
diff --git a/includes/civicrm.shortcodes.php b/includes/civicrm.shortcodes.php
index 26e1b163cb..833a6dec48 100644
--- a/includes/civicrm.shortcodes.php
+++ b/includes/civicrm.shortcodes.php
@@ -1,7 +1,7 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 5                                                  |
+ | CiviCRM version 4.7                                                |
  +--------------------------------------------------------------------+
  | Copyright CiviCRM LLC (c) 2004-2018                                |
  +--------------------------------------------------------------------+
diff --git a/includes/civicrm.users.php b/includes/civicrm.users.php
index 800b8888eb..6c387d7818 100644
--- a/includes/civicrm.users.php
+++ b/includes/civicrm.users.php
@@ -1,7 +1,7 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 5                                                  |
+ | CiviCRM version 4.7                                                |
  +--------------------------------------------------------------------+
  | Copyright CiviCRM LLC (c) 2004-2018                                |
  +--------------------------------------------------------------------+
diff --git a/languages/civicrm.pot b/languages/civicrm.pot
index 4e28eef654..22349420e7 100644
--- a/languages/civicrm.pot
+++ b/languages/civicrm.pot
@@ -2,7 +2,7 @@
 # This file is distributed under the same license as the CiviCRM package.
 msgid ""
 msgstr ""
-"Project-Id-Version: CiviCRM 5\n"
+"Project-Id-Version: CiviCRM 4.6\n"
 "Report-Msgid-Bugs-To: http://wordpress.org/tag/civicrm\n"
 "POT-Creation-Date: 2014-11-11 09:48:56+00:00\n"
 "MIME-Version: 1.0\n"
diff --git a/uninstall.php b/uninstall.php
index c73696a982..7579c21f70 100644
--- a/uninstall.php
+++ b/uninstall.php
@@ -2,7 +2,7 @@
 
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 5                                                  |
+ | CiviCRM version 4.7                                                |
  +--------------------------------------------------------------------+
  | Copyright CiviCRM LLC (c) 2004-2018                                |
  +--------------------------------------------------------------------+
diff --git a/wp-cli/civicrm.php b/wp-cli/civicrm.php
index 4a02543027..a3c35c5521 100644
--- a/wp-cli/civicrm.php
+++ b/wp-cli/civicrm.php
@@ -2,7 +2,7 @@
 
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 5                                                  |
+ | CiviCRM version 4.7                                                |
  +--------------------------------------------------------------------+
  | Copyright CiviCRM LLC (c) 2004-2018                                |
  +--------------------------------------------------------------------+
@@ -379,7 +379,7 @@ if ( ! defined( 'CIVICRM_WPCLI_LOADED' ) ) {
 			$upload_dir = wp_upload_dir();
 			$settings_dir = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR;
 			civicrm_setup( $upload_dir['basedir'] . DIRECTORY_SEPARATOR );
-			WP_CLI::launch( "chmod 0777 $settings_dir -R" );
+			WP_CLI::launch( "chmod 0755 $settings_dir -R" );
 
 			# now we've got some files in place, require PEAR DB and check db setup
 			$dsn = "mysql://{$dbuser}:{$dbpass}@{$dbhost}/{$dbname}?new_link=true";
@@ -654,7 +654,7 @@ if ( ! defined( 'CIVICRM_WPCLI_LOADED' ) ) {
 
 			$restore_backup_dir .= '/plugins/restore/' . $date;
 
-			if ( ! mkdir( $restore_backup_dir, 777, true ) ) {
+			if ( ! mkdir( $restore_backup_dir, 0755, true ) ) {
 				return WP_CLI::error( 'Failed creating directory: ' . $restore_backup_dir );
 			}
 
@@ -999,7 +999,7 @@ if ( ! defined( 'CIVICRM_WPCLI_LOADED' ) ) {
 			# begin upgrade
 
 			$backup_dir .= '/plugins/' . $date;
-			if ( ! mkdir( $backup_dir, 777, true ) ) {
+			if ( ! mkdir( $backup_dir, 0755, true ) ) {
 				return WP_CLI::error( 'Failed creating directory: ' . $backup_dir );
 			}
 
-- 
GitLab