#!/usr/bin/env bash

# bin/compile <build-dir> <cache-dir> <env-dir>

# fail hard
set -o pipefail
# fail harder
set -eu
# move hidden files too, just in case
shopt -s dotglob

STACK=${STACK:-cedar-14} # Anvil has none
build_dir=$1
cache_dir=$2/php
mkdir -p "$cache_dir"
env_dir=${3:-} # Anvil has none
bp_dir=$(cd $(dirname $0); cd ..; pwd)

export BPLOG_PREFIX="buildpack.php"
export BUILDPACK_LOG_FILE=${BUILDPACK_LOG_FILE:-/dev/null}

# convenience functions
source $bp_dir/bin/util/common.sh

# use err_trap from common.sh on error
# we do not 'set -o errtrace', because that would cause subshell failures to fire the trap twice, e.g. with someval=$(func_that_fails)
trap 'err_trap' ERR

# stdlib
# download to a file first, as the function restarts the entire download on code 18 connection reset (not all servers support -C)
curl_retry_on_18 --fail --silent --location -o $bp_dir/bin/util/stdlib.sh https://lang-common.s3.amazonaws.com/buildpack-stdlib/v8/stdlib.sh || {
	error <<-EOF
		Failed to download standard library for bootstrapping!
		
		This is most likely a temporary internal error. If the problem
		persists, make sure that you are not running a custom or forked
		version of the Heroku PHP buildpack which may need updating.
	EOF
}
source $bp_dir/bin/util/stdlib.sh
rm $bp_dir/bin/util/stdlib.sh

# failure counting
source $bp_dir/bin/util/failures.sh

# for extensions that need special treatment
source $bp_dir/bin/util/newrelic.sh
source $bp_dir/bin/util/blackfire.sh

# if this is set it prevents Git clones (e.g. for Composer installs from source) during the build in some circumstances, and it is set in SSH Git deploys to Heroku
unset GIT_DIR

cd $build_dir

export_env_dir "$env_dir" '^COMPOSER$'
if [[ -n ${COMPOSER:-} ]]; then
	status "Using '$COMPOSER' (from "'$COMPOSER env var) for installation.'
else
	export COMPOSER="composer.json"
fi
export COMPOSER_LOCK=$(basename "$COMPOSER" ".json")".lock" # replace .json with .lock if it exists, append .lock otherwise

# we're using this error message in two places
composer_lock_parse_error=$(
	cat <<-EOF
		Failed to parse '$COMPOSER_LOCK'!

		There was an error parsing '$COMPOSER_LOCK'; it must be a valid
		file generated by Composer and be in an up-to-date state.

		Look above for any parse errors and address them if necessary.

		You most likely created or edited the file by hand, or a merge
		conflict was not resolved properly, resulting in a syntax error
		in the file. Refer to the docs for information on re-generating
		the lock file: https://getcomposer.org/doc/01-basic-usage.md

		Please perform the following steps locally on your computer to
		resolve this issue before attempting another deploy:
		1) Run 'composer update' to re-generate the lock file
		2) stage the lock file changes using 'git add $COMPOSER_LOCK'
		3) commit the change using 'git commit'

		You can run 'composer validate' locally on your computer for
		further diagnosis. Remember to also always keep your lock file
		up to date with any changes according to the instructions at
		https://getcomposer.org/doc/01-basic-usage.md

		Please remember to always keep your '$COMPOSER_LOCK' updated in
		lockstep with '$COMPOSER' to avoid common problems related
		to dependencies during collaboration and deployment.
	EOF
)

