Resources

Useful references

Downloadable PDFs

ProductResource
Campaign FactoryProduct Privacy Notice
Campaign StudioProduct Privacy Notice
CertificationJapanese-Language Exam Study Guide for Acquia Certified Drupal 9 Site Builder
CertificationJapanese-Language Exam Study Guide for Acquia Certified Drupal 10 Site Builder
CertificationJapanese-Language Exam Study Guide for Acquia Certified Drupal 9 Developer
CertificationJapanese-Language Exam Study Guide for Acquia Certified Drupal 10 Developer
CertificationJapanese-Language Exam Study Guide for Acquia Certified Drupal 9 Front End Specialist
CertificationJapanese-Language Exam Study Guide for Acquia Certified Drupal 10 Front End Specialist
CertificationJapanese-Language Exam Study Guide for Acquia Certified Drupal 9 Back End Specialist
CertificationJapanese-Language Exam Study Guide for Acquia Certified Drupal 10 Back End Specialist
CertificationJapanese-Language Exam Study Guide for Acquia Certified Cloud Pro
CertificationJapanese-Language Exam Study Guide for Acquia Certified Campaign Studio Marketing Pro
CertificationJapanese-Language Exam Study Guide for Acquia Certified DAM Administrator
Classic DAMProduct Privacy Notice
Cloud Platform Email ServiceProduct Privacy Notice
Cloud PlatformProduct Privacy Notice
Content HubProduct Privacy Notice
Customer Data PlatformProduct Privacy Notice
DAMProduct Privacy Notice
EdgeProduct Privacy Notice - Edge products powered by Akamai
EdgeProduct Privacy Notice - Edge products powered by Cloudflare
PersonalizationProduct Privacy Notice
Site FactoryProduct Privacy Notice
Site StudioProduct Privacy Notice
SupportProduct Privacy Notice

Cloud Platform

acquia-inc-examples-file.inc

Examples for IP address restriction

Sample code for settings.php includes and $conf settings that help you quickly
lock down an Acquia Cloud environment using basic auth and / or IP whitelisting.

- All site lockdown logic in located in acquia.inc
- All settings are in $conf variables.
    - ``$conf['ah_basic_auth_credentials']`` An array of basic auth username /
      password combinations
    - ``$conf['ah_whitelist']`` An array of IP addresses to allow on to the site.
    - ``$conf['ah_blacklist']`` An array of IP addresses that will be denied access to the site.
    - ``$conf['ah_paths_no_cache']`` Paths we should explicitly never cache.
    - ``$conf['ah_paths_skip_auth']`` Skip basic authentication for these paths.
    - ``$conf['ah_restricted_paths']`` Paths which may not be accessed unless the user is on the IP whitelist.
- The site lockdown process happens by calling ``ac_protect_this_site();`` with defined $conf elements.
- Whitelist / blacklist IPs may use any of the following syntax:
    - CIDR (100.0.0.3/4)
    - Range (100.0.0.3-100.0.5.10)
    - Wildcard (100.0.0.*)
    - Single  (100.0.0.1)
    
## Business Logic
- With no $conf values set, ``ac_protect_this_site();`` will do nothing.
- If the path is marked as restricted, all users not on the whitelist will receive access denied.
- If a user's IP is on the blacklist and **not** on the whitelist they will receive access denied.
- Filling ``$conf['ah_basic_auth_credentials']`` will result in all requests being requring an .htaccess log in.
- Securing the site requires entries in both ``$conf['ah_whitelist']`` **and** ``$conf['ah_restricted_paths']``


## Examples

#### Block access to non-whitelisted users on all pages of non-production environments.
```
$conf['ah_restricted_paths'] = array(
  '*',
);

$conf['ah_whitelist'] = array(
  '100.0.0.*',
  '100.0.0.1/5',
);

if (file_exists('/var/www/site-php')) {
  require('/var/www/site-php/{site}/{site}-settings.inc');

  if(!defined('DRUPAL_ROOT')) {
    define('DRUPAL_ROOT', getcwd());
  }

  if (file_exists(DRUPAL_ROOT . '/sites/acquia.inc')) {
    if (isset($_ENV['AH_NON_PRODUCTION']) && $_ENV['AH_NON_PRODUCTION']) {
      require DRUPAL_ROOT . '/sites/acquia.inc';
      ac_protect_this_site();
    }
  }
}
```
#### Block access to user and admin pages on the production environment. Enforce .htaccess authentication on non-production.  Allow access to an API path without authentication
 
```
if (file_exists('/var/www/site-php')) {
  require('/var/www/site-php/{site}/{site}-settings.inc');

  if(!defined('DRUPAL_ROOT')) {
    define('DRUPAL_ROOT', getcwd());
  }

  if (file_exists(DRUPAL_ROOT . '/sites/acquia.inc')) {
    if (isset($_ENV['AH_SITE_ENVIRONMENT'])) {
      if ($_ENV['AH_SITE_ENVIRONMENT'] != 'prod') {
        $conf['ah_basic_auth_credentials'] = array(
          'Editor' => 'Password',
          'Admin' => 'P455w0rd',
        );
        $conf['ah_paths_no_cache'] = array(
          'api'
        );
      }
      else {
        $conf['ah_restricted_paths'] = array(
          'user',
          'user/*',
          'admin',
          'admin/*',
        );
        $conf['ah_whitelist'] = array(
          '100.0.0.9',
          '100.0.0.1/5',
        );
      }
      require DRUPAL_ROOT . '/sites/acquia.inc';
      ac_protect_this_site();
    }
  }
}
```

#### Blacklist known bad IPs on all environments

```
$conf['ah_blacklist'] = array(
  '12.13.14.15',
);

if (file_exists('/var/www/site-php')) {
  require('/var/www/site-php/{site}/{site}-settings.inc');

  if(!defined('DRUPAL_ROOT')) {
    define('DRUPAL_ROOT', getcwd());
  }

  if (file_exists(DRUPAL_ROOT . '/sites/acquia.inc')) {
    require DRUPAL_ROOT . '/sites/acquia.inc';
    ac_protect_this_site();
  }
}
```

acquia-inc-sample.inc

<?php
 
/**
 * @file
 * Utilities for use in protecting an environment via basic auth or IP whitelist.
 */
 
function ac_protect_this_site() {
  global $conf;
  $client_ip = ip_address();
 
  // Test if we are using drush (command-line interface)
  $cli = drupal_is_cli();
  
  // Default to not skipping the auth check
  $skip_auth_check = FALSE;
 
  // Is the user on the VPN? Default to FALSE.
  $on_vpn = $cli ? TRUE : FALSE;
 
  if (!empty($client_ip) && !empty($conf['ah_whitelist'])) {
    $on_vpn = ah_ip_in_list($client_ip, $conf['ah_whitelist']);
    $skip_auth_check = $skip_auth_check || $on_vpn;
  }
 
  // If the IP is not explicitly whitelisted check to see if the IP is blacklisted.
  if (!$on_vpn && !empty($client_ip) && !empty($conf['ah_blacklist'])) {
    if (ah_ip_in_list($client_ip, $conf['ah_blacklist'])) {
      ah_page_403($client_ip);
    }
  }
  // Check if we should skip auth check for this page.
  if (ah_path_skip_auth()) {
    $skip_auth_check = TRUE;
  }
 
  // Check if we should disable cache for this page.
  if (ah_path_no_cache()) {
    $conf['page_cache_maximum_age'] = 0;
  }
 
  // Is the page restricted to whitelist only? Default to FALSE.
  $restricted_page = FALSE;
 
  // Check to see whether this page is restricted.
  if (!empty($conf['ah_restricted_paths']) && ah_paths_restrict()) {
    $restricted_page = TRUE;
  }
 
  $protect_ip = !empty($conf['ah_whitelist']);
  $protect_password = !empty($conf['ah_basic_auth_credentials']);
 
  // Do not protect command line requests, e.g. Drush.
  if ($cli) {
    $protect_ip = FALSE;
    $protect_password = FALSE;
  }
 
  // Un-comment to disable protection, e.g. for load tests.
  // $skip_auth_check = TRUE;
  // $on_vpn = TRUE;
 
  // If not on whitelisted IP prevent access to protected pages.
  if ($protect_ip && !$on_vpn && $restricted_page) {
    ah_page_403($client_ip);
  }
 
  // If not skipping auth, check basic auth.
  if ($protect_password && !$skip_auth_check) {
    ah_check_basic_auth();
  }
}
 
/**
 * Output a 403 (forbidden access) response.
 */
function ah_page_403($client_ip) {
  header('HTTP/1.0 403 Forbidden');
  print "403 Forbidden: Access denied ($client_ip)";
  exit;
}
 
/**
 * Output a 401 (unauthorized) response.
 */
function ah_page_401($client_ip) {
  header('WWW-Authenticate: Basic realm="This site is protected"');
  header('HTTP/1.0 401 Unauthorized');
  print "401 Unauthorized: Access denied ($client_ip)";
  exit;
}
 
/**
 * Check basic auth against allowed values.
 */
function ah_check_basic_auth() {
  global $conf;
 
  $authorized = FALSE;
  $php_auth_user = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : NULL;
  $php_auth_pw = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : NULL;
  $credentials = isset($conf['ah_basic_auth_credentials']) ? $conf['ah_basic_auth_credentials'] : NULL;
 
  if ($php_auth_user && $php_auth_pw && !empty($credentials)) {
    if (isset($credentials[$php_auth_user]) && $credentials[$php_auth_user] == $php_auth_pw) {
      $authorized = TRUE;
    }
  }
 
  if ($authorized) {
    return;
  }
 
  // Always fall back to 401.
  ah_page_401(ip_address());
}
 
/**
 * Determine if the current path is in the list of paths to not cache.
 */
function ah_path_no_cache() {
  global $conf;
 
  $q = isset($_GET['q']) ? $_GET['q'] : NULL;
  $paths = isset($conf['ah_paths_no_cache']) ? $conf['ah_paths_no_cache'] : NULL;
  if (!empty($q) && !empty($paths)) {
    foreach ($paths as $path) {
      if ($q == $path || strpos($q, $path) === 0) {
        return TRUE;
      }
    }
  }
}
 
/**
 * Determine if the current path is in the list of paths on which to not check
 * auth.
 */
function ah_path_skip_auth() {
  global $conf;
 
  $q = isset($_GET['q']) ? $_GET['q'] : NULL;
  $paths = isset($conf['ah_paths_skip_auth']) ? $conf['ah_paths_skip_auth'] : NULL;
  if (!empty($q) && !empty($paths)) {
    foreach ($paths as $path) {
      if ($q == $path || strpos($q, $path) === 0) {
        return TRUE;
      }
    }
  }
}
 
/**
 * Check whether a path has been restricted.
 *
 */
function ah_paths_restrict() {
  global $conf;
 
  if (isset($_GET['q'])) {
 
    // Borrow some code from drupal_match_path()
    foreach ($conf['ah_restricted_paths'] as &$path) {
      $path = preg_quote($path, '/');
    }
 
    $paths = preg_replace('/\\\\\*/', '.*', $conf['ah_restricted_paths']);
    $paths = '/^(' . join('|', $paths) . ')$/';
 
    // If this is a restricted path, return TRUE.
    if (preg_match($paths, $_GET['q'])) {
      // Do not cache restricted paths
      $conf['page_cache_maximum_age'] = 0;
      return TRUE;
    }
  }
  return FALSE;
}
 
/**
 * Determine if the IP is within the ranges defined in the white/black list.
 */
function ah_ip_in_list($ip, $list) {
  foreach ($list as $item) {
 
    // Match IPs in CIDR format.
    if (strpos($item, '/') !== false) {
      list($range, $mask) = explode('/', $item);
 
      // Take the binary form of the IP and range.
      $ip_dec = ip2long($ip);
      $range_dec = ip2long($range);
 
      // Verify the given IPs are valid IPv4 addresses
      if (!$ip_dec || !$range_dec) {
        continue;
      }
 
      // Create the binary form of netmask.
      $mask_dec = ~ (pow(2, (32 - $mask)) - 1);
 
      // Run a bitwise AND to determine whether the IP and range exist
      // within the same netmask.
      if (($mask_dec & $ip_dec) == ($mask_dec & $range_dec)) {
        return TRUE;
      }
    }
 
    // Match against wildcard IPs or IP ranges.
    elseif (strpos($item, '*') !== false || strpos($item, '-') !== false) {
 
      // Construct a range from wildcard IPs
      if (strpos($item, '*') !== false) {
        $item = str_replace('*', 0, $item) . '-' . str_replace('*', 255, $item);
      }
 
      // Match against ranges by converting to long IPs.
      list($start, $end) = explode('-', $item);
 
      $start_dec = ip2long($start);
      $end_dec = ip2long($end);
      $ip_dec = ip2long($ip);
 
      // Verify the given IPs are valid IPv4 addresses
      if (!$start_dec || !$end_dec || !$ip_dec) {
        continue;
      }
 
      if ($start_dec <= $ip_dec && $ip_dec <= $end_dec) {
        return TRUE;
      }
    }
 
    // Match against single IPs
    elseif ($ip === $item) {
      return TRUE;
    }
  }
  return FALSE;
}

