Resources

Useful references

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

Acquia Help

Filter by product:

Common questions

If you can't find what you're looking for, try refining your search or contact our Support teamfor further assistance.