diff --git a/README.md b/README.md index fc43d34..c6260ca 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,16 @@ $mailer->textTemplate->textTemplateFunction(); ``` +## Sending mail without mailserver using SMTP + +> This method is for testing only. Most Mailservers will +> reject mail transferred with this method. + +``` + +``` + + ## Demos - [Basic/simple template sending mail](docs/simple-demo.php) @@ -92,3 +102,14 @@ print_r ($phpmail); $phpmail->Send(); ``` +## Intercepting outgoing mail + +```php +$mailer->setSendMailFunction(function (PHPMailer $mail, PhoreMailer $phoreMailer) { + $res["to"] = $mail->getAllRecipientAddresses(); + $res["subject"] = $mail->Subject; + $res["html"] = $mail->Body; + $res["text"] = $mail->AltBody; +}); +``` + diff --git a/kickstart.sh b/kickstart.sh index 945eda0..1a55550 100755 --- a/kickstart.sh +++ b/kickstart.sh @@ -1,5 +1,4 @@ -#!/usr/bin/env bash -PROGPATH="$( cd "$(dirname "$0")" ; pwd -P )" # The absolute path to kickstart.sh +#!/bin/bash # # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # DO NOT EDIT THIS FILE! CHANGES WILL BE OVERWRITTEN ON UPDATE @@ -9,9 +8,8 @@ PROGPATH="$( cd "$(dirname "$0")" ; pwd -P )" # The absolute path to kickstart # environment for this project. # # Config-File: .kick.yml -# Website..: https://infracamp.org/getting-started/ -# Copyright: Matthias Leuffen -# Released under GNU General Public License +# Website....: https://infracamp.org/getting-started/ +# Author.....: Matthias Leuffen # ################################################################################################ ### DON'T CHANGE ANY VARIABLES HERE --- see ~/.kickstartconfig or ./.kickstartconfig instead ### @@ -26,30 +24,12 @@ KICKSTART_DOCKER_RUN_OPTS="" # External Port bindings KICKSTART_PORTS="80:80/tcp;4000:4000/tcp;4100:4100/tcp;4200:4200/tcp;4000:4000/udp" -# 1 = Don't try to download the images fr/home/matthes/Projects/infracampom the internet +# 1 = Don't try to download the images from the internet OFFLINE_MODE=0 # Specify the container name by yourself (switch of auto-detection) CONTAINER_NAME= -# The image (e.g. infracamp/kickstart-flavor-base:testing) specified in .kick.yml from:-section -FROM_IMAGE= - -# The Host IP Address. Leave empty to autodetect. -KICKSTART_HOST_IP= - -# Where to mount the current project folder (default: /opt) -DOCKER_MOUNT_PARAMS="-v $PROGPATH/:/opt/" - -# User to run inside the container (Default: 'user') -KICKSTART_USER="user" - - -# For WINDOWS (WSL) users only: Change this for mapping from wsl to docker4win. Execute this in linux shell: -# `echo "KICKSTART_WIN_PATH=C:/" >> ~/.kickstartconfig` -KICKSTART_WIN_PATH="" - - ############################ ### CODE BELOW ### ############################ @@ -59,8 +39,29 @@ KICKSTART_WIN_PATH="" set -o errtrace trap 'on_error $LINENO' ERR; PROGNAME=$(basename $0) +PROGPATH="$( cd "$(dirname "$0")" ; pwd -P )" # The absolute path to kickstart.sh + +function on_error () { + echo "Error: ${PROGNAME} on line $1" 1>&2 + exit 1 +} + +KICKSTART_HOST_IP=$(hostname -i | awk '{print $1;}') +if [ "$KICKSTART_HOST_IP" == "" ] +then + # Workaround for systems not supporting hostname -i (MAC) + # See doc/workaround-plattforms.md for more about this + KICKSTART_HOST_IP=$(host `hostname`|awk '{print $NF}') +fi; + + + + + + +CONTAINER_NAME=${PWD##*/} if test -t 1; then # see if it supports colors... ncolors=$(tput colors) @@ -86,39 +87,6 @@ if test -t 1; then fi; - -function on_error () { - local exit_code=$? - local prog=$BASH_COMMAND - - echo -e "\e[1;101;30m\n" 1>&2 - echo -en "KICKSTART ERROR: '$prog' (Exit code: $exit_code on ${PROGNAME} line $1) - inspect output above for more information.\n" 1>&2 - echo -e "\e[0m" 1>&2 - - exit 1 -} - - -if [[ "$KICKSTART_HOST_IP" == "" ]] -then - # Autodetect for ubuntu, arch - KICKSTART_HOST_IP=$(ip route list | grep -v default | grep -v linkdown | grep src | tail -1 | awk 'match($0, / [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/){print substr($0, RSTART+1, RLENGTH-1)}' 2> /dev/null) -fi; -if [[ "$KICKSTART_HOST_IP" == "" ]] -then - # Workaround for systems not supporting hostname -i (MAC) - # See doc/workaround-plattforms.md for more about this - KICKSTART_HOST_IP=$(ping -c 1 $(hostname) | grep icmp_seq | awk 'match($0,/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/){print substr($0, RSTART, RLENGTH)}') -fi; - - -if [[ "$CONTAINER_NAME" == "" ]] -then - CONTAINER_NAME=${PWD##*/} -fi; - - - KICKSTART_CACHE_DIR="$HOME/.kick_cache" mkdir -p $KICKSTART_CACHE_DIR @@ -148,26 +116,7 @@ _KICKSTART_CURRENT_VERSION="1.2.0" ## # This variables can be overwritten by ~/.kickstartconfig # - - - -ask_user() { - echo ""; - read -r -p "$1 (y|N)" choice - case "$choice" in - n|N) - echo "Abort!"; - ;; - y|Y) - return 0; - ;; - - *) - echo 'Response not valid';; - esac - exit 1; -} - +KICKSTART_WIN_PATH="" if [ ! -f "$PROGPATH/.kick.yml" ] then @@ -175,18 +124,8 @@ then ask_user "Do you want to create a new .kick.yml-file?" echo "# Kickstart container config file - see https://gitub.com/infracamp/kickstart" > $PROGPATH/.kick.yml echo "# Run ./kickstart.sh to start a development-container for this project" >> $PROGPATH/.kick.yml - echo "version: 1" >> $PROGPATH/.kick.yml echo 'from: "infracamp/kickstart-flavor-base"' >> $PROGPATH/.kick.yml - echo "command:" >> $PROGPATH/.kick.yml - echo " build:" >> $PROGPATH/.kick.yml - echo " - \"echo 'I am executed on build time'\"" >> $PROGPATH/.kick.yml - echo " init:" >> $PROGPATH/.kick.yml - echo " test:" >> $PROGPATH/.kick.yml - echo " run:" >> $PROGPATH/.kick.yml - echo " dev:" >> $PROGPATH/.kick.yml - echo " - \"echo 'I am executed in dev mode'\"" >> $PROGPATH/.kick.yml - echo "File created. See $_KICKSTART_DOC_URL for more information"; echo "" echo "You can now run ./kickstart.sh to start the container" @@ -213,6 +152,7 @@ fi if [ -e "$PROGPATH/.kickstartconfig" ] then echo "Loading $PROGPATH/.kickstartconfig (This is risky if you - abort if unsure)" + sleep 1 # @todo Search for .kickstartconfig in gitignore to verify the user wants this. . $PROGPATH/.kickstartconfig fi @@ -241,12 +181,6 @@ _usage() { $0 skel upgrade Upgrade to the latest kickstart version - $0 secrets list - List all secrets stored for this project - - $0 secrets edit [secret_name] - Edit / create secret - $0 wakeup Try to start a previous image with same container name (faster startup) @@ -257,15 +191,12 @@ _usage() { $0 :debug Execute the container in debug-mode (don't execute kick-commands) ARGUMENTS - -h Show this help - -t, --tag= Run container with this tag (development) - -u, --unflavored Run the container whithout running any scripts (develpment) - --offline Do not pull images nor ask for version upgrades - --no-tty Disable interactive tty - -e, --env ENV=value Set environment variables - -v, --volume list Bind mount a volume - -f, --force Restart / kill running containers - -r, --reset Shutdown all services and restart stack services + -h Show this help + -t --tag= Run container with this tag (development) + -u --unflavored Run the container whithout running any scripts (develpment) + --offline Do not pull images nor ask for version upgrades + --no-tty Disable interactive tty + " exit 1 } @@ -315,61 +246,27 @@ run_shell() { if [ `docker ps | grep "$CONTAINER_NAME\$" | wc -l` -gt 0 ] then echo "[kickstart.sh] Container '$CONTAINER_NAME' already running" + echo "Starting shell... (please press enter)" + echo ""; - choice="s" - - if [ "$forceKillContainer" -eq "1" ] + shellarg="/bin/bash" + if [ "$ARGUMENT" != "" ] then - choice="r" - else - if [[ "$ARGUMENT" == "" ]] - then - read -r -p "Your choice: (S)hell, (r)estart, (a)bort?" choice - fi + shellarg="kick $ARGUMENT" fi; - case "$choice" in - a) - echo "Abort"; - exit 0; - ;; - - r|R) - echo "Restarting container..." - docker kill $CONTAINER_NAME - run_container - exit 0; - ;; - s|S|*) - echo "Starting shell... (please press enter)" - echo ""; - - shellarg="/bin/bash" - if [ "$ARGUMENT" != "" ] - then - shellarg="kick $ARGUMENT" - fi; - echo -e $COLOR_NC; - docker exec $terminal --user $KICKSTART_USER -e "DEV_TTYID=[SUB]" $CONTAINER_NAME $shellarg - - echo -e $COLOR_CYAN; - echo "<=== [kickstart.sh] Leaving container." - echo -e $COLOR_NC - exit 0; - ;; - esac - + docker exec $terminal --user user -e "DEV_TTYID=[SUB]" $CONTAINER_NAME $shellarg + echo -e $COLOR_CYAN; + echo "<=== [kickstart.sh] Leaving container." + echo -e $COLOR_NC + exit 0; fi echo "[kickstart.s] Another container is already running!" docker ps echo "" - choice="k" - if [ "$forceKillContainer" -eq "0" ] - then - read -r -p "Your choice: (i)gnore/run anyway, (s)hell, (k)ill, (a)bort?:" choice - fi; + read -r -p "Your choice: (i)gnore/run anyway, (s)hell, (k)ill, (a)bort?:" choice case "$choice" in i|I) run_container @@ -378,7 +275,8 @@ run_shell() { s|S) echo "===> [kickstart.sh] Opening new shell: " echo -e $COLOR_NC - docker exec $terminal --user $KICKSTART_USER -e "DEV_TTYID=[SUB]" `docker ps | grep "/kickstart/" | cut -d" " -f1` /bin/bash + + docker exec $terminal --user user -e "DEV_TTYID=[SUB]" `docker ps | grep "/kickstart/" | cut -d" " -f1` /bin/bash echo -e $COLOR_CYAN; echo "<=== [kickstart.sh] Leaving container." @@ -387,7 +285,7 @@ run_shell() { ;; k|K) echo "Killing running kickstart containers..." - docker kill `docker ps | grep "/kickstart/" | cut -d " " -f1` + docker kill `docker ps | grep "/kickstart/" | cut -d" " -f1` return 0; ;; @@ -400,29 +298,42 @@ run_shell() { } +ask_user() { + echo ""; + read -r -p "$1 (y|N)" choice + case "$choice" in + n|N) + echo "Abort!"; + ;; + y|Y) + return 0; + ;; + + *) + echo 'Response not valid';; + esac + exit 1; +} _ci_build() { echo "CI_BUILD: Building container.. (CI_* Env is preset by gitlab-ci-runner)"; - [[ "$CI_REGISTRY" == "" ]] && echo "[Error deploy]: Environment CI_REGISTRY not set" && exit 1; - [[ "$CI_BUILD_NAME" == "" ]] && echo "CI_BUILD_NAME not set - setting default tag to 'latest'" && CI_BUILD_NAME="latest"; - local imageName="$CI_REGISTRY_IMAGE:$CI_BUILD_NAME" + BUILD_TAG=":$CI_BUILD_NAME" + if [ "$CI_REGISTRY" == "" ] + then + echo "[Error deploy]: Environment CI_REGISTRY not set" + exit 1 + fi - CMD="docker build --pull -t $imageName -f ./Dockerfile ." + CMD="docker build --pull -t $CI_REGISTRY_IMAGE$BUILD_TAG -f ./Dockerfile ." echo "[Building] Running '$CMD' (MODE1)"; eval $CMD - if [ "$CI_REGISTRY_PASSWORD" != "" ] - then - echo "Logging in to: $CI_REGISTRY_USER @ $CI_REGISTRY" - echo "$CI_REGISTRY_PASSWORD" | docker login --username $CI_REGISTRY_USER --password-stdin $CI_REGISTRY - else - echo "No registry credentials provided in env CI_REGISTRY_PASSWORD - skipping docker login." - fi; - - docker push $imageName - echo "Push successful (Image: $imageName)..." + echo "Logging in to: $CI_REGISTRY_USER @ $CI_REGISTRY" + echo "$CI_REGISTRY_PASSWORD" | docker login --username $CI_REGISTRY_USER --password-stdin $CI_REGISTRY + docker push $CI_REGISTRY_IMAGE$BUILD_TAG + echo "Push successful..." exit } @@ -441,23 +352,11 @@ run_container() { echo -e $COLOR_RED "OFFLINE MODE! Not pulling image from registy. " $COLOR_NC fi; - ## Mutliarch support - ##imageArchitecture=$( docker image inspect "$FROM_IMAGE" -f '{{.Architecture}}') - isArmImage=$(echo "$FROM_IMAGE" | grep "arm32v7") || true - isX86=$(uname -m | grep "x86") || true - - if [ "$isX86" != "" ] && [ "$isArmImage" != '' ] - then - ask_user "You are trying to load arm32 image on x86 architecture. Enable multiarch/qemu?" - docker run --rm --privileged multiarch/qemu-user-static:register --reset --credential yes - fi - if [ "$KICKSTART_WIN_PATH" != "" ] then # For Windows users: Rewrite Path of bash to Windows path # Will work only on drive C:/ PROGPATH="${PROGPATH/\/mnt\/c\//$KICKSTART_WIN_PATH}" - DOCKER_MOUNT_PARAMS="-v $PROGPATH/:/opt/" fi docker rm $CONTAINER_NAME || true @@ -468,21 +367,15 @@ run_container() { if [ -e "$_STACKFILE" ]; then _STACK_NETWORK_NAME=$CONTAINER_NAME - if [ $resetServices -eq 1 ] - then - echo "Reset Services. Leaving swarm..." - docker swarm leave --force - fi; - echo "Startin in stack mode... (network: '$_STACK_NETWORK_NAME')" _NETWORKS=$(docker network ls | grep $_STACK_NETWORK_NAME | wc -l) echo nets: $_NETWORKS if [ $_NETWORKS -eq 0 ]; then - docker swarm init --advertise-addr $KICKSTART_HOST_IP || true + docker swarm init docker network create --attachable -d overlay $_STACK_NETWORK_NAME fi; - docker stack deploy --prune --with-registry-auth -c $_STACKFILE $CONTAINER_NAME + docker stack deploy --prune -c $_STACKFILE $CONTAINER_NAME DOCKER_OPT_PARAMS="$DOCKER_OPT_PARAMS --network $_STACK_NETWORK_NAME" fi; @@ -491,20 +384,11 @@ run_container() { if [ ! -t 1 ] then # Switch to non-interactive terminal (ci-build etc) - # For Gitlab Actions: $UID unset (use uid of path) - dev_uid=$(stat -c '%u' $PROGPATH) - fi; - - if [ "$dev_uid" -eq "0" ] - then - # Never run a container as root user - # For Gitlab-CI: Gitlab-CI checks out everything world writable but as user root (0) => Set UID to normal user - # (otherwise composer / npm won't install) dev_uid=1000 fi; cmd="docker $KICKSTART_DOCKER_OPTS run $terminal \ - $DOCKER_MOUNT_PARAMS \ + -v \"$PROGPATH/:/opt/\" \ -e \"DEV_CONTAINER_NAME=$CONTAINER_NAME\" \ -e \"DEV_TTYID=[MAIN]\" \ -e \"DEV_UID=$dev_uid\" \ @@ -532,31 +416,16 @@ run_container() { -forceKillContainer=0 + ARGUMENT=""; # Parse the command parameters ARGUMENT=""; -resetServices=0; while [ "$#" -gt 0 ]; do case "$1" in -t) USE_PIPF_VERSION="-t $2"; shift 2;; --tag=*) USE_PIPF_VERSION="-t ${1#*=}"; shift 1;; - -e|--env) DOCKER_OPT_PARAMS="$DOCKER_OPT_PARAMS -e '$2'"; shift 2;; - - -v|--volume) DOCKER_OPT_PARAMS="$DOCKER_OPT_PARAMS -v '$2'"; shift 2;; - - -f|--force) - forceKillContainer=1; - shift 1; - ;; - - -r|--reset) - resetServices=1; - shift 1; - ;; - --offline) OFFLINE_MODE=1; shift 1;; @@ -608,24 +477,6 @@ while [ "$#" -gt 0 ]; do fi exit 0;; - secrets) - secretDir="$HOME/.kickstart/secrets/$CONTAINER_NAME" - mkdir -p $secretDir - - [[ "$2" == "list" ]] && echo "Listing secrets from $secretDir:" && ls $secretDir && exit 0; - - [[ "$2" != "edit" ]] && echo -e "Error: No secret specified\nUsage: $0 secrets list|edit []" && exit 1; - - [[ "$3" == "" ]] && echo -e "Error: No secret specified\nUsage: $0 secrets list|edit []" && exit 1; - - secretFile=$secretDir/$3 - - editor $secretFile - echo "Edit successful: $secretFile" - - exit 0;; - - ci-build|--ci-build) _ci_build $2 $3 exit0;; @@ -661,11 +512,8 @@ fi if [ -e "$HOME/.bash_history" ] then - bashHistoryFile="$HOME/.kickstart/bash_history/$CONTAINER_NAME"; - echo "Mounting containers bash-history from $bashHistroyFile..." - mkdir -p $(dirname $bashHistoryFile) - touch $bashHistoryFile - DOCKER_OPT_PARAMS="$DOCKER_OPT_PARAMS -v $bashHistoryFile:/home/user/.bash_history"; + echo "Mounting $HOME/.bash_history..." + DOCKER_OPT_PARAMS="$DOCKER_OPT_PARAMS -v $HOME/.bash_history:/home/user/.bash_history"; fi if [ -e "$PROGPATH/.env" ] @@ -674,18 +522,6 @@ then DOCKER_OPT_PARAMS="$DOCKER_OPT_PARAMS --env-file $PROGPATH/.env"; fi -secretsPath="$HOME/.kickstart/secrets/$CONTAINER_NAME" -echo "Scanning for secrets in $secretsPath"; -if [ -e $secretsPath ] -then - for _cur_secret_name in $(find $secretsPath -type f -printf "%f\n") - do - echo "Adding secret from $secretsPath/$_cur_secret_name -> /run/secrets/$_cur_secret_name" - DOCKER_OPT_PARAMS="$DOCKER_OPT_PARAMS -v '$secretsPath/$_cur_secret_name:/run/secrets/$_cur_secret_name' " - done; -fi; - - # Ports to be exposed IFS=';' read -r -a _ports <<< "$KICKSTART_PORTS" for _port in "${_ports[@]}" diff --git a/src/PhoreMailer.php b/src/PhoreMailer.php index 5baaddc..aedf25b 100644 --- a/src/PhoreMailer.php +++ b/src/PhoreMailer.php @@ -33,6 +33,12 @@ class PhoreMailer public $curMeta = []; + /** + * @var null|PHPMailer + */ + private $sendMailFunction = null; + + protected $smtpDirectConnectHeloHostname = null; public function __construct(PHPMailer $phpmailer=null, TextTemplate $textTemplate=null) @@ -65,8 +71,9 @@ public function setRelay(string $relay) $this->phpmailer->Port = isset ($url["port"]) ? $url["port"] : 25; $this->phpmailer->isSMTP(); } - - + + + /** * It this is set and no SMTP relay host is * set, it will load the MX Record of the @@ -81,6 +88,28 @@ public function setSmtpDirectConnect(string $heloHotname) } + /** + * Set a alternative function to send the mail (instead of + * using the default PHPMailer send function + * + * + * $mailer->setSendMailFunction(function (PHPMailer $mail, PhoreMailer $phoreMailer) { + * $res["to"] = $mail->getAllRecipientAddresses(); + * $res["subject"] = $mail->Subject; + * $res["html"] = $mail->Body; + * $res["text"] = $mail->AltBody; + * }); + * + * + * + * @param callable $fn + */ + public function setSendMailFunction(callable $fn) + { + $this->sendMailFunction = $fn; + } + + /** * Set PhpMailers config variables by array * @@ -143,6 +172,7 @@ private function _registerTemplateFunctions () } + public function prepare(string $template, array $data = []) : PHPMailer { $this->curMail = clone $this->phpmailer; @@ -163,8 +193,10 @@ public function prepare(string $template, array $data = []) : PHPMailer $addr = array_keys($this->curMail->getAllRecipientAddresses()); if (count ($addr) !== 1) throw new \InvalidArgumentException("Cannot send to more than one recipient using direct-smtp-connect mode"); + $hostname = explode('@', $addr[0]); $hostname = array_pop($hostname); + if (getmxrr($hostname, $mxRR)) { $this->curMail->Host = $mxRR[0]; @@ -180,9 +212,23 @@ public function prepare(string $template, array $data = []) : PHPMailer } + /** + * Parse the Template and send the mail directly + * + * @param string $template + * @param array $data + * @return bool + * @throws \PHPMailer\PHPMailer\Exception + */ public function send(string $template, array $data = []) { + if ($this->sendMailFunction !== null) { + $mailer = $this->prepare($template, $data); + ($this->sendMailFunction)($mailer, $this); + return true; + } $this->prepare($template, $data)->send(); + return true; } diff --git a/tests/intercepting-sendmail.phpt b/tests/intercepting-sendmail.phpt new file mode 100644 index 0000000..d89c9c1 --- /dev/null +++ b/tests/intercepting-sendmail.phpt @@ -0,0 +1,55 @@ + +

Hello {= name}

+

This is a Html Mail

+ +{/html} + +Hello {=name}, + +This is the alternative text E-Mail part. +EOT; + + +$res = []; +$mailer = new PhoreMailer(); +$mailer->setSendMailFunction(function (PHPMailer $mail) use (&$res) { + $res["to"] = $mail->getAllRecipientAddresses(); + $res["subject"] = $mail->Subject; + $res["html"] = $mail->Body; + $res["text"] = $mail->AltBody; +}); + +$mailer->send($template, [ + "name" => "John Doe", + "recipient_email" => "58c9d9dbca-ce7762@inbox.mailtrap.io", + "recipient_name" => "Some Name" +]); + + +print_r ($res); + +Assert::equal(true, true); \ No newline at end of file diff --git a/tests/send-standalone-mail.php b/tests/send-standalone-mail.php new file mode 100644 index 0000000..d2d476b --- /dev/null +++ b/tests/send-standalone-mail.php @@ -0,0 +1,34 @@ + +

Hello

+

This is a Html Mail

+ +{/html} + +This is the alternative text E-Mail part. + +EOT; + + + + +$mailer = new \Phore\Mail\PhoreMailer(); +$mailer->setSmtpDirectConnect("infracamp.org"); + +$mailer->send($template);