acquia_config.php

<?php

/**
 * @file
 * SimpleSamlPhp Acquia Configuration.
 *
 * This file was last modified on in July 2018.
 *
 * All custom changes below. Modify as needed.
 */

/**
 * Defines Acquia account specific options in $config keys.
 *
 *   - 'store.sql.name': Defines the Acquia Cloud database name which
 *     will store SAML session information.
 *   - 'store.type: Define the session storage service to use in each
 *     Acquia environment ("defualts to sql").
 */

// Set some security and other configs that are set above, however we
// overwrite them here to keep all changes in one area.
$config['technicalcontact_name'] = "Test Name";
$config['technicalcontact_email'] = "[email protected]";

// Change these for your installation.
$config['secretsalt'] = 'AddYourSaltStringHere';
$config['auth.adminpassword'] = 'ChangeThisPlease';

$config['admin.protectindexpage'] = TRUE;
//$config['admin.protectmetadata'] = TRUE;

/**
 * Support SSL Redirects to SAML login pages.
 *
 * Uncomment the code following code block to set
 * server port to 443 on HTTPS environment.
 *
 * This is a requirement in SimpleSAML when providing a redirect path.
 *
 * @link https://github.com/simplesamlphp/simplesamlphp/issues/450
 *
 */
// Prevent Varnish from interfering with SimpleSAMLphp.
// SSL terminated at the ELB / balancer so we correctly set the SERVER_PORT
// and HTTPS for SimpleSAMLphp baseurl configuration.
$protocol = 'http://';
$port = ':80';
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
  $_SERVER['SERVER_PORT'] = 443;
  $_SERVER['HTTPS'] = 'true';
  $protocol = 'https://';
  $port = ':' . $_SERVER['SERVER_PORT'];
}
$config['baseurlpath'] = $protocol . $port . '/simplesaml/';

$ah_options = array(
  // Use the database "role" without the "stage", e.g., "example", not
  // "exampletest" or "exampleprod".
  // Change the following line to match your database name.
  'database_name' => 'test',

  'session_store' => array(
    // Valid values are "memcache" and "database", database is recommended.
    // Note that the below config will be for only the dev, test, and prod
    // environments. If you would like to cover additional environments, list
    // them here.
    'prod' => 'database',
    'test' => 'database',
    'dev'  => 'database',
  ),
);

/**
 * Cookies No Cache.
 *
 * Allow users to be automatically logged in if they signed in via the same
 * SAML provider on another site by uncommenting the setcookie line below.
 *
 * Warning: This has performance implications for anonymous users.
 *
 * @link https://docs.acquia.com/resource/simplesaml/
 */
// Commenting out NO_CACHE cookie to prevent Varnish caching bypass.
// setcookie('NO_CACHE', '1');

/**
 * Generate Acquia session storage via hosting creds.json.
 *
 * Session storage defaults using the database for the current request.
 *
 * @link https://docs.acquia.com/resource/using-simplesamlphp-acquia-cloud-site/#storing-session-information-using-the-acquia-cloud-sql-database
 */

if (!getenv('AH_SITE_ENVIRONMENT')) {
  // Add / modify your local configuration here.
  $config['store.type'] = 'sql';
  $config['store.sql.dsn'] = sprintf('mysql:host=%s;port=%s;dbname=%s', '127.0.0.1', '', 'drupal');
  $config['store.sql.username'] = 'drupal';
  $config['store.sql.password'] = 'drupal';
  $config['store.sql.prefix'] = 'simplesaml';
  $config['certdir'] = "/var/www/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/simplesamlphp/cert/";
  $config['metadatadir'] = "/var/www/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/simplesamlphp/metadata";
  $config['baseurlpath'] = 'simplesaml/';
  $config['loggingdir'] = '/var/www/simplesamlphp/log/';

  // Enable as IdP for local Idp domains.
  if (in_array($_SERVER['SERVER_NAME'], ['local.example.com', 'employee.example.com'])) {
    $config['enable.saml20-idp'] = TRUE;
  }
}
elseif (getenv('AH_SITE_ENVIRONMENT')) {
  // Set ACE and ACSF sites based on hosting database and site name.
  $config['certdir'] = "/mnt/www/html/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/simplesamlphp/cert/";
  $config['metadatadir'] = "/mnt/www/html/{$_ENV['AH_SITE_GROUP']}.{$_ENV['AH_SITE_ENVIRONMENT']}/simplesamlphp/metadata";
  // Base url path already set above.
   $config['baseurlpath'] = 'simplesaml/';
  // Setup basic logging.
  $config['logging.handler'] = 'file';
  $config['loggingdir'] = dirname(getenv('ACQUIA_HOSTING_DRUPAL_LOG'));
  $config['logging.logfile'] = 'simplesamlphp-' . date('Ymd') . '.log';
  $creds_json = file_get_contents('/var/www/site-php/' . $_ENV['AH_SITE_GROUP'] . '.' . $_ENV['AH_SITE_ENVIRONMENT'] . '/creds.json');
  $databases = json_decode($creds_json, TRUE);
  $creds = $databases['databases'][$_ENV['AH_SITE_GROUP']];
  if (substr($_ENV['AH_SITE_ENVIRONMENT'], 0, 3) === 'ode') {
    $creds['host'] = key($creds['db_url_ha']);
  }
  else {
    require_once "/usr/share/php/Net/DNS2_wrapper.php";
    try {
      $resolver = new Net_DNS2_Resolver([
        'nameservers' => [
          '127.0.0.1',
          'dns-master',
        ],
      ]);
      $response = $resolver->query("cluster-{$creds['db_cluster_id']}.mysql", 'CNAME');
      $creds['host'] = $response->answer[0]->cname;
    }
    catch (Net_DNS2_Exception $e) {
      $creds['host'] = "";
    }
  }
  $config['store.type'] = 'sql';
  $config['store.sql.dsn'] = sprintf('mysql:host=%s;port=%s;dbname=%s', $creds['host'], $creds['port'], $creds['name']);
  $config['store.sql.username'] = $creds['user'];
  $config['store.sql.password'] = $creds['pass'];
  $config['store.sql.prefix'] = 'simplesaml';
}

api-notification-example.php

<?php

// This example requires `league/oauth2-client` package.
// Run `composer require league/oauth2-client` before running.
require __DIR__ . '/vendor/autoload.php';

use League\OAuth2\Client\Provider\GenericProvider;
use GuzzleHttp\Client;

// The UUID of an application you want to create the database for.
$applicationUuid = 'APP-UUID';
$dbName = 'test_database_1';
// See https://docs.acquia.com/cloud-platform/develop/api/auth/
// for how to generate a client ID and Secret.
$clientId = 'API-KEY';
$clientSecret = 'API-SECRET';

$provider = new GenericProvider([
    'clientId'                => $clientId,
    'clientSecret'            => $clientSecret,
    'urlAuthorize'            => '',
    'urlAccessToken'          => 'https://accounts.acquia.com/api/auth/oauth/token',
    'urlResourceOwnerDetails' => '',
]);

$client = new Client();
$provider->setHttpClient($client);

echo 'retrieving access token', PHP_EOL;
$accessToken = $provider->getAccessToken('client_credentials');
echo 'access token retrieved', PHP_EOL;

// Generate a request object using the access token.
$request = $provider->getAuthenticatedRequest(
    'POST',
    "https://cloud.acquia.com/api/applications/{$applicationUuid}/databases",
    $accessToken,
    [
        'headers' => ['Content-Type' => 'application/json'],
        'body' => json_encode(['name' => $dbName])
    ]
);

// Send the request.
echo 'requesting db create api', PHP_EOL;
$response = $client->send($request);

echo 'response parsing', PHP_EOL;
$responseBody = json_decode($response->getBody()->getContents(), true);

$notificationLink = $responseBody['_links']['notification']['href'];

$retryCount = 10;

echo 'start watching for notification status at ', $notificationLink, PHP_EOL;
do {
    sleep(5);
    // create notification request.
    $request = $provider->getAuthenticatedRequest(
        'GET',
        $notificationLink,
        $accessToken
    );

    echo 'requesting notification status', PHP_EOL;
    $response = $client->send($request);
    $responseBody = json_decode($response->getBody()->getContents(), true);
    echo 'notification status: ', $responseBody['status'], PHP_EOL;

    if ($responseBody['status'] === 'succeeded') {
        echo 'Successfully created database.';
        exit(0);
    } elseif ($responseBody['status'] === 'failed') {
        echo 'Failed to create database.';
        exit(1);
    } else {
        echo 'retrying notification in 5 sec', PHP_EOL;
        $retryCount--;
        $retry = $retryCount > 0;
    }
} while ($retry);

	

api-v2-auth.php

<?php
require __DIR__ . '/vendor/autoload.php';

use League\OAuth2\Client\Provider\GenericProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use GuzzleHttp\Client;

// See https://docs.acquia.com/cloud-platform/develop/api/auth/
// for how to generate a client ID and Secret.
$clientId = 'API Key';
$clientSecret = 'Api Secret';

$provider = new GenericProvider([
    'clientId'                => $clientId,
    'clientSecret'            => $clientSecret,
    'urlAuthorize'            => '',
    'urlAccessToken'          => 'https://accounts.acquia.com/api/auth/oauth/token',
    'urlResourceOwnerDetails' => '',
]);

try {
    // Try to get an access token using the client credentials grant.
    $accessToken = $provider->getAccessToken('client_credentials');

    // Generate a request object using the access token.
    $request = $provider->getAuthenticatedRequest(
        'GET',
        'https://cloud.acquia.com/api/account',
        $accessToken
    );

    // Send the request.
    $client = new Client();
    $response = $client->send($request);

    $responseBody = $response->getBody();


} catch (IdentityProviderException $e) {
    // Failed to get the access token.
    exit($e->getMessage());
}

 

myisam_to_innodb.sh.inc

#!/bin/sh
# Script to load a database, doing some conversions along the way

# EDIT THESE
dbfilename='db-backup.sql.gz'
dbuser='root'
dbpassword='rootpassword'
dbname='mydatabase'

# Flag to say whether we want to convert from innoDB to MyISAM (1 == yes)
# It will only convert the tables matching the regexp
innodb_to_myisam=0  
innodb_to_myisam_exclude_tables_regexp='^(locales_source|locales_target|menu_links|workbench_scheduler_types)$'

# Flag for converting MyISAM to InnoDB (1 == yes)
# It will only convert the tables matching the regexp
myisam_to_innodb=0
myisam_to_innodb_exclude_tables_regexp='^XXX$'

# Tables that will be created with structure only and NO data
no_data_import_tables_regexp='^(__ACQUIA_MONITORING|accesslog|batch|boost_cache|cache|cache_.*|history|queue|search_index|search_dataset|search_total|sessions|watchdog|panels_hash_database_cache|migrate_.*)$'