# a bunch of sanity checks first
if [[ -s "$COMPOSER" ]]; then
	cat "$COMPOSER" | python -mjson.tool &> /dev/null || {
		mcount "failures.composer_json.lint"
		error <<-EOF
			Basic validation for '$COMPOSER' failed!
			
			It must be a valid JSON document compatible with Composer.
			
			You most likely created or edited the file by hand, and it now
			contains a syntax error. Please refer to the documentation at
			https://getcomposer.org/doc/ for information on the format.
			
			You can run 'composer validate' locally on your computer for
			further diagnosis. Remember to also always keep your lock file
			up to date with any changes according to the instructions at
			https://getcomposer.org/doc/01-basic-usage.md
		EOF
	}
	if [[ ! -f "$COMPOSER_LOCK" ]]; then
		cat "$COMPOSER" | python -c 'import sys, json; sys.exit(bool(json.load(sys.stdin).get("require", {})))' 2> /dev/null || {
			mcount "failures.composer_lock.missing"
			error <<-EOF
				No '$COMPOSER_LOCK' found!
				
				A '$COMPOSER_LOCK' file was not found in your project, but there
				is a '$COMPOSER' file with dependencies inside 'require'.
				
				The lock file is required in order to guarantee reliable and
				reproducible installation of dependencies across platforms and
				deploys. You must follow the Composer best practice of having
				your lock file under version control in order to deploy. The
				lock file must not be in your '.gitignore'.
				
				Please perform the following steps locally on your computer to
				resolve this issue before attempting another deploy:
				1) remove '$COMPOSER_LOCK' from file '.gitignore', if present
				2) if no '$COMPOSER_LOCK' exists, run 'composer update'
				3) stage the lock file changes using 'git add $COMPOSER_LOCK'
				4) if you edited '.gitignore', also run 'git add .gitignore'
				5) commit the change using 'git commit'
				
				Please remember to always keep your '$COMPOSER_LOCK' updated in
				lockstep with '$COMPOSER' to avoid common problems related
				to dependencies during collaboration and deployment.
				
				Please refer to the Composer documentation for further details:
				https://getcomposer.org/doc/
				https://getcomposer.org/doc/01-basic-usage.md
			EOF
		}
	else
		cat "$COMPOSER_LOCK" | python -mjson.tool &> /dev/null || {
			mcount "failures.composer_lock.lint"
			error "$composer_lock_parse_error"
		}
	fi
else
	if [[ ! -f "$COMPOSER" ]]; then
		mcount "warnings.composer_json.missing"
		warning <<-EOF
			No '$COMPOSER' found!
			
			Your project only contains an 'index.php', no '$COMPOSER'.
			
			Using 'index.php' to declare app type as PHP is deprecated and
			may lead to unexpected behavior.
			
			Please consider updating your codebase to utilize Composer and
			modern dependency management in order to benefit from the latest
			PHP runtimes and improved application performance, as well as
			control over the PHP versions and extensions available.
			
			For an introduction to dependency management with Composer and
			how to get the most out of PHP on Heroku, refer to the docs at
			https://getcomposer.org/doc/00-intro.md and
			https://devcenter.heroku.com/articles/getting-started-with-php
		EOF
	else
		mcount "warnings.composer_json.empty"
		notice <<-EOF
			Your '$COMPOSER' is completely empty!
			
			A completely empty file is not a valid JSON document.
			
			Heroku automatically corrected this problem, but it is strongly
			recommended you change the contents to at least '{}'.
			
			For documentation on Composer and dependency management, check
			out the introduction at https://getcomposer.org/doc/00-intro.md
		EOF
	fi
	echo "{}" > $COMPOSER
fi

# PHP expects to be installed in /app/.heroku/php because of compiled paths, let's set that up!
mkdir -p /app/.heroku
# all system packages live in there
mkdir -p $build_dir/.heroku/php
# set up Composer
export COMPOSER_HOME=$cache_dir/.composer
mkdir -p $COMPOSER_HOME

# if the build dir is not "/app", we symlink in the .heroku/php subdir (and only that, to avoid problems with other buildpacks) so that PHP correctly finds its INI files etc
[[ $build_dir == '/app' ]] || ln -s $build_dir/.heroku/php /app/.heroku/php

status "Bootstrapping..."

