Resources

Useful references

Downloadable PDFs

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);
}