pv -p $dbfilename |gzip -d -c | awk -F'`' '
NR==1 { 
  # http://superuser.com/questions/246784/how-to-tune-mysql-for-restoration-from-mysql-dump
  # TODO? http://www.palominodb.com/blog/2011/08/02/mydumper-myloader-fast-backup-and-restore ?
  print "SET SQL_LOG_BIN=0;"
  print "SET unique_checks=0;"
  print "SET autocommit=0;"
  print "SET foreign_key_checks=0;"
  output=1;
} 
{ 
  start_of_line=substr($0,1,200);
  # Detect beginning of table structure definition.
  if (index(start_of_line, "-- Table structure for table")==1) {
    output=1
    print "COMMIT;"
    print "SET autocommit=0;"
    current_db=$2
  }
  # Switch the engine from InnoDB to MyISAM : MUCHO FAST. 
  if (substr(start_of_line,1,8)==") ENGINE") {
    if ('${innodb_to_myisam:-0}' == 1) {
      if (current_db ~ /'"$innodb_to_myisam_exclude_tables_regexp"'/) {
        print "Skipping InnoDB -> MyISAM for " current_db >"/dev/stderr"
      } else {
        gsub(/=InnoDB/, "=MyISAM", $0);
        #gsub(/CHARSET=utf8/, "CHARSET=latin1", $0);
      }
    }
    if ('${myisam_to_innodb:-0}' == 1) {
      if (current_db ~ /'"$myisam_to_innodb_exclude_tables_regexp"'/) {
        print "Skipping MyISAM -> InnoDB for " current_db >"/dev/stderr"
      } else {
        gsub(/=MyISAM/, "=InnoDB", $0);
      }
    }
  }
  # Detect beginning of table data dump.
  if (index(start_of_line, "-- Dumping data for table")==1) {
    if (current_db != $2) {
      print "Internal problem: unexpected data, seems to come from table " $2 " whereas expected table " current_db;
      current_db=$2
    }
    printf "\r Processing table " current_db > "/dev/stderr"
    output=1
    # Skip data in some tables
    if (current_db ~ /'"$no_data_import_tables_regexp"'/) {
      output=0
      print "Skipping Data import (imported structure only) for " current_db >"/dev/stderr"
    }
  }
  if (output==1) {
    print
  }
}
END {
  print "COMMIT;"
}' |mysql -u$dbuser --password=$dbpassword $dbname

example.sitename.conf

[ req ]
default_bits = 4096
default_keyfile = private.key
distinguished_name = req_distinguished_name
req_extensions = req_ext # The extensions to add to the self signed cert

[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = US
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Massachusetts
localityName = Locality Name (eg, city)
localityName_default = Boston
organizationName = Organization Name (eg, company)
organizationName_default = Acquia
organizationalUnitName = Organizational Unit Name (department, division)
organizationalUnitName_default =
commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_max = 64
commonName_default = localhost
emailAddress = Email Address (such as [email protected])
emailAddress_default =

[ req_ext ]
subjectAltName = @alt_names

[alt_names]
DNS.1 = www.example.com
DNS.2 = edit.example.com

memcache.yml

services:
  # Replaces the default lock backend with a memcache implementation.
  lock:
    class: Drupal\Core\Lock\LockBackendInterface
    factory: memcache.lock.factory:get

acsfd7.memcache.settings.php

<?php

/**
 * @file
 * Contains Drupal 7 Acquia memcache configuration to be added directly following the Acquia database require line
 * (see https://docs.acquia.com/cloud-platform/manage/code/require-line/ for more info)
 */

if (getenv('AH_SITE_ENVIRONMENT') &&
  isset($conf['memcache_servers'])
) {
  $conf['memcache_extension'] = 'Memcached';
  $conf['cache_backends'][] = 'sites/all/modules/contrib/memcache/memcache.inc';
  $conf['cache_default_class'] = 'MemCacheDrupal';
  $conf['cache_class_cache_form'] = 'DrupalDatabaseCache';

  // Enable compression
  $conf['memcache_options'][Memcached::OPT_COMPRESSION] = TRUE;

  $conf['memcache_stampede_protection_ignore'] = array(
  // Ignore some cids in 'cache_bootstrap'.
  'cache_bootstrap' => array(
    'module_implements',
    'variables',
    'lookup_cache',
    'schema:runtime:*',
    'theme_registry:runtime:*',
    '_drupal_file_scan_cache',
  ),
  // Ignore all cids in the 'cache' bin starting with 'i18n:string:'
  'cache' => array(
    'i18n:string:*',
  ),
  // Disable stampede protection for the entire 'cache_path' and 'cache_rules'
  // bins.
  'cache_path',
  'cache_rules',
);

# Move semaphore out of the database and into memory for performance purposes
  $conf['lock_inc'] = 'sites/all/modules/contrib/memcache/memcache-lock.inc';
}

authsources.php

<?php

// This file is available at
// https://docs.acquia.com/resource/simplesaml/sources/

$config = array(
    // This is a authentication source which handles admin authentication.
    'admin' => array(
        // The default is to use core:AdminPassword, but it can be replaced with
        // any authentication source.

        'core:AdminPassword',
    ),
    'default-sp' => array(
        'saml:SP',
        // The entityID is the entityID of the SP that the IdP is expecting.
        // This value must be exactly what the IdP is expecting. If the
        // entityID is not set, it defaults to the URL of the SP's metadata.
        // Don't declare an entityID for Site Factory.
        'entityID' => 'SP EntityID',

        // If the IdP requires the SP to hold a certificate, the location
        // of the self-signed certificate.
        // If you need to generate a SHA256 cert, see
        // https://gist.github.com/guitarte/5745b94c6883eaddabfea68887ba6ee6
        'certificate' => "../cert/saml.crt",
        'privatekey' => "../cert/saml.pem",
        'redirect.sign' => TRUE,
        'redirect.validate' => TRUE,

        // The entityID of the IdP.
        // This is included in the metadata from the IdP.
        'idp' => 'IdP EntityID',

        // NameIDFormat is included in the metadata from the IdP
        'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient',

        // If the IdP does not pass any attributes, but provides a NameID in
        // the authentication response, we can filter and add the value as an
        // attribute.
        // See https://simplesamlphp.org/docs/stable/saml:nameidattribute
        'authproc' => array(
                20 => array(
                        'class' => 'saml:NameIDAttribute',
                        'format' => '%V',
                ),
        ),
        // The RelayState parameter needs to be set if SSL is terminated
        // upstream. If you see the SAML response come back with
        // https://example.com:80/saml_login, you likely need to set this.
        // See https://github.com/simplesamlphp/simplesamlphp/issues/420
        'RelayState' => 'https://' . $_SERVER['HTTP_HOST'] . '/saml_login',

        // If working with ADFS, Microsoft may soon only allow SHA256 certs.
        // You must specify signature.algorithm as SHA256.
        // Defaults to SHA1 (http://www.w3.org/2000/09/xmldsig#rsa-sha1)
        // See https://docs.microsoft.com/en-us/security/trusted-root/program-requirements

        // 'signature.algorithm'  => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
    ),
);

clam_av_script.sh.inc

#!/bin/bash
#
# Shell script to scan the default files directory with ClamAV
# Arguments:
#  Email recipients: Comma separated list of email recipients wrapped in quotes
#  Site environment: Site name and environment formatted like [site].[env]
#

SCAN_OUTPUT=/mnt/tmp/clamscan.log
EMAIL_RECIPIENTS=$1
SITE_ENV=$2
DATE=$(date)
CRON_OUTPUT=/var/log/sites/${SITE_ENV}/logs/$(hostname -s)/clamscan.log

if [ -d /mnt/gfs/${SITE_ENV} ]
then
    {
    echo -e "=============================\nStarting scan ${DATE}\n"

    /usr/bin/clamscan -ri /mnt/gfs/${SITE_ENV}/sites/default/files > ${SCAN_OUTPUT}

    echo -e "Checking output...\n"

    cat ${SCAN_OUTPUT} | grep "FOUND"
    if [ $? -eq 0 ] ; then
        echo -e "FOUND VIRUS, SENDING EMAILS TO ${EMAIL_RECIPIENTS}.\n"
        cat ${SCAN_OUTPUT} | mail -s "${DATE} ClamAV has detected a virus on your website files directory" "${EMAIL_RECIPIENTS}"
    else
        echo -e "CLEAN, NO VIRUSES FOUND.\n"
    fi

    echo -e "Done\n=============================\n"
    } >> ${CRON_OUTPUT} 2>&1
else
    echo "ERROR: directory /mnt/gfs/${SITE_ENV} is not a valid path. Please update your scheduled task with the correct [site].[env] as the second parameter"
fi

cloud-memcache-d7.php

/**
 * @file
 * Contains Drupal 7 Acquia memcache configuration to be added directly following the Acquia database require line
 * (see https://docs.acquia.com/cloud-platform/manage/code/require-line/ for more info)
 */

if (getenv('AH_SITE_ENVIRONMENT') &&
  isset($conf['memcache_servers'])
) {
  $conf['memcache_extension'] = 'Memcached';
  $conf['cache_backends'][] = 'sites/all/modules/contrib/memcache/memcache.inc';
  $conf['cache_default_class'] = 'MemCacheDrupal';
  $conf['cache_class_cache_form'] = 'DrupalDatabaseCache';

  // Enable compression
  $conf['memcache_options'][Memcached::OPT_COMPRESSION] = TRUE;

  $conf['memcache_stampede_protection_ignore'] = array(
  // Ignore some cids in 'cache_bootstrap'.
  'cache_bootstrap' => array(
    'module_implements',
    'variables',
    'lookup_cache',
    'schema:runtime:*',
    'theme_registry:runtime:*',
    '_drupal_file_scan_cache',
  ),
  // Ignore all cids in the 'cache' bin starting with 'i18n:string:'
  'cache' => array(
    'i18n:string:*',
  ),
  // Disable stampede protection for the entire 'cache_path' and 'cache_rules'
  // bins.
  'cache_path',
  'cache_rules',
);

# Move semaphore out of the database and into memory for performance purposes
  $conf['lock_inc'] = 'sites/all/modules/contrib/memcache/memcache-lock.inc';
}

Default Memcached configuration

<?php
/**
 * @file
 * Contains caching configuration.
 * Last change: 2022-08-01
 */

use Composer\Autoload\ClassLoader;

/**
 * Use memcache as cache backend.
 *
 * Autoload memcache classes and service container in case module is not
 * installed. Avoids the need to patch core and allows for overriding the
 * default backend when installing Drupal.
 *
 * @see https://www.drupal.org/node/2766509
 */


// Determine if site is currently running under Acquia Cloud Next.
$is_acquia_cloud_next = (getenv("HOME") == "/home/clouduser");

if (getenv('AH_SITE_ENVIRONMENT') &&
  array_key_exists('memcache', $settings) &&
  array_key_exists('servers', $settings['memcache']) &&
  !empty($settings['memcache']['servers']) &&
  !$is_acquia_cloud_next
) {
  // Check for PHP Memcached libraries.
  $memcache_exists = class_exists('Memcache', FALSE);
  $memcached_exists = class_exists('Memcached', FALSE);
  $memcache_services_yml = DRUPAL_ROOT . '/modules/contrib/memcache/memcache.services.yml';
  $memcache_module_is_present = file_exists($memcache_services_yml);
  if ($memcache_module_is_present && ($memcache_exists || $memcached_exists)) {
    // Use Memcached extension if available.
    if ($memcached_exists) {
      $settings['memcache']['extension'] = 'Memcached';
    }
    if (class_exists(ClassLoader::class)) {
      $class_loader = new ClassLoader();
      $class_loader->addPsr4('Drupal\\memcache\\', DRUPAL_ROOT . '/modules/contrib/memcache/src');
      $class_loader->register();
      $settings['container_yamls'][] = $memcache_services_yml;

      // Acquia Default Settings for the memcache module
      // Default settings for the Memcache module.
      // Enable compression for PHP 7.
      $settings['memcache']['options'][Memcached::OPT_COMPRESSION] = TRUE;

      // Set key_prefix to avoid drush cr flushing all bins on multisite.
      $settings['memcache']['key_prefix'] = $conf['acquia_hosting_site_info']['db']['name'] . '_';

      // Decrease latency.
      $settings['memcache']['options'][Memcached::OPT_TCP_NODELAY] = TRUE;

      // Bootstrap cache.container with memcache rather than database.
      $settings['bootstrap_container_definition'] = [
        'parameters' => [],
        'services' => [
          'database' => [
            'class' => 'Drupal\Core\Database\Connection',
            'factory' => 'Drupal\Core\Database\Database::getConnection',
            'arguments' => ['default'],
          ],
          'settings' => [
            'class' => 'Drupal\Core\Site\Settings',
            'factory' => 'Drupal\Core\Site\Settings::getInstance',
          ],
          'memcache.settings' => [
            'class' => 'Drupal\memcache\MemcacheSettings',
            'arguments' => ['@settings'],
          ],
          'memcache.factory' => [
            'class' => 'Drupal\memcache\Driver\MemcacheDriverFactory',
            'arguments' => ['@memcache.settings'],
          ],
          'memcache.timestamp.invalidator.bin' => [
            'class' => 'Drupal\memcache\Invalidator\MemcacheTimestampInvalidator',
            'arguments' => ['@memcache.factory', 'memcache_bin_timestamps', 0.001],
          ],
          'memcache.backend.cache.container' => [
            'class' => 'Drupal\memcache\DrupalMemcacheInterface',
            'factory' => ['@memcache.factory', 'get'],
            'arguments' => ['container'],
          ],
          'cache_tags_provider.container' => [
            'class' => 'Drupal\Core\Cache\DatabaseCacheTagsChecksum',
            'arguments' => ['@database'],
          ],
          'cache.container' => [
            'class' => 'Drupal\memcache\MemcacheBackend',
            'arguments' => [
              'container',
              '@memcache.backend.cache.container',
              '@cache_tags_provider.container',
              '@memcache.timestamp.invalidator.bin',
              '@memcache.settings',
            ],
          ],
        ],
      ];

      // Content Hub 2.x requires the Depcalc module which needs to use the database backend.
      $settings['cache']['bins']['depcalc'] = 'cache.backend.database';

      // Use memcache for bootstrap, discovery, config instead of fast chained
      // backend to properly invalidate caches on multiple webs.
      // See https://www.drupal.org/node/2754947
      $settings['cache']['bins']['bootstrap'] = 'cache.backend.memcache';
      $settings['cache']['bins']['discovery'] = 'cache.backend.memcache';
      $settings['cache']['bins']['config'] = 'cache.backend.memcache';

      // Use memcache as the default bin.
      $settings['cache']['default'] = 'cache.backend.memcache';
    }
  }
}

Content Hub

ach-bulk-import-batch-functions.php

<?php

/**
 * Process a subset of all the entities to be enqueued in a single request.
 *
 * @param $entity_type
 *   The entity type.
 * @param $bundle
 *   The entity bundle.
 * @param $bundle_key
 *   THe entity bundle key.
 */
function export_enqueue_entities($entity_type, $bundle, $entity_ids, &$context) {
  /**
   * Number of entities per iteration. Decrease this number if your site has
   * too many dependencies per node.
   *
   * @var int $entities_per_iteration
   */
  $entities_per_iteration = 5;

  if (empty($context['sandbox'])) {
    $context['sandbox']['progress'] = 0;
    $context['sandbox']['max'] = count($entity_ids);
    $context['results']['total'] = 0;
  }

  /** @var \Drupal\acquia_contenthub\EntityManager $entity_manager */
  $entity_manager = \Drupal::service('acquia_contenthub.entity_manager');
  /** @var \Drupal\acquia_contenthub\Controller\ContentHubEntityExportController $export_controller */
  $export_controller = \Drupal::service('acquia_contenthub.acquia_contenthub_export_entities');

  $slice_entity_ids = array_slice($entity_ids, $context['sandbox']['progress'], $entities_per_iteration);
  $ids = array_values($slice_entity_ids);
  if (!empty($ids)) {
    $entities = \Drupal::entityTypeManager()
      ->getStorage($entity_type)
      ->loadMultiple($ids);
    foreach ($entities as $entity) {
      if ($entity_manager->isEligibleEntity($entity)) {
          // Entity is eligible, then re-export.
          $export_controller->exportEntities([$entity]);
      }
    }
  }
  $context['sandbox']['progress'] += count($ids);

  $enqueued = implode(',', $ids);
  $message = empty($enqueued) ? "Enqueuing '$entity_type' ($bundle) entities: No entities to queue." :  "Enqueuing '$entity_type' ($bundle) entities with IDs: " . $enqueued . "\n";

  $context['results']['total'] += count($ids);
  $context['message'] = $message;

  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  }

}