s3_url="https://lang-php.s3.amazonaws.com/dist-${STACK}-stable/"
# prepend the default repo to the list configured by the user
# list of repositories to use is in ascening order of precedence
export_env_dir "$env_dir" '^HEROKU_PHP_PLATFORM_REPOSITORIES$'
HEROKU_PHP_PLATFORM_REPOSITORIES="${s3_url} ${HEROKU_PHP_PLATFORM_REPOSITORIES:-}"
if [[ "${HEROKU_PHP_PLATFORM_REPOSITORIES}" == *" - "* ]]; then
	# a single "-" in the user supplied string removes everything to the left of it; can be used to delete the default repo
	notice_inline "Default platform repository disabled."
	HEROKU_PHP_PLATFORM_REPOSITORIES=${HEROKU_PHP_PLATFORM_REPOSITORIES#*" - "}
	s3_url=$(echo "$HEROKU_PHP_PLATFORM_REPOSITORIES" | cut -f1 -d" " | sed 's/[^/]*$//')
	notice_inline "Bootstrapping using ${s3_url}..."
fi


# minimal PHP needed for installs, and make "composer" invocations use that for now
mkdir -p $build_dir/.heroku/php-min
ln -s $build_dir/.heroku/php-min /app/.heroku/php-min

curl_retry_on_18 --fail --silent --location -o $build_dir/.heroku/php-min.tar.gz "${s3_url}php-min-7.3.14.tar.gz" || {
	mcount "failures.bootstrap.download.php-min"
	error <<-EOF
		Failed to download minimal PHP for bootstrapping!
		
		This is most likely a temporary internal error. If the problem
		persists, make sure that you are not running a custom or forked
		version of the Heroku PHP buildpack which may need updating.
	EOF
}
tar xzf $build_dir/.heroku/php-min.tar.gz -C $build_dir/.heroku/php-min
rm $build_dir/.heroku/php-min.tar.gz

curl_retry_on_18 --fail --silent --location -o $build_dir/.heroku/composer.tar.gz "${s3_url}composer-1.9.2.tar.gz" || {
	mcount "failures.bootstrap.download.composer"
	error <<-EOF
		Failed to download Composer for bootstrapping!
		
		This is most likely a temporary internal error. If this problem
		persists, make sure that you are not running a custom or forked
		version of the Heroku PHP buildpack which may need updating.
	EOF
}
tar xzf $build_dir/.heroku/composer.tar.gz -C $build_dir/.heroku/php
rm $build_dir/.heroku/composer.tar.gz

# this alias is just for now while we install platform packages
composer() {
	/app/.heroku/php-min/bin/php /app/.heroku/php/bin/composer "$@"
}
export -f composer

# we use --no-plugins just in case the vendor dir is there, see e.g. https://github.com/Ocramius/PackageVersions/issues/64
composer_vendordir=$(composer config --no-plugins vendor-dir)
composer_bindir=$(composer config --no-plugins bin-dir)

# packages that get installed will add to this file, it's both for us and for buildpacks that follow
# composer bin-dir goes last to avoid any conflicts
echo "export PATH=/app/.heroku/php/bin:\$PATH:/app/$composer_bindir" > $bp_dir/export
# make sure Composer and binaries for it are on the path at runtime
# composer bin-dir goes last to avoid any conflicts
mkdir -p $build_dir/.profile.d
echo "export PATH=\$HOME/.heroku/php/bin:\$PATH:\$HOME/$composer_bindir" > $build_dir/.profile.d/100-composer.sh

# we perform this check early so people with stale lock files are reminded why if their lock file errors in the next step
composer_lock_outdated=false
composer validate --no-plugins --no-check-publish --no-check-all --quiet "$COMPOSER" 2>/dev/null || {
	mcount "warnings.composer_lock.outdated"
	composer_lock_outdated=true
	warning <<-EOF
		Your '$COMPOSER_LOCK' is out of date!
		
		The '$COMPOSER_LOCK' file in your project is not up to date with
		the main '$COMPOSER' file. This may result in installation
		of incorrect packages or package versions.
		
		The lock file is required in order to guarantee reliable and
		reproducible installation of dependencies across systems and
		deploys. It must always be kept in sync with '$COMPOSER'.
		
		Whenever you change '$COMPOSER', ensure that you perform
		the following steps locally on your computer:
		1) run 'composer update'
		2) add all changes using 'git add $COMPOSER $COMPOSER_LOCK'
		3) commit using 'git commit'
		
		Ensure that you updated the lock file correctly, and that you
		ran 'git add' on both files, before deploying again.
		
		Please remember to always keep your '$COMPOSER_LOCK' updated in
		lockstep with '$COMPOSER' to avoid common problems related
		to dependencies during collaboration and deployment.
		
		Please refer to the Composer documentation for further details:
		https://getcomposer.org/doc/
		https://getcomposer.org/doc/01-basic-usage.md
	EOF
}