function export_enqueue_finished($success, $results, $operations) {
  // The 'success' parameter means no fatal PHP errors were detected. All
  // other error management should be handled using 'results'.
  if ($success) {
    $message = 'Total number of enqueued entities: ' . $results['total'];
  }
  else {
    $message = t('Finished with an error.');
  }
  drush_print($message);
}

content-hub-enqueue-entity-eligibility.php

<?php

    namespace Drupal\acquia_contenthub_publisher\EventSubscriber\EnqueueEligibility;

    use Drupal\acquia_contenthub_publisher\AcquiaContentHubPublisherEvents;
    use Drupal\acquia_contenthub_publisher\Event\ContentHubEntityEligibilityEvent;
    use Drupal\file\FileInterface;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;

    /**
     * Subscribes to entity eligibility to prevent enqueueing temporary files.
     */
    class FileIsTemporary implements EventSubscriberInterface {

      /**
       * {@inheritdoc}
       */
      public static function getSubscribedEvents() {
        $events[AcquiaContentHubPublisherEvents::ENQUEUE_CANDIDATE_ENTITY][] = ['onEnqueueCandidateEntity', 50];
        return $events;
      }

      /**
       * Prevent temporary files from enqueueing.
       *
       * @param \Drupal\acquia_contenthub_publisher\Event\ContentHubEntityEligibilityEvent $event
       *   The event to determine entity eligibility.
       */
      public function onEnqueueCandidateEntity(ContentHubEntityEligibilityEvent $event) {
        // If this is a file with status = 0 (TEMPORARY FILE) do not export it.
        // This is a check to avoid exporting temporary files.
        $entity = $event->getEntity();
        if ($entity instanceof FileInterface && $entity->isTemporary()) {
          $event->setEligibility(FALSE);
          $event->stopPropagation();
        }
      }

    }

ach-bulk-import.php

<?php
/**
 * @file
 * Add entities from Content Hub to the Import Queue.
 *
 * Please locate this field in the 'scripts' directory as a sibling of docroot:
 * <DOCROOT>/../scripts/ach-bulk-import.php
 *
 * To run the script, execute the drush command:
 * $drush scr ../scripts/ach-bulk-import.php
 *
 * Make sure to enable the Import Queue before executing this script.
 *
 * Notes:
 *
 * 1) If you want to explicitly avoid importing a particular entity type, please
 *    add it to the list of $global_excluded_types.
 * 2) By default importing includes all dependencies. To change this behavior
 *    change the variable $include_dependencies to FALSE.
 * 3) You can decided whether to publish entities after importing them. To
 *    publish entities after importing, set variable $publishing_status to 1.
 *    Setting $publishing_status to 0 imports them as unpublished.
 * 4) You can decide to use FIFO (first exported entities are imported first),
 *    or LIFO (last exported entities are imported first), according to the
 *    $fifo variable: $fifo = 1 uses FIFO, $fifo = 0 uses LIFO.
 * 5) You can set the author of the nodes to be imported locally. Example: If
 *    you set the $uid = 1, it will import all nodes as administrator (author
 *    is administrator). Change it to specific UID to use as author.
 */

use Drupal\acquia_contenthub\ContentHubEntityDependency;
use Drupal\Component\Serialization\Json;

// Global exclusion of entity types.
$global_excluded_types = [
  //   'redirect' => 'redirect',
];

// Include importing dependencies. By default it is "TRUE".
$include_dependencies = TRUE;

// Determine if we want to publish imported entities or not.
// 1: Publish entities, 0: Do not publish.
$publishing_status = 1;

// If TRUE, it will import from the last page to the first (FIFO: first entities
// exported will be the first to import), otherwise will use LIFO (Last exported
// entities will be imported first).
$fifo = TRUE;

// Determine the author UUID for the nodes to be created.
$uid = 1; // administrator.
$user = \Drupal\user\Entity\User::load($uid);
$author = $user->uuid();

/** @var \Drupal\acquia_contenthub\ContentHubEntitiesTracking $entities_tracking */
$entities_tracking = \Drupal::service('acquia_contenthub.acquia_contenthub_entities_tracking');

// Loading ClientManager to be able to execute requests to Content Hub and
// to check connection.
/** @var \Drupal\acquia_contenthub\Client\ClientManager $client_manager */
$client_manager = \Drupal::service('acquia_contenthub.client_manager');
$client = $client_manager->getConnection();

// The ImportEntityManager Service allows to import entities.
/** @var \Drupal\acquia_contenthub\ImportEntityManager $import_manager */
$import_manager = \Drupal::service("acquia_contenthub.import_entity_manager");

// List all the 'dependent' entities type IDs.
$dependent_entity_type_ids = ContentHubEntityDependency::getPostDependencyEntityTypes();

$excluded_types = array_merge($global_excluded_types, $dependent_entity_type_ids);

// Checks whether the import queue has been enabled.
$import_with_queue = \Drupal::config('acquia_contenthub.entity_config')->get('import_with_queue');
if (!$import_with_queue) {
  drush_user_abort('Please enable the Import Queue.');
}

// Check if the site is connected to Content Hub.
if (!$client_manager->isConnected()) {
  return;
}

$list = $client_manager->createRequest('listEntities', [[]]);
$total = floor($list['total'] / 1000) * 1000;

// Starting page.
$start = $fifo ? $total : 0;
// Step
$step = $fifo ? -1000 : 1000;

// Counter of queued entities.
$i = 0;
do {
  // List all entities you want to import by modifying the $options array.
  /*
  * Example of how to structure the $options parameter:
  *
  * $options = [
  *     'type'   => 'node',
  *     'origin' => '11111111-1111-1111-1111-111111111111',
  *     'filters' => [
  *         'status' => 1,
  *         'title' => 'New*',
  *         'body' => '/Boston/',
  *     ],
  * ];
  *
  */
  $options = [
    'start' => $start,
  ];
  $list = $client_manager->createRequest('listEntities', [$options]);
  foreach ($list['data'] as $entity) {
    $i++;
    // We do not want to import "dependent" entities.
    // These 3 lines are not needed in this example, but if we are listing all
    // entities, make sure to exclude dependent entities to be sent directly to
    // the importRemoteEntity() method because you would not be sure if their
    // host (parent) entity exist in the system yet.
    if (in_array($entity['type'], $excluded_types)) {
      drush_print("{$i}) Skipped entity type = {$entity['type']} , UUID = {$entity['uuid']} (Dependent or excluded entity type)");
      continue;
    }

    // Do not import the entity if it has been previously imported and has the
    // same "modified" flag, which means there are no new updates on the entity.
    if ($imported_entity = $entities_tracking->loadImportedByUuid($entity['uuid'])) {
      if ($imported_entity->getModified() === $entity['modified']) {
        drush_print("{$i}) Skipped entity type = {$entity['type']} , UUID = {$entity['uuid']} (Entity already imported)");
        continue;
      }
    }

    // Add entity to import queue.
    try {
      $response = $import_manager->addEntityToImportQueue($entity['uuid'], $include_dependencies, $author, $publishing_status);
      $status = Json::decode($response->getContent());
      if (!empty($status['status']) && $status['status'] == 200) {
        drush_print("{$i}) Entity added to import queue: type = {$entity['type']} , UUID = {$entity['uuid']}");
      }
      else {
        drush_print("{$i}) ERROR: Cannot add entity to import queue: type = {$entity['type']} , UUID = {$entity['uuid']}");
      }
    } catch (\Drupal\Core\Entity\EntityStorageException $ex) {
      drush_print("{$i}) ERROR: Failed to add entity to import queue: type = {$entity['type']} , UUID = {$entity['uuid']} [{$ex->getMessage()}]");
    }
  }
  $start = $start + $step;
  $exit_condition = $fifo ? $start >= 0 : $start <= $total;
} while ($exit_condition);

content-hub-publish-entities.php


<?php

   namespace Drupal\acquia_contenthub_publisher\EventSubscriber\PublishEntities;

   use Drupal\acquia_contenthub_publisher\AcquiaContentHubPublisherEvents;
   use Drupal\acquia_contenthub_publisher\Event\ContentHubPublishEntitiesEvent;
   use Drupal\acquia_contenthub_publisher\PublisherTracker;
   use Drupal\Core\Database\Connection;
   use Symfony\Component\EventDispatcher\EventSubscriberInterface;

   class RemoveUnmodifiedEntities implements EventSubscriberInterface {

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;

   /**
   * RemoveUnmodifiedEntities constructor.
   *
   * @param \Drupal\Core\Database\Connection $database
   *   The database connection.
   */
   public function __construct(Connection $database) {
    $this->database = $database;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[AcquiaContentHubPublisherEvents::PUBLISH_ENTITIES][] = ['onPublishEntities', 1000];
    return $events;
  }

  /**
   * Removes unmodified entities before publishing.
   *
   * @param \Drupal\acquia_contenthub_publisher\Event\ContentHubPublishEntitiesEvent $event
   */
  public function onPublishEntities(ContentHubPublishEntitiesEvent $event) {
    $dependencies = $event->getDependencies();
    $uuids = array_keys($dependencies);
    $query = $this->database->select('acquia_contenthub_publisher_export_tracking', 't')
      ->fields('t', ['entity_uuid', 'hash']);
    $query->condition('t.entity_uuid', $uuids, 'IN');
    $query->condition('t.status', [PublisherTracker::CONFIRMED, PublisherTracker::EXPORTED], 'IN');
    $results = $query->execute();
    foreach ($results as $result) {
      // Can't check it if it doesn't have a hash.
      // @todo make this a query.
      if (!$result->hash) {
        continue;
      }
      $wrapper = $dependencies[$result->entity_uuid];
      if ($wrapper->getHash() == $result->hash) {
        $event->removeDependency($result->entity_uuid);
      }
    }
  }

}