# if prefer-stable is false and minimum-stability is not stable, warn about potential unstable platform installs
[[ ! -f "$COMPOSER_LOCK" ]] || minimum_stability=$(cat "$COMPOSER_LOCK" | python -c 'import sys, json; l = json.load(sys.stdin); print(l.get("minimum-stability")); sys.exit(l.get("minimum-stability", "stable") != "stable" and l.get("prefer-stable", False) == False);' 2> /dev/null) || {
	possible_stabilities="dev, alpha, beta, or RC"
	case $minimum_stability in
		alpha)
			possible_stabilities="alpha, beta, or RC"
			;;
		beta)
			possible_stabilities="beta or RC"
			;;
		[rR][cC])
			possible_stabilities="release candidate"
			;;
	esac
	mcount "warnings.composer_lock.minimum_stability"
	warning <<-EOF
		Non-stable 'minimum-stability'!
	
		Your '$COMPOSER' contains a 'minimum-stability' setting of
		'$minimum_stability', and the 'prefer-stable' setting is not enabled.
		
		This combination of options may negatively impact the stability
		of your app and result in crashes as it permits installation of
		$possible_stabilities versions of PHP runtimes and extensions.
		
		If possible, you should always use explicit stability flags on
		only those dependencies that you want unstable versions of, and
		leave 'minimum-stability' at its default 'stable' setting.
		
		If you really need a global 'minimum-stability' setting lower
		than 'stable', it is strongly recommended that you enable the
		'prefer-stable' setting in '$COMPOSER'.
		
		For more information, refer to the following documentation:
		https://getcomposer.org/doc/articles/versions.md
		https://getcomposer.org/doc/04-schema.md#package-links
		https://getcomposer.org/doc/04-schema.md#minimum-stability
		https://getcomposer.org/doc/04-schema.md#prefer-stable
	EOF
}

status "Installing platform packages..."

if [[ $STACK == "cedar-14" || $STACK == "heroku-16" ]]; then
	HEROKU_PHP_DEFAULT_RUNTIME_VERSION="^5.5.17"
else
	HEROKU_PHP_DEFAULT_RUNTIME_VERSION="^7.0.0"
fi
export HEROKU_PHP_DEFAULT_RUNTIME_VERSION
# extract requirements from composer.lock
/app/.heroku/php-min/bin/php $bp_dir/bin/util/platform.php "$bp_dir/support/installer/" $HEROKU_PHP_PLATFORM_REPOSITORIES 2>&1 >$build_dir/.heroku/php/composer.json | indent || {
	code=$?
	if [[ $code -eq 3 ]]; then
		mcount "failures.platform.composer_lock.runtime_only_in_dev"
		error <<-EOF
			Runtime specified in 'require-dev' but not in 'require'!
			
			Your '$COMPOSER' contains a 'require-dev' section which
			specifies a PHP runtime version (either directly, or through
			a dependency), but no such requirement is present in 'require'
			or in any of the packages listed in 'require'.
			
			Even if dev requirements are not being installed, the entirety
			of all dependencies needs to resolve to an installable set.
			Heroku cannot select a default runtime version in this case.
			
			Please perform the following steps locally on your computer to
			resolve this issue before attempting another deploy:
			1) add a dependency for 'php' to 'require' in '$COMPOSER'
			2) run 'composer update' to re-generate the lock file
			3) stage changes using 'git add $COMPOSER $COMPOSER_LOCK'
			4) commit changes using 'git commit'
			
			For more information on selecting PHP runtimes, please refer to
			https://devcenter.heroku.com/articles/php-support
		EOF
	else
		mcount "failures.platform.composer_lock.parse"
		error "$composer_lock_parse_error"
	fi
}

# reset COMPOSER for the platform install step
COMPOSER_bak="$COMPOSER"
export COMPOSER=composer.json

# pass export_file_path and profile_dir_path to composer install; they will be used by the installer plugin
# they are also used in later install attempts for add-on extensions (blackfire, newrelic, ...)
export export_file_path=$bp_dir/export
export profile_dir_path=$build_dir/.profile.d
# grep and sed in a subshell with || true, or a failing grep match will cause the exit code to be 1 if "composer install" fails with code 2
if composer install -d "$build_dir/.heroku/php" ${HEROKU_PHP_INSTALL_DEV-"--no-dev"} 2>&1 | tee $build_dir/.heroku/php/install.log | ( grep --line-buffered -E '^  - (Instal|Enab)ling heroku-sys/' | sed -u -E -e 's/^  - (Instal|Enab)ling /- /' -e 's/heroku-sys\///g' || true ) | indent; then
	:
else
	code=$?
	install_log=$(
		cat $build_dir/.heroku/php/install.log | sed -E \
			-e 's/heroku-sys\///g' \
			-e 's/^Loading composer repositories with package information/Loading repositories with available runtimes and extensions/' \
			-e 's/^Installing dependencies.*//' \
			-e '/^Potential causes:/,$d' \
			-e "\#satisfiable by ${COMPOSER}/${COMPOSER_LOCK}#d" \
			-e "s#(${COMPOSER}/${COMPOSER_LOCK}) dev-[0-9a-f]+#\1#" \
			-e 's/^/> /'
	)
	if [[ $code -eq 2 ]]; then
		# dependency solving failed
		mcount "failures.platform.solving.$(detect_platform_solving_failures <<< "$install_log")"
	else
		# something else
		mcount "failures.platform.install.$(detect_platform_install_failures <<< "$install_log")"
	fi
	
	error <<-EOF
		Failed to install system packages!
		
		Your platform requirements (for runtimes and extensions) could
		not be resolved to an installable set of dependencies, or a
		platform package repository was unreachable.
		
		This usually means that you (or packages you are using) depend
		on a combination of PHP versions and/or extensions that are
		currently not available on Heroku.
		
		The following is the full output from the installation attempt:
		
		$install_log
		$(
			$composer_lock_outdated && cat <<-EOF2
				$(echo -e "\033[1;33m")
				A possible cause for this error is your '$COMPOSER_LOCK' file,
				which is currently out of date, as changes have been made to
				your '$COMPOSER' that are not yet reflected in the lock file.
		
				In order to guarantee reliable installation of system packages,
				Heroku uses information from '$COMPOSER_LOCK'.
		
				If you already attempted to fix the error above by updating the
				requirements in '$COMPOSER', remember to also update the
				lock file by running 'composer update', followed by a 'git add'
				and 'git commit' of the changes. The warning message further
				above contains step-by-step instructions.
		
				Please remember to always keep your '$COMPOSER_LOCK' updated in
				lockstep with '$COMPOSER' to avoid common problems related
				to dependencies during collaboration and deployment.
				$(echo -e "\033[1;31m")
			EOF2
		)
		For reference, the following runtimes are currently available:
		
		PHP:  $(composer show -d "$build_dir/.heroku/php" --available heroku-sys/php 2>&1 | sed -n 's/^versions : //p' | fold -s -w 58 || true)
		HHVM: $(composer show -d "$build_dir/.heroku/php" --available heroku-sys/hhvm 2>&1 | sed -n 's/^versions : //p' | fold -s -w 58 || true)
		
		Please verify that all requirements for runtime versions in
		'$COMPOSER_LOCK' are compatible with the list above, and ensure
		all required extensions are available for the desired runtimes.
		
		When choosing a PHP runtimes and extensions, please also ensure
		they are available on your app's stack ($STACK), and select
		a different stack if needed after consulting the article below.
		$(
			[[ "$STACK" == "heroku-18" ]] && cat <<-EOF2
				$(echo -e "\033[1;33m")
				Be advised that the heroku-18 stack you're currently using only
				supports PHP version 7.1 and later.
				$(echo -e "\033[1;31m")
			EOF2
		)
		For a list of supported runtimes & extensions on Heroku, please
		refer to: https://devcenter.heroku.com/articles/php-support
	EOF
fi

if composer show -d "$build_dir/.heroku/php" --installed --quiet heroku-sys/php 2>/dev/null; then
	engine="php"
	engine_r="php -r"
elif composer show -d "$build_dir/.heroku/php" --installed --quiet heroku-sys/hhvm 2>/dev/null; then
	engine="hhvm"
	engine_r="hhvm --php -r"