Personalization

ACSF-D8-settings-sample-factory-hook.php

<?php

/**
 * @file
 * Contains Environment variables.
 */
$ah_env = isset($_ENV['AH_SITE_ENVIRONMENT']) ? $_ENV['AH_SITE_ENVIRONMENT'] : NULL;
$ah_group = isset($_ENV['AH_SITE_GROUP']) ? $_ENV['AH_SITE_GROUP'] : NULL;
$is_ah_env = (bool) $ah_env;
$is_ah_prod_env = ($ah_env == 'prod' || $ah_env == '01live');
$is_ah_stage_env = ($ah_env == 'test' || $ah_env == '01test');
$is_ah_preview_env = ($ah_env == 'preview' || $ah_env == '01preview');
$is_ah_dev_cloud = (!empty($_SERVER['HTTP_HOST']) && strstr($_SERVER['HTTP_HOST'], 'devcloud'));
$is_ah_dev_env = (preg_match('/^dev[0-9]*$/', $ah_env) || $ah_env == '01dev');
$is_acsf = (!empty($ah_group) && file_exists("/mnt/files/$ah_group.$ah_env/files-private/sites.json"));
$acsf_db_name = $is_acsf ? $GLOBALS['gardens_site_settings']['conf']['acsf_db_name'] : NULL;
$is_local_env = !$is_ah_env;
$is_domain_a= (!empty($_SERVER['HTTP_HOST']) && strstr($_SERVER['HTTP_HOST'], 'domaina'));
$is_domain_b= (!empty($_SERVER['HTTP_HOST']) && strstr($_SERVER['HTTP_HOST'], 'domainb'));


/**
 * @file
 * Contains Acquia Lift and Content Hub configuration.
 */
if ($is_ah_env && $is_domain_a) {
  switch ($ah_env) {
    case '01live':
      $config['acquia_lift.settings']['credential']['account_id'] = 'LIFTACCOUNT';
      $config['acquia_lift.settings']['credential']['site_id'] = 'site_id_domain_a_prod'; //Set in Lift Profile Manager
      $config['acquia_lift.settings']['credential']['content_origin'] = '12312312312312312312312'; //Same as origin below

      // Configure these at at /admin/config/services/acquia-contenthub and run
      // "drush cget acquia_contenthub.admin_settings --include-overridden" to get all the settings.
      $config['acquia_contenthub.admin_settings']['api_key'] = '121231231231'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['secret_key'] = '123123123123123123123123123'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['client_name'] = 'site_id_domain_a_prod'; //Arbitrary but usually matches site_id
      $config['acquia_contenthub.admin_settings']['origin'] = '12312312312312312312312';
      $config['acquia_contenthub.admin_settings']['webhook'] = [
        'uuid' => 'will be generated on /admin/config/services/acquia-contenthub',
        'url' => 'create at /admin/config/services/acquia-contenthub',
        'settings_url' => 'will be generated on /admin/config/services/acquia-contenthub',
      ];
      break;
    case '01test':
      $config['acquia_lift.settings']['credential']['account_id'] = 'LIFTACCOUNT';
      $config['acquia_lift.settings']['credential']['site_id'] = 'site_id_domain_a_test'; //Set in Lift Profile Manager
      $config['acquia_lift.settings']['credential']['content_origin'] = '12312312312312312312312'; //Same as origin below
      $config['acquia_contenthub.admin_settings']['api_key'] = '121231231231'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['secret_key'] = '123123123123123123123123123'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['client_name'] = 'site_id_domain_a_test'; //Arbitrary but usually matches site_id
      $config['acquia_contenthub.admin_settings']['origin'] = '12312312312312312312312';
      $config['acquia_contenthub.admin_settings']['webhook'] = [
        'uuid' => 'will be generated on /admin/config/services/acquia-contenthub',
        'url' => 'create at /admin/config/services/acquia-contenthub',
        'settings_url' => 'will be generated on /admin/config/services/acquia-contenthub',
      ];
      break;
    case '01dev':
      $config['acquia_lift.settings']['credential']['account_id'] = 'LIFTACCOUNT';
      $config['acquia_lift.settings']['credential']['site_id'] = 'site_id_domain_a_dev'; //Set in Lift Profile Manager
      $config['acquia_lift.settings']['credential']['content_origin'] = '12312312312312312312312'; //Same as origin below
      $config['acquia_contenthub.admin_settings']['api_key'] = '121231231231'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['secret_key'] = '123123123123123123123123123'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['client_name'] = 'site_id_domain_a_dev'; //Arbitrary but usually matches site_id
      $config['acquia_contenthub.admin_settings']['origin'] = '12312312312312312312312';
      $config['acquia_contenthub.admin_settings']['webhook'] = [
        'uuid' => 'will be generated on /admin/config/services/acquia-contenthub',
        'url' => 'create at /admin/config/services/acquia-contenthub',
        'settings_url' => 'will be generated on /admin/config/services/acquia-contenthub',
      ];
      break;
  }
}

if ($is_ah_env && $is_domain_b) {
  switch ($ah_env) {
    case '01live':
      $config['acquia_lift.settings']['credential']['account_id'] = 'LIFTACCOUNT';
      $config['acquia_lift.settings']['credential']['site_id'] = 'site_id_domain_b_prod'; //Set in Lift Profile Manager
      $config['acquia_lift.settings']['credential']['content_origin'] = '12312312312312312312312'; //Same as origin below
      $config['acquia_contenthub.admin_settings']['api_key'] = '121231231231'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['secret_key'] = '123123123123123123123123123'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['client_name'] = 'site_id_domain_b_prod'; //Arbitrary but usually matches site_id
      $config['acquia_contenthub.admin_settings']['origin'] = '12312312312312312312312';
      $config['acquia_contenthub.admin_settings']['webhook'] = [
        'uuid' => 'will be generated on /admin/config/services/acquia-contenthub',
        'url' => 'create at /admin/config/services/acquia-contenthub',
        'settings_url' => 'will be generated on /admin/config/services/acquia-contenthub',
      ];
      break;
    case '01test':
      $config['acquia_lift.settings']['credential']['account_id'] = 'LIFTACCOUNT';
      $config['acquia_lift.settings']['credential']['site_id'] = 'site_id_domain_b_test'; //Set in Lift Profile Manager
      $config['acquia_lift.settings']['credential']['content_origin'] = '12312312312312312312312'; //Same as origin below
      $config['acquia_contenthub.admin_settings']['api_key'] = '121231231231'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['secret_key'] = '123123123123123123123123123'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['client_name'] = 'site_id_domain_b_test'; //Arbitrary but usually matches site_id
      $config['acquia_contenthub.admin_settings']['origin'] = '12312312312312312312312';
      $config['acquia_contenthub.admin_settings']['webhook'] = [
        'uuid' => 'will be generated on /admin/config/services/acquia-contenthub',
        'url' => 'create at /admin/config/services/acquia-contenthub',
        'settings_url' => 'will be generated on /admin/config/services/acquia-contenthub',
      ];
      break;
    case '01dev':
      $config['acquia_lift.settings']['credential']['account_id'] = 'LIFTACCOUNT';
      $config['acquia_lift.settings']['credential']['site_id'] = 'site_id_domain_b_dev'; //Set in Lift Profile Manager
      $config['acquia_lift.settings']['credential']['content_origin'] = '12312312312312312312312'; //Same as origin below
      $config['acquia_contenthub.admin_settings']['api_key'] = '121231231231'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['secret_key'] = '123123123123123123123123123'; //Get from Profile Manager Customer Details
      $config['acquia_contenthub.admin_settings']['client_name'] = 'site_id_domain_b_dev'; //Arbitrary but usually matches site_id
      $config['acquia_contenthub.admin_settings']['origin'] = '12312312312312312312312';
      $config['acquia_contenthub.admin_settings']['webhook'] = [
        'uuid' => 'will be generated on /admin/config/services/acquia-contenthub',
        'url' => 'create at /admin/config/services/acquia-contenthub',
        'settings_url' => 'will be generated on /admin/config/services/acquia-contenthub',
      ];      
      break;
  }
}




if ($is_local_env) {
  $config['acquia_lift.settings']['credential']['customer_site'] = 'local';
  $config['acquia_contenthub.admin_settings']['origin'] = 'Not connected';
}

D7-example-settings.php

<?php
if (isset($_ENV['AH_SITE_ENVIRONMENT'])) {
  switch ($_ENV['AH_SITE_ENVIRONMENT']) {
case 'prod':
    // Acquia Lift Unique Values - Create in Lift Profile Manager Admin > Manage Configuration Data > Customer Sites
    $conf['acquia_lift_site_id'] = 'domain_prod'; //Unique
    // Uncomment this if you only want Content Hub content from this environment to show in Experience Builder (value should match origin below)
    //$conf['acquia_lift_content_origin'] = '';

    // Acquia Content Hub Settings for dev - Create client_name in Content Hub module
    $conf['content_hub_connector_client_name'] = 'domain_prod'; //Unique
    $conf['content_hub_connector_origin'] = ''; //Unique
    break;

  case 'test':
    // Acquia Lift Unique Values - Create in Lift Profile Manager Admin > Manage Configuration Data > Customer Sites
    $conf['acquia_lift_site_id'] = 'domain_test'; //Unique
    // Uncomment this if you only want Content Hub content from this environment to show in Experience Builder (value should match origin below)
    //$conf['acquia_lift_content_origin'] = '';

    // Acquia Content Hub Settings for dev - Create client_name in Content Hub module
    $conf['content_hub_connector_client_name'] = 'domain_test'; //Unique
    $conf['content_hub_connector_origin'] = ''; //Unique
    break;

  case 'dev':
    // Acquia Lift Unique Values - Create in Lift Profile Manager Admin > Manage Configuration Data > Customer Sites
    $conf['acquia_lift_site_id'] = 'domain_dev'; //Unique
    // Uncomment this if you only want Content Hub content from this environment to show in Experience Builder (value should match origin below)
    //$conf['acquia_lift_content_origin'] = '';

    // Acquia Content Hub Settings for dev - Create client_name in Content Hub module
    $conf['content_hub_connector_client_name'] = 'domain_dev'; //Unique
    $conf['content_hub_connector_origin'] = ''; //Unique
  }
}

D8-example-settings.php

<?php
if (isset($_ENV['AH_SITE_ENVIRONMENT'])) {
  switch ($_ENV['AH_SITE_ENVIRONMENT']) {
case 'prod':
  // Acquia Lift Unique Values - Create in Lift Profile Manager Admin > Manage Configuration Data > Customer Sites
  $config['acquia_lift.settings']['credential']['site_id'] = 'mysite_prod';
      // Uncomment this if you only want Content Hub content from this environment to show in Experience Builder
  //$config['acquia_lift.settings']['credential']['content_origin'] = 'will be generated on /admin/config/services/acquia-contenthub';

  // Acquia Content Hub Settings for prod - Create client_name in Content Hub module
      // Run "drush cget acquia_contenthub.admin_settings --include-overridden" to get all the settings.
  $config['acquia_contenthub.admin_settings']['client_name'] = 'create at /admin/config/services/acquia-contenthub';
  $config['acquia_contenthub.admin_settings']['origin'] = 'will be generated on /admin/config/services/acquia-contenthub';
      $config['acquia_contenthub.admin_settings']['webhook'] = [
        'uuid' => 'will be generated on /admin/config/services/acquia-contenthub',
        'url' => 'create at /admin/config/services/acquia-contenthub',
        'settings_url' => 'will be generated on /admin/config/services/acquia-contenthub',
      ];
  break;

case 'test':
  // Acquia Lift Unique Values - Create in Lift Profile Manager Admin > Manage Configuration Data > Customer Sites
  $config['acquia_lift.settings']['credential']['site_id'] = 'mysite_test';
      // Uncomment this if you only want Content Hub content from this environment to show in Experience Builder
  //$config['acquia_lift.settings']['credential']['content_origin'] = 'will be generated on /admin/config/services/acquia-contenthub';

  // Acquia Content Hub Settings for test - Create client_name in Content Hub module
  $config['acquia_contenthub.admin_settings']['client_name'] = 'create at /admin/config/services/acquia-contenthub';
  $config['acquia_contenthub.admin_settings']['origin'] = 'will be generated on /admin/config/services/acquia-contenthub';
      $config['acquia_contenthub.admin_settings']['webhook'] = [
        'uuid' => 'will be generated on /admin/config/services/acquia-contenthub',
        'url' => 'create at /admin/config/services/acquia-contenthub',
        'settings_url' => 'will be generated on /admin/config/services/acquia-contenthub',
      ];
  break;

case 'dev':
  // Acquia Lift Unique Values - Create in Lift Profile Manager Admin > Manage Configuration Data > Customer Sites
  $config['acquia_lift.settings']['credential']['site_id'] = 'mysite_dev';
      // Uncomment this if you only want Content Hub content from this environment to show in Experience Builder
  //$config['acquia_lift.settings']['credential']['content_origin'] = 'will be generated on /admin/config/services/acquia-contenthub';

  // Acquia Content Hub Settings for dev - Create client_name in Content Hub module
  $config['acquia_contenthub.admin_settings']['client_name'] = 'create at /admin/config/services/acquia-contenthub';
  $config['acquia_contenthub.admin_settings']['origin'] = 'will be generated on /admin/config/services/acquia-contenthub';
      $config['acquia_contenthub.admin_settings']['webhook'] = [
        'uuid' => 'will be generated on /admin/config/services/acquia-contenthub',
        'url' => 'create at /admin/config/services/acquia-contenthub',
        'settings_url' => 'will be generated on /admin/config/services/acquia-contenthub',
      ];
  break;
  }
}

LiftWebJavaClient-HMACv1.java

package com.acquia.lift.examples;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HttpContext;

/**
 * Example Java client for talking to Lift Web API.
 * 
 * Please note this class is for illustrative purposes only
 *  - it's not thread-safe
 *  - it does not clean resources completely after itself
 *  - may not be as performant as you'd want
 */
public class LiftWebJavaClient {

    /**
     * The API URL for Lift Web.
     */
    private String apiUrl;

    /**
     * The Lift Web account name to use.
     */
    private String accountId;

    /**
     * The access key to use for authorization.
     */
    private String accessKey;

    /**
     * The secret key to use for authorization.
     */
    private String secretKey;

    /**
     * The list of headers that can be used in the canonical request.
     */
    private static final String[] HEADER_WHITE_LIST = { "Accept", "Host", "User-Agent" };

    /**
     * HMAC SHA1 algorithm constant
     */
    private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";

    /**
     * UTF8 encoding
     */
    private static final String UTF8 = "UTF-8";

    /**
     * constructor
     *
     * @param accountId The name of the Lift Web account.
     * @param apiUrl The URL to use for API calls.
     * @param accessKey The access key to use for authorization.
     * @param secretKey The secret key to use for authorization.
     */
    private LiftWebJavaClient(String accountId, String apiUrl, String accessKey, String secretKey) {
        this.accountId = accountId;
        this.apiUrl = apiUrl;
        this.accessKey = accessKey;
        this.secretKey = secretKey;
    }

    /**
     * Generates an endpoint for a particular section of the Lift Web API.
     *
     * @param path The endpoint path, e.g. 'segments' or 'events/my-event'
     * @return String The endpoint to make calls to.
     */
    protected String generateEndpoint(String path) {
        return this.apiUrl + "/dashboard/rest/" + this.accountId + "/" + path;
    }

    /**
     * Returns the canonical representation of a provided HTTP request.
     *
     * @param httpRequest request
     * @return String The canonical representation of the request.
     */
    private String canonicalizeRequest(HttpRequest httpRequest) throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append(httpRequest.getRequestLine().getMethod().toUpperCase()).append("\n");

        for (String headerName : LiftWebJavaClient.HEADER_WHITE_LIST) {
            Header header = httpRequest.getFirstHeader(headerName);
            if (header != null) {
                String lowercaseHeaderName = headerName.toLowerCase();
                String trimmedHeaderValue = header.getValue().trim();
                sb.append(lowercaseHeaderName).append(":").append(trimmedHeaderValue).append("\n");
            }
        }

        URI uri = new URI(httpRequest.getRequestLine().getUri());
        sb.append(uri.getPath());

        String query = uri.getQuery();
        if (query != null && query.trim().length() > 0) {
            List<String> parameterNameValuePairs = Arrays.<String> asList(query.split("&"));
            if (parameterNameValuePairs.size() > 0) {
                Collections.sort(parameterNameValuePairs);
                sb.append("?").append(parameterNameValuePairs.get(0));
                for (int i = 1; i < parameterNameValuePairs.size(); i++) {
                    sb.append("&").append(parameterNameValuePairs.get(i));
                }
            }
        }

        return sb.toString();
    }

    /**
     * calculates HMAC representation of the data using provided algorithm and key
     *
     *@param algorithm algorithm of choice; for us always SHA1
     *@param data to hash
     * @param key to has it with
     */
    private String hashHMAC(String algorithm, String data, String key) throws Exception {
        String result;
        SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(LiftWebJavaClient.UTF8),
            algorithm);
        Mac mac = Mac.getInstance(algorithm);
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(data.getBytes());
        result = Base64.encodeBase64String(rawHmac);
        return result;
    }

    /**
     * adds 'Authorization' header to the request
     *
     * @param httpRequest request
     */
    private void addAuthenticationCredentials(HttpRequest httpRequest) throws Exception {
        // if access key is not provided, it means REST APIs are not authenticated
        if (accessKey == null) {
            return;
        }
        String canonical = canonicalizeRequest(httpRequest);
        String hmac = hashHMAC(LiftWebJavaClient.HMAC_SHA1_ALGORITHM, canonical, this.secretKey);
        String authorizationHeader = "HMAC " + this.accessKey + ":" + hmac;

        System.out.println(authorizationHeader);
        httpRequest.addHeader("Authorization", authorizationHeader);
    }

    /**
     * when using Apache HTTP Client library, we need to correctly inject the authorization header
     * 
     * @returns an HTTP Client that can be used to submit a request
     */
    private CloseableHttpClient createHttpClient() {
        return HttpClientBuilder.create().addInterceptorLast(new HttpRequestInterceptor() {

            @Override
            public void process(HttpRequest request, HttpContext context) throws HttpException,
                    IOException {
                try {
                    addAuthenticationCredentials(request);
                } catch(Exception e) {
                    throw new IOException(e.getMessage(), e);
                }
            }
        }).build();
    }

    /**
     * converts the response body into a string
     * 
     * @param httpResponse
     * @return String representation of the HTTP response body, if possible
     */
    protected String readResponse(HttpResponse httpResponse) throws Exception {
        HttpEntity entity = httpResponse.getEntity();
        BufferedReader br = new BufferedReader(new InputStreamReader(entity.getContent(),
            LiftWebJavaClient.UTF8));
        StringBuilder body = new StringBuilder("");
        String line = null;
        while ((line = br.readLine()) != null) {
            if (body.length() > 0) {
                body.append("\n");
            }
            body.append(line);
        }
        br.close();
        return body.toString().trim();
    }

    //
    // EXAMPLES START HERE
    //

    /**
     * these are several examples in the code
     */
    public static void main(String[] args) throws Exception {

        String accountId = "your_account_id";
        String accessKey = "your_access_key"; // or null if the REST APIs are not authenticated
        String secretKey = "your_secret_key";
        String apiUrl = "your_apiUrl";

        LiftWebJavaClient client = new LiftWebJavaClient(accountId, apiUrl, accessKey, secretKey);

        // example 1 - get segments

        {
            String segmentsPath = "segments";
            String url = client.generateEndpoint(segmentsPath);
            HttpGet httpGet = new HttpGet(url);
            try (CloseableHttpClient httpClient = client.createHttpClient()) {
                HttpResponse httpResponse = httpClient.execute(httpGet);
                int statusCode = httpResponse.getStatusLine().getStatusCode();
                System.out.println(statusCode);
                if (statusCode == 200) {
                    System.out.println(client.readResponse(httpResponse));
                } else {
                    throw new Exception("status not HTTP OK"
                            + httpResponse.getStatusLine().toString());
                }
            }
        }

        // example 2 - put event
        {
            String eventName = "LiftWebRESTEEventExample";
            String eventType = "OTHER";

            String eventsPath = "events/" + eventName + "?type=" + eventType;
            String url = client.generateEndpoint(eventsPath);
            HttpPut request = new HttpPut(url);
            try (CloseableHttpClient httpClient = client.createHttpClient()) {
                HttpResponse httpResponse = httpClient.execute(request);
                int statusCode = httpResponse.getStatusLine().getStatusCode();
                System.out.println(statusCode);
                if (statusCode != 200) {

                    if (httpResponse.getStatusLine().getStatusCode() != 200) {
                        throw new Exception("status not HTTP OK"
                                + httpResponse.getStatusLine().toString());
                    }
                }
            }
        }

        // example 3 - delete event
        {
            String eventName = "LiftWebRESTEEventExample";

            String eventsPath = "events/" + eventName;
            String url = client.generateEndpoint(eventsPath);
            HttpDelete request = new HttpDelete(url);
            try (CloseableHttpClient httpClient = client.createHttpClient()) {
                HttpResponse httpResponse = httpClient.execute(request);
                int statusCode = httpResponse.getStatusLine().getStatusCode();
                System.out.println(statusCode);
                if (statusCode != 200) {

                    if (httpResponse.getStatusLine().getStatusCode() != 200) {
                        throw new Exception("status not HTTP OK"
                                + httpResponse.getStatusLine().toString());
                    }
                }
            }
        }

    }
}

LiftWebJavaClient-HMACv2.java

package com.acquia.lift.examples;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HttpContext;

import com.acquia.http.HMACHttpRequestInterceptor;
import com.acquia.http.HMACHttpResponseInterceptor;

/**
 * Example Java client for talking to Lift Web API.
 * 
 * Please note this class is for illustrative purposes only
 *  - it's not thread-safe
 *  - it does not clean resources completely after itself
 *  - may not be as performant as you'd want
 */
public class LiftWebJavaClient {

    /**
     * The API URL for Lift Web.
     */
    private String apiUrl;

    /**
     * The Lift Web account name to use.
     */
    private String accountId;

    /**
     * The access key to use for authorization.
     */
    private String accessKey;

    /**
     * The secret key to use for authorization.
     */
    private String secretKey;

    /**
     * UTF8 encoding
     */
    private static final String UTF8 = "UTF-8";

    /**
     * constructor
     *
     * @param accountId The name of the Lift Web account.
     * @param apiUrl The URL to use for API calls.
     * @param accessKey The access key to use for authorization.
     * @param secretKey The secret key to use for authorization.
     */
    private LiftWebJavaClient(String accountId, String apiUrl, String accessKey, String secretKey) {
        this.accountId = accountId;
        this.apiUrl = apiUrl;
        this.accessKey = accessKey;
        this.secretKey = secretKey;
    }

    /**
     * Generates an endpoint for a particular section of the Lift Web API.
     *
     * @param path The endpoint path, e.g. 'segments' or 'events/my-event'
     * @return String The endpoint to make calls to.
     */
    protected String generateEndpoint(String path) {
        return this.apiUrl + "/" + this.accountId + "/" + path;
    }

    /**
     * when using Apache HTTP Client library, we need to correctly inject the authorization header
     * 
     * @returns an HTTP Client that can be used to submit a request
     */
    private CloseableHttpClient createHttpClient() {
        HttpClientBuilder clientBuilder = HttpClientBuilder.create();

        HMACHttpRequestInterceptor requestInterceptor = new HMACHttpRequestInterceptor("Acquia",
            this.accessKey, this.secretKey, "SHA256") { //v2 only supports SHA256

            @Override
            public void process(HttpRequest request, HttpContext context)
                    throws HttpException, IOException {
                super.process(request, context);
            }

        };
        clientBuilder.addInterceptorLast(requestInterceptor);

        HMACHttpResponseInterceptor responseInterceptor = new HMACHttpResponseInterceptor(
            this.secretKey, "SHA256"); //v2 only supports SHA256
        clientBuilder.addInterceptorLast(responseInterceptor);

        return clientBuilder.build();
    }