fi

# log number of installed platform packages
mmeasure "platform.count" $(composer show -d "$build_dir/.heroku/php" --installed "heroku-sys/*" 2> /dev/null | wc -l)

# done with platform installs; restore COMPOSER from previous value
export COMPOSER="$COMPOSER_bak"
unset COMPOSER_bak

# clean up
rm -rf /app/.heroku/php-min $build_dir/.heroku/php-min
unset -f composer

# export our "do not auto.start APM extensions" magic INI directory to PHP_INI_SCAN_DIR for ourself and for later buildpacks (but not for runtime)
export_env_dir "$env_dir" '^PHP_INI_SCAN_DIR$'
echo "export PHP_INI_SCAN_DIR=\${PHP_INI_SCAN_DIR-}:$bp_dir/conf/php/apm-nostart-overrides/" >> $bp_dir/export

# earlier we wrote at least one $PATH entry that we'll need now (and just above we added a PHP_INI_SCAN_DIR export), and installed packages will likely have added to it too
source $bp_dir/export

composer() {
	$engine $(which composer) "$@"
}
export -f composer

# log runtime version
mcount "platform.packages.php.$($engine_r "echo ${engine^^}_VERSION;")"

if eoldate=$($engine $bp_dir/bin/util/eol.php); then
	:
else
	code=$?
	eolmsg=$(
		cat <<-EOF
			It is strongly recommended you update your app to a version of
			PHP with "active support" status immediately to ensure you get
			the latest bugfixes and security updates each time you deploy.
			
			You may check the list of versions supported by the PHP Group
			and their EOL dates here: http://php.net/supported-versions.php
			
			For a list of supported runtimes & extensions on Heroku, please
			refer to: https://devcenter.heroku.com/articles/php-support
		EOF
	)
	if (( $code == 2 )); then
		mcount "warnings.runtime_eol.eol_reached"
		warning <<-EOF
			Your selected PHP version has reached end-of-life
			
			No updates or security fixes have been provided for your PHP
			version series by the PHP Group since $eoldate.
			
			$eolmsg
		EOF
	elif (( $code == 3 )); then
		mcount "warnings.runtime_eol.eol_close"
		warning <<-EOF
			Your selected PHP version is close to end-of-life
			
			No updates or security fixes will be provided for your PHP
			version series by the PHP Group as of $eoldate.
			
			$eolmsg
		EOF
	elif (( $code == 4 )); then
		mcount "warnings.runtime_eol.eom_reached"
		warning <<-EOF
			Your app's PHP version is no longer actively maintained
			
			Only security updates will be provided for your PHP version
			series by the PHP Group until its end-of-life on $eoldate.
			
			$eolmsg
		EOF
	elif (( $code == 5 )); then
		mcount "warnings.runtime_eol.eom_close"
		warning <<-EOF
			Your app's PHP version is close to end of maintenance
			
			Only security updates will be provided for your PHP version
			series by the PHP Group as of $eoldate.
			
			$eolmsg
		EOF
	fi
fi

status "Installing dependencies..."

# echo composer version for info purposes
# tail to get rid of outdated version warnings (Composer sends those to STDOUT instead of STDERR)
composer --no-plugins --version 2> /dev/null | tail -n 1 | indent