    /**
     * converts the response body into a string
     * 
     * @param httpResponse
     * @return String representation of the HTTP response body, if possible
     */
    protected String readResponse(HttpResponse httpResponse) throws Exception {
        HttpEntity entity = httpResponse.getEntity();
        BufferedReader br = new BufferedReader(
            new InputStreamReader(entity.getContent(), LiftWebJavaClient.UTF8));
        StringBuilder body = new StringBuilder("");
        String line = null;
        while ((line = br.readLine()) != null) {
            if (body.length() > 0) {
                body.append("\n");
            }
            body.append(line);
        }
        br.close();
        return body.toString().trim();
    }

    //
    // EXAMPLES START HERE
    //

    /**
     * these are several examples in the code
     */
    public static void main(String[] args) throws Exception {

        String accountId = "your_account_id";
        String accessKey = "your_access_key"; // or null if the REST APIs are not authenticated
        String secretKey = "your_secret_key";
        String apiUrl = "your_apiUrl";

        LiftWebJavaClient client = new LiftWebJavaClient(accountId, apiUrl, accessKey, secretKey);

        // example 1 - get segments
        {
            String segmentsPath = "segments";
            String url = client.generateEndpoint(segmentsPath);
            HttpGet httpGet = new HttpGet(url);
            try (CloseableHttpClient httpClient = client.createHttpClient()) {
                HttpResponse httpResponse = httpClient.execute(httpGet);
                int statusCode = httpResponse.getStatusLine().getStatusCode();
                System.out.println(statusCode);
                if (statusCode == 200) {
                    System.out.println(client.readResponse(httpResponse));
                } else {
                    throw new Exception(
                        "status not HTTP OK" + httpResponse.getStatusLine().toString());
                }
            }
        }

        // example 2 - put event
        {
            String eventName = "LiftWebRESTEEventExample";
            String eventType = "OTHER";

            String eventsPath = "events/" + eventName + "?type=" + eventType;
            String url = client.generateEndpoint(eventsPath);
            HttpPut request = new HttpPut(url);
            try (CloseableHttpClient httpClient = client.createHttpClient()) {
                HttpResponse httpResponse = httpClient.execute(request);
                int statusCode = httpResponse.getStatusLine().getStatusCode();
                System.out.println(statusCode);
                if (statusCode != 200) {
                    throw new Exception(
                        "status not HTTP OK" + httpResponse.getStatusLine().toString());
                }
            }
        }

        // example 3 - delete event
        {
            String eventName = "LiftWebRESTEEventExample";

            String eventsPath = "events/" + eventName;
            String url = client.generateEndpoint(eventsPath);
            HttpDelete request = new HttpDelete(url);
            try (CloseableHttpClient httpClient = client.createHttpClient()) {
                HttpResponse httpResponse = httpClient.execute(request);
                int statusCode = httpResponse.getStatusLine().getStatusCode();
                System.out.println(statusCode);
                if (statusCode != 200) {
                    throw new Exception(
                        "status not HTTP OK" + httpResponse.getStatusLine().toString());
                }
            }
        }

    }

}

LiftWebPHPClient.php

<?php
/**
 * @file
 * Example PHP client for talking to Lift Web API.
 */

class LiftWebPHPClient {
  /**
   * An http client for making calls to Lift Web.
   */
  protected $httpClient;

  /**
   * The API URL for Lift Web.
   *
   * @var string
   */
  protected $apiUrl;

  /**
   * The Lift Web account ID to use.
   *
   * @var string
   */
  protected $accountId;

  /**
   * The access key to use for authorization.
   *
   * @var string
   */
  protected $accessKey;

  /**
   * The secret key to use for authorization.
   *
   * @var string
   */
  protected $secretKey;

  /**
   * The list of headers that can be used in the canonical request.
   *
   * @var array
   */
  protected $headerWhitelist = array(
    'Accept',
    'Host',
    'User-Agent'
  );

  /**
   * The singleton instance.
   *
   * @var ALProfilesAPI
   */
  private static $instance;

  /**
   * Singleton factory method.
   *
   * @param $account_id
   *   The ID of the Lift Web account.
   * @param $api_url
   *   The URL to use for API calls.
   * @param $access_key
   *   The access key to use for authorization.
   * @param $secret_key
   *   The secret key to use for authorization.
   *
   * @return ALProfilesAPI
   */
  public static function getInstance($account_id, $customer_site, $api_url, $access_key, $secret_key) {
    if (empty(self::$instance)) {
      self::$instance = new self($account_id, $customer_site, $api_url, $access_key, $secret_key);
    }
    return self::$instance;
  }

  /**
   * Private constructor as this is a singleton.
   *
   * @param $account_id
   *   The ID of the Lift Web account.
   * @param $api_url
   *   The URL to use for API calls.
   * @param $access_key
   *   The access key to use for authorization.
   * @param $secret_key
   *   The secret key to use for authorization.
   */
  private function __construct($account_id, $site, $api_url, $access_key, $secret_key) {
    $this->accountId = $account_id;
    $this->customerSite = $site;
    $this->apiUrl = $api_url;
    $this->accessKey = $access_key;
    $this->secretKey = $secret_key;
  }

  /**
   * Returns an http client to use for Lift Web calls.
   */
  protected function httpClient() {
    if (!isset($this->httpClient)) {
      $this->httpClient = new AcquiaLiftDrupalHttpClient();
    }
    return $this->httpClient;
  }

  /**
   * Generates an endpoint for a particular section of the Lift Web API.
   *
   * @param string $path
   *   The endpoint path, e.g. 'segments' or 'events/my-event'
   * @return string
   *   The endpoint to make calls to.
   */
  protected function generateEndpoint($path) {
    return $this->apiUrl . '/dashboard/rest/' . $this->accountId . '/' . $path;
  }

  /**
   * Returns the canonical representation of a request.
   *
   * @param $method
   *   The request method, e.g. 'GET'.
   * @param $path
   *   The path of the request, e.g. '/dashboard/rest/[ACCOUNTID]/segments'.
   * @param array $parameters
   *   An array of request parameters.
   * @param array $headers
   *   An array of request headers.
   * @param bool $add_extra_headers
   *   Whether to add the extra headers that we know drupal_http_request will add
   *   to the request. Set to FALSE if the request will not be handled by
   *   drupal_http_request.
   *
   * @return string
   *   The canonical representation of the request.
   */
  public function canonicalizeRequest($method, $url, $parameters = array(), $headers = array(), $add_extra_headers = TRUE) {
    $parsed_url = parse_url($url);
    $str = strtoupper($method) . "\n";
    // Certain headers may get added to the actual request so we need to
    // add them here.
    if ($add_extra_headers && !isset($headers['User-Agent'])) {
      $headers['User-Agent'] = 'Drupal (+http://drupal.org/)';
    }
    if ($add_extra_headers && !isset($headers['Host'])) {
      $headers['Host'] = $parsed_url['host'] . (!empty($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
    }
    // Sort all header names alphabetically.
    $header_names = array_keys($headers);
    uasort($header_names, create_function('$a, $b', 'return strtolower($a) < strtolower($b) ? -1 : 1;'));
    // Add each header (trimmed and lowercased) and value to the string, separated by
    // a colon, and with a new line after each header:value pair.
    foreach ($header_names as $header) {
      if (!in_array($header, $this->headerWhitelist)) {
        continue;
      }
      $str .= trim(strtolower($header)) . ':' . trim($headers[$header]) . "\n";
    }
    // Add the path.
    $str .= $parsed_url['path'];
    // Sort any parameters alphabetically and add them as a querystring to our string.
    if (!empty($parameters)) {
      ksort($parameters);
      $first_param = key($parameters);
      $str .= '?' . $first_param . '=' . array_shift($parameters);
      foreach ($parameters as $key => $value) {
        $str .= '&' . $key . '=' . $value;
      }
    }
    return $str;
  }

  /**
   * Returns a string to use for the 'Authorization' header.
   *
   * @return string
   */
  public function getAuthHeader($method, $path, $parameters = array(), $headers = array()) {
    $canonical = $this->canonicalizeRequest($method, $path, $parameters, $headers, is_a($this->httpClient(), 'AcquiaLiftDrupalHttpClient'));
    $binary = hash_hmac('sha1', (string) $canonical, $this->secretKey, TRUE);
    $hex = hash_hmac('sha1', (string) $canonical, $this->secretKey, FALSE);
    $hmac = base64_encode($binary);
    return 'HMAC ' . $this->accessKey . ':' . $hmac;
  }

  /**
   * Example method that makes a call to the "example" endpoint.
   */
  public function getMakeAPICall() {
    // First get our Authorization header.
    $headers = array('Accept' => 'application/json');
    $url = $this->generateEndpoint('example');
    $params = array();
    if (!empty($this->customerSite)) {
      $params['customerSite'] = $this->customerSite;
    }
    $auth_header = $this->getAuthHeader('GET', $url, $params, $headers);
    $headers += array('Authorization' => $auth_header);
    $querystring = empty($this->customerSite) ? '' : '?customerSite=' . rawurlencode($this->customerSite);
    $response = $this->httpClient()->get($url . $querystring, $headers);
    // Do something with the response.
  }

}

Site Factory

acsf-backups.php

#!/usr/bin/env php
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

// Example script for making backups of several sites through the REST API.
// Two things are left up to the script user:
// - Including Guzzle, which is used by request();
//   e.g. by doing: 'composer init; composer require guzzlehttp/guzzle'
require 'vendor/autoload.php';

// - Populating $config:
$config = [
  // URL of a subsection inside the SF REST API; must end with sites/.
  'url' => 'https://www.[CLIENT].acsitefactory.com/api/v1/sites/',
  'api_user' => '',
  'api_key' => '',

  // Site IDs of the sites to process; can also be provided as CLI argument.
  'sites' => [],

  // Number of days before backups are deleted; can also be provided on ClI.
  'backup_retention' => 30,

  // Request parameter for /api/v1#List-sites.
  'limit' => 100,

  // The components of the websites to backup.
  // Details: /api/v1#Create-a-site-backup.
  // 'codebase' is excluded from the default components since those files would
  // be the same in each site backup, and cannot be restored into the factory.
  'components' => ['database', 'public files', 'private files', 'themes'],
];

if ($argc < 2 || $argc > 4 || !in_array($argv[1], array('backup-add', 'backup-del'), TRUE)) {
  $help = <<<EOT
    Usage: php application.php parameter [sites] [backup_retention=30].
    Where:
    - parameter is one of {backup-add, backup-del}
    - [sites] is be either a comma separated list (e.g. 111,222,333) or 'all'
    - [backup_retention] the number of days for which the backups should be retained. If passed this threshold they will be deleted when using backup-del command (defaults to 30 days)

EOT;
  echo $help;
  exit(1);
}

// Lower the 'limit' parameter to the maximum which the API allows.
if ($config['limit'] > 100) {
  $config['limit'] = 100;
}

// Check if the list of sites in $config is to be overridden by the provided
// input. If the input is set to 'all' then fetch the list of sites using the
// Site Factory API, otherwise it should be a comma separated list of site IDs.
if ($argc >= 3) {
  if ($argv[2] == 'all') {
    $config['sites'] = get_all_sites($config);
  }
  else {
    // Removing spaces.
    $no_spaces = str_replace(' ', '', $argv[2]);

    // Keeping only IDs that are valid.
    $config['sites'] = array_filter(explode(',', $no_spaces), "id_check");

    // Removing duplicates.
    $config['sites'] = array_unique($config['sites']);
  }
}

// Check if the backup_retention parameter is overwritten.
if ($argc >= 4 && id_check($argv[3])) {
  $config['backup_retention'] = $argv[3];
}

// Helper; returns true if given ID is valid (numeric and > 0), false otherwise.
function id_check($id) {
  return is_numeric($id) && $id > 0;
}

// Fetches the list of all sites using the Site Factory REST API.
function get_all_sites($config) {
  // Starting from page 1.
  $page = 1;

  $sites = array();

  printf("Getting all sites - Limit / request: %d\n", $config['limit']);

  // Iterate through the paginated list until we get all sites, or
  // an error occurs.
  do {
    printf("Getting sites page: %d\n", $page);

    $method = 'GET';
    $url = $config['url'] . "?limit=" . $config['limit'] . "&page=" . $page;
    $has_another_page = FALSE;
    $res = request($url, $method, $config);

    if ($res->getStatusCode() != 200) {
      echo "Error whilst fetching site list!\n";
      exit(1);
    }

    $next_page_header = $res->getHeader('link');
    $response = json_decode($res->getBody()->getContents());

    // If the next page header is present and has a "next" link, we know we
    // have another page.
    if (!empty($next_page_header) && strpos($next_page_header[0], 'rel="next"') !== FALSE) {
      $has_another_page = TRUE;
      $page++;
    }

    foreach ($response->sites as $site) {
      $sites[] = $site->id;
    }
  } while ($has_another_page);

  return $sites;
}

// Helper function to return API user and key.
function get_request_auth($config) {
  return [
    'auth' => [$config['api_user'], $config['api_key']],
  ];
}

// Sends a request using the guzzle HTTP library; prints out any errors.
function request($url, $method, $config, $form_params = []) {
  // We are setting http_errors => FALSE so that we can handle them ourselves.
  // Otherwise, we cannot differentiate between different HTTP status codes
  // since all 40X codes will just throw a ClientError exception.
  $client = new Client(['http_errors' => FALSE]);

  $parameters = get_request_auth($config);
  if ($form_params) {
    $parameters['form_params'] = $form_params;
  }

  try {
    $res = $client->request($method, $url, $parameters);
    return $res;
  }
  catch (RequestException $e) {
    printf("Request exception!\nError message %s\n", $e->getMessage());
  }

  return NULL;
}

// Iterates through backups for a certain site and deletes them if they are
// past the backup_retention mark.
function backup_del($backups, $site_id, $config) {
  // Iterating through existing backups for current site and deleting those
  // that are X days old.
  $time = $config['backup_retention'] . ' days ago';
  foreach ($backups as $backup) {
    $timestamp = $backup->timestamp;
    if ($timestamp < strtotime($time)) {
      printf("Deleting %s with backup (ID: %d).\n", $backup->label, $backup->id);

      $method = 'DELETE';
      $url = $config['url'] . $site_id . '/backups/' . $backup->id;

      $res = request($url, $method, $config);
      if (!$res || $res->getStatusCode() != 200) {
        printf("Error! Whilst deleting backup ID %d. Please check the above messages for the full error.\n", $backup->id);
        continue;
      }
      $task = json_decode($res->getBody()->getContents())->task_id;
      printf("Deleting backup (ID: %d) with task ID %d.\n", $backup->id, $task);
    }
    else {
      printf("Keeping %s since it was created sooner than %s (ID: %d).\n", $backup->label, $time, $backup->id);
    }
  }
}

// Creates or deletes backups depending on the operation given.
function backup($operation, $config) {
  // Setting global operation endpoints and messages.
  if ($operation === 'backup-add') {
    $endpoint = '/backup';
    $message = "Creating backup for site ID %d.\n";
    $method = 'POST';
    $form_params = [
      'components' => $config['components'],
    ];
  }
  else {
    // Unlike in other code, we do not paginate through backups, but we get the
    // maximum for one request.
    $endpoint = '/backups?limit=100';
    $message = "Retrieving old backups for site ID %d.\n";
    $method = 'GET';
    $form_params = [];
  }

  // Iterating through the list of sites defined in secrets.php.
  for ($i = 0; $i < count($config['sites']); $i++) {
    // Sending API request.
    $url = $config['url'] . $config['sites'][$i] . $endpoint;
    $res = request($url, $method, $config, $form_params);

    $message_site = sprintf($message, $config['sites'][$i]);
    // If request returned an error, we show that and
    // we continue with another site.
    if (!$res) {
      // An exception was thrown.
      printf('Error whilst %s', $message_site);
      printf("Please check the above messages for the full error.\n");
      continue;
    }
    elseif ($res->getStatusCode() != 200) {
      // If a site has no backups, it will return a 404.
      if ($res->getStatusCode() == 404 && $operation == 'backup-del') {
        printf("Site ID %d has no backups.\n", $config['sites'][$i]);
      }
      else {
        printf('Error whilst %s', $message_site);
        printf("HTTP code %d\n", $res->getStatusCode());
        $body = json_decode($res->getBody()->getContents());
        printf("Error message: %s\n", $body ? $body->message : '<empty>');
      }
      continue;
    }

    // All good here.
    echo $message_site;

    // For deleting backups, we have to iterate through the backups we get.
    if ($operation == 'backup-del') {
      backup_del(json_decode($res->getBody()->getContents())->backups, $config['sites'][$i], $config);
    }
  }
}

backup($argv[1], $config);

acsfd8+.memcache.settings.php

<?php
/**
 * @file
 * Contains caching configuration.
 */
use Composer\Autoload\ClassLoader;

/**
 * Use memcache as cache backend.
 *
 * Autoload memcache classes and service container in case module is not
 * installed. Avoids the need to patch core and allows for overriding the
 * default backend when installing Drupal.
 *
 * @see https://www.drupal.org/node/2766509
 */

if (!function_exists('get_deployment_id')) {
  function get_deployment_id() {
    static $id = NULL;
    if ($id == NULL) {
      $site_settings = $GLOBALS['gardens_site_settings'];
      $deployment_id_file = "/mnt/www/site-php/{$site_settings['site']}.{$site_settings['env']}/.vcs_head_ref";
      if (is_readable($deployment_id_file)) {
        $id = file_get_contents($deployment_id_file);
        if ($id === FALSE) {
          $id = NULL;
        }
      }
      else {
        $id = NULL;
      }
    }
    return $id;
  }
}

if (getenv('AH_SITE_ENVIRONMENT') &&
  array_key_exists('memcache', $settings) &&
  array_key_exists('servers', $settings['memcache']) &&
  !empty($settings['memcache']['servers'])
) {

// Check for PHP Memcached libraries.
$memcache_exists = class_exists('Memcache', FALSE);
$memcached_exists = class_exists('Memcached', FALSE);
$memcache_services_yml = DRUPAL_ROOT . '/modules/contrib/memcache/memcache.services.yml';
$memcache_module_is_present = file_exists($memcache_services_yml);
if ($memcache_module_is_present && ($memcache_exists || $memcached_exists)) {
  // Use Memcached extension if available.
  if ($memcached_exists) {
    $settings['memcache']['extension'] = 'Memcached';
  }
  if (class_exists(ClassLoader::class)) {
    $class_loader = new ClassLoader();
    $class_loader->addPsr4('Drupal\\memcache\\', DRUPAL_ROOT . '/modules/contrib/memcache/src');
    $class_loader->register();
    $settings['container_yamls'][] = $memcache_services_yml;

    // Acquia Default Settings for the memcache module
    // Default settings for the Memcache module.
    // Enable compression for PHP 7.
    $settings['memcache']['options'][Memcached::OPT_COMPRESSION] = TRUE;

    // Set key_prefix to avoid drush cr flushing all bins on multisite.
    $settings['memcache']['key_prefix'] = sprintf('%s%s_', $conf['acquia_hosting_site_info']['db']['name'], get_deployment_id());

    // Decrease latency.
    $settings['memcache']['options'][Memcached::OPT_TCP_NODELAY] = TRUE;

    // Bootstrap cache.container with memcache rather than database.
    $settings['bootstrap_container_definition'] = [
      'parameters' => [],
      'services' => [
        'database' => [
          'class' => 'Drupal\Core\Database\Connection',
          'factory' => 'Drupal\Core\Database\Database::getConnection',
          'arguments' => ['default'],
        ],
        'settings' => [
          'class' => 'Drupal\Core\Site\Settings',
          'factory' => 'Drupal\Core\Site\Settings::getInstance',
        ],
        'memcache.settings' => [
          'class' => 'Drupal\memcache\MemcacheSettings',
          'arguments' => ['@settings'],
        ],
        'memcache.factory' => [
          'class' => 'Drupal\memcache\Driver\MemcacheDriverFactory',
          'arguments' => ['@memcache.settings'],
        ],
        'memcache.timestamp.invalidator.bin' => [
          'class' => 'Drupal\memcache\Invalidator\MemcacheTimestampInvalidator',
          'arguments' => ['@memcache.factory', 'memcache_bin_timestamps', 0.001],
        ],
        'memcache.backend.cache.container' => [
          'class' => 'Drupal\memcache\DrupalMemcacheInterface',
          'factory' => ['@memcache.factory', 'get'],
          'arguments' => ['container'],
        ],
        'cache_tags_provider.container' => [
          'class' => 'Drupal\Core\Cache\DatabaseCacheTagsChecksum',
          'arguments' => ['@database'],
        ],
        'cache.container' => [
          'class' => 'Drupal\memcache\MemcacheBackend',
          'arguments' => [
            'container',
            '@memcache.backend.cache.container',
            '@cache_tags_provider.container',
            '@memcache.timestamp.invalidator.bin',
            '@memcache.settings',
          ],
        ],
      ],
    ];

    // Content Hub 2.x requires the Depcalc module which needs to use the database backend.
    $settings['cache']['bins']['depcalc'] = 'cache.backend.database';

    // Use memcache for bootstrap, discovery, config instead of fast chained
    // backend to properly invalidate caches on multiple webs.
    // See https://www.drupal.org/node/2754947
    $settings['cache']['bins']['bootstrap'] = 'cache.backend.memcache';
    $settings['cache']['bins']['discovery'] = 'cache.backend.memcache';
    $settings['cache']['bins']['config'] = 'cache.backend.memcache';

    // Use memcache as the default bin.
    $settings['cache']['default'] = 'cache.backend.memcache';
  }
}
}

api-dbupdate.txt

#!/bin/sh
## Initiate a code and database update from Site Factory
## Origin: http://docs.acquia.com/site-factory/extend/api/examples

# This script should primarily be used on non-production environments.

# Mandatory parameters:
# env : environment to run update on. Example: dev, pprod, qa2, test.
#       - the api user must exist on this environment.
#       - for security reasons, update of prod environment is *not*
#         supported and must be performed manually through UI
# branch : branch/tag to update. Example: qa-build
# update_type : code or code,db

source $(dirname "$0")/includes/global-api-settings.inc.sh

env="$1"
branch="$2"
update_type="$3"

# add comma to "code,db" if not already entered
if [ "$update_type" == "code,db" ]
then
update_type="code, db"
fi

# Edit the following line, replacing [domain] with the appropriate
# part of your domain name.

curl "https://www.${env}-[domain].acsitefactory.com/api/v1/update" \
-v -u ${user}:${api_key} -k -X POST \
-H 'Content-Type: application/json' \
-d "{\"sites_ref\": \"${branch}\", \"sites_type\": \"${update_type}\"}"

acsf-cache-lifetime.php

<?php

/**
 * @file
 *
 * This post-settings-php hook is created to conditionally set the cache
 * lifetime of Drupal to be a value that is greater than 300 (5 minutes).
 * It also does not let you set it to be lower than 5 minutes.
 *
 * This does not fire on Drush requests, as it interferes with site creation.
 * It also means that drush will report back incorrect values for the 
 * cache lifetime, so using a real browser is the easiest way to validate
 * what the current settings are.
 *
 * How to enable this for a site:
 *  - drush vset acsf_allow_override_page_cache 1
 *  - drush vset page_cache_maximum_age 3600
 */

if (!drupal_is_cli()) {
  $result = db_query("SELECT value FROM {variable} WHERE name = 'acsf_allow_override_page_cache';")->fetchField();
  if ($result) {
    $acsf_allow_override_page_cache = unserialize($result);
    if ($acsf_allow_override_page_cache) {
      $result = db_query("SELECT value FROM {variable} WHERE name = 'page_cache_maximum_age';")->fetchField();
      // An empty array indicates no value was set in the database, so we ignore
      // the site.
      if ($result) {
        $page_cache_maximum_age = (int) unserialize($result);
        if ($page_cache_maximum_age > 300) {
          $conf['page_cache_maximum_age'] = $page_cache_maximum_age;
        }
      }
    }
  }
}

acsf-hook-tx-isolation.php

<?php

/**
 * @file
 * Example implementation of ACSF post-settings-php hook.
 *
 * @see https://docs.acquia.com/site-factory/extend/hooks
 */

// Changing the database transaction isolation level from `REPEATABLE-READ`
// to `READ-COMMITTED` to avoid/minimize the deadlocks.
// @see https://support-acquia.force.com/s/article/360005253954-Fixing-database-deadlocks
// for reference.

$databases['default']['default']['init_commands'] = [
  'isolation' => "SET SESSION tx_isolation='READ-COMMITTED'",
];
if (file_exists('/var/www/site-php')) {
  acquia_hosting_db_choose_active($conf['acquia_hosting_site_info']['db'], 'default', $databases, $conf);
}