# throw a notice if people have added their vendor dir to Git; that's bad practice and makes everything slow and cluttered
if [[ -f "$composer_vendordir/autoload.php" && -d "$composer_vendordir/composer" ]]; then
	# we should not do this check separately; there is no reliable way of telling whether or not it really is the real Composer bin dir or if it comes from somewhere else
	mcount "warnings.vendor_dir"
	warning <<-EOF
		Composer vendor dir found in project!
		
		Your Git repository contains Composer's '$composer_vendordir' directory.
		
		This directory should not be under version control; only your
		'$COMPOSER' and '$COMPOSER_LOCK' files need to be added, as
		Composer will handle installation of dependencies on deploy.
		
		To suppress this notice, first remove the folder from the index
		by running 'git rm -r --cached $composer_vendordir/'.
		Next, edit your project's '.gitignore' file and add the folder
		'/$composer_vendordir/' to the list, then commit the changes.
		$(
			[[ ! "$composer_bindir/" == "$composer_vendordir"/* && -d "$composer_bindir" ]] && cat <<-EOF2
			
				Your Composer bin dir is configured to reside outside of vendor
				dir, so please repeat the two steps above for '$composer_bindir/'.
			
			EOF2
		)
		For more info, refer to the Composer FAQ: http://bit.ly/1rlCSZU
	EOF
fi

# handle custom oauth keys
export_env_dir "$env_dir" '^COMPOSER_GITHUB_OAUTH_TOKEN$'
COMPOSER_GITHUB_OAUTH_TOKEN=${COMPOSER_GITHUB_OAUTH_TOKEN:-}
if [[ -n "$COMPOSER_GITHUB_OAUTH_TOKEN" ]]; then
	if curl --fail --silent -H "Authorization: token $COMPOSER_GITHUB_OAUTH_TOKEN" https://api.github.com/rate_limit > /dev/null; then
		composer config --no-plugins -g github-oauth.github.com "$COMPOSER_GITHUB_OAUTH_TOKEN" &> /dev/null # redirect outdated version warnings (Composer sends those to STDOUT instead of STDERR)
		notice_inline 'Using $COMPOSER_GITHUB_OAUTH_TOKEN for GitHub OAuth.'
	else
		mcount "failures.dependencies.auth.COMPOSER_GITHUB_OAUTH_TOKEN"
		error <<-EOF
			Invalid token for GitHub OAuth!
			
			The OAuth token set in the '\$COMPOSER_GITHUB_OAUTH_TOKEN'
			config var is invalid. Please ensure that you obtain a valid
			token from https://github.com/settings/tokens and set it on the
			app using 'heroku config:set COMPOSER_GITHUB_OAUTH_TOKEN=...'.
		EOF
	fi
else
	# don't forget to remove any stored key if it's gone from the env
	composer config --no-plugins -g --unset github-oauth.github.com &> /dev/null # redirect outdated version warnings (Composer sends those to STDOUT instead of STDERR)
fi
# no need for the token to stay around in the env
unset COMPOSER_GITHUB_OAUTH_TOKEN

# install dependencies unless composer.json is completely empty (in which case it'd talk to packagist.org which may be slow and is unnecessary)
export_env_dir "$env_dir" '^[A-Z_][A-Z0-9_]*$' '^(HOME|PATH|GIT_DIR|CPATH|CPPATH|LD_PRELOAD|LIBRARY_PATH|LD_LIBRARY_PATH|STACK|REQUEST_ID|IFS|HEROKU_PHP_INSTALL_DEV|BPLOG_PREFIX|BUILDPACK_LOG_FILE|PHP_INI_SCAN_DIR)$'
if cat "$COMPOSER" | python -c 'import sys,json; sys.exit(not json.load(sys.stdin));'; then
	install_log=$(mktemp -t heroku-buildpack-php-composer-install-log-XXXX)
	composer install ${HEROKU_PHP_INSTALL_DEV-"--no-dev"} --prefer-dist --optimize-autoloader --no-interaction 2>&1 | tee "$install_log" | indent || {
		code=$?
		if [[ $code -eq 2 ]]; then
			# dependency solving failed, which should only ever happen if people have platform overrides in their config
			mcount "failures.dependencies.solving.$(detect_dependencies_solving_failures < "$install_log")"
		else
			mcount "failures.dependencies.install.$(detect_dependencies_install_failures < "$install_log")"
		fi
		# FIXME: can we provide more advice depending on the error?
		error <<-EOF
			Dependency installation failed!
			
			The 'composer install' process failed with an error. The cause
			may be the download or installation of packages, or a pre- or
			post-install hook (e.g. a 'post-install-cmd' item in 'scripts')
			in your '$COMPOSER'.
			
			Typical error cases are out-of-date or missing parts of code,
			timeouts when making external connections, or memory limits.
			
			Check the above error output closely to determine the cause of
			the problem, ensure the code you're pushing is functioning
			properly, and that all local changes are committed correctly.
			
			For more information on builds for PHP on Heroku, refer to
			https://devcenter.heroku.com/articles/php-support
		EOF
	}
fi

# only perform the check for buildpack package if we're not running in Heroku CI
if [[ -z "${HEROKU_PHP_INSTALL_DEV+are-we-running-in-ci}" ]]; then
	composer show --installed heroku/heroku-buildpack-php &> /dev/null && {
		mcount "failures.dependencies.buildpack_as_dependency"
		error <<-EOF
			Package 'heroku/heroku-buildpack-php' found!
			
			Your '$COMPOSER' requires 'heroku/heroku-buildpack-php' in
			the 'require' section. This package may only be used as a
			dependency in 'require-dev', because Heroku installs the latest
			version of the buildpack during builds.
			
			Please perform the following steps locally on your computer to
			resolve this issue before attempting another deploy:
			1) move the requirement for 'heroku/heroku-buildpack-php' from
			   section 'require' to 'require-dev' in '$COMPOSER'
			2) run 'composer update' to re-generate the lock file
			3) stage changes using 'git add $COMPOSER $COMPOSER_LOCK'
			4) commit changes using 'git commit'
		EOF
	}
fi

# log number of installed dependencies
mmeasure "dependencies.count" $(composer show --installed 2> /dev/null | wc -l)

if cat "$COMPOSER" | python -c 'import sys,json; sys.exit("compile" not in json.load(sys.stdin).get("scripts", {}));'; then
	status "Running 'composer compile'..."
	composer run-script ${HEROKU_PHP_INSTALL_DEV-"--no-dev"} --no-interaction compile 2>&1 | indent || {
		mcount "failures.compile_step"
		error <<-EOF
			Compile step failed!
			
			Installation of dependencies was successful, but the custom
			script you're using to perform actions after 'composer install'
			failed with the error above.
			
			Check that the 'compile' command(s) in the 'scripts' section of
			your '$COMPOSER' are working properly and not running into
			timeouts or memory limits.
			
			For more information on the 'composer compile' step, refer to
			https://devcenter.heroku.com/articles/php-support
		EOF
	}
fi

status "Preparing runtime environment..."

# install this buildpack like a composer package
# it will contain the apache/nginx/php configs and the boot script
# TODO: warn if require-dev has the package using a different branch
shopt -u dotglob # we don't want .git, .gitignore et al
# figure out the package dir name to write to and copy to it
hbpdir="$composer_vendordir/$(cat $bp_dir/composer.json | python -c 'import sys, json; print(json.load(sys.stdin)["name"])')"
mkdir -p "$build_dir/$hbpdir"
cp -r "$bp_dir"/* "$build_dir/$hbpdir/"
# make bin dir, just in case
mkdir -p "$build_dir/$composer_bindir"
# figure out shortest relative path from vendor/heroku/heroku-buildpack-php to vendor/bin (or whatever the bin dir is)
relbin=$(python -c "import os.path; print(os.path.relpath('$hbpdir', '$composer_bindir'))")
# collect bin names from composer.json
relbins=$(cat $bp_dir/composer.json | python -c 'from __future__ import print_function; import sys, json; { print(sys.argv[1]+"/"+bin) for bin in json.load(sys.stdin)["bin"] }' $relbin)
# link to bins
cd $build_dir/$composer_bindir
ln -fs $relbins .
cd $build_dir

if [[ ! -f "Procfile" ]]; then
	echo "web: heroku-$engine-apache2" > Procfile
	notice_inline "No Procfile, using 'web: heroku-$engine-apache2'."
fi

# write empty WEB_CONCURRENCY.sh to overwrite the defaults logic from a prior buildpack, e.g. Node (all buildpacks use the same filename to allow this)
> $build_dir/.profile.d/WEB_CONCURRENCY.sh

# reset COMPOSER for the platform install step
COMPOSER_bak="$COMPOSER"
export COMPOSER=composer.json

# unless we're running a CI build...
if [[ "${HEROKU_PHP_INSTALL_DEV+CI}" != "CI" ]]; then
	status "Checking for additional extensions to install..."
	# special treatment for Blackfire; we enable it if we detect a server id and a server token for it
	install_blackfire_ext
	# special treatment for New Relic; we enable it if we detect a license key for it
	install_newrelic_ext
	install_newrelic_userini
fi

# done with platform installs; restore COMPOSER from previous value
export COMPOSER="$COMPOSER_bak"
unset COMPOSER_bak
