diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..b79488d351 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.idea +fake_mail_output +mailpoet +mailpoet-premium +wordpress diff --git a/.gitignore b/.gitignore index 858a8e7344..51dfa611da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ .DS_Store +.env .idea .vscode +docker-compose.override.yml +fake_mail_output node_modules npm-debug.log mailpoet-premium diff --git a/do b/do new file mode 100755 index 0000000000..70178e12a8 --- /dev/null +++ b/do @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +function syntax { + cat << EOF + ./do setup Setup the dev environment. + ./do start Start the docker containers (docker-compose up -d). + ./do stop Stop the docker containers (docker-compose stop). + ./do ssh [--test] Run an interactive bash shell inside the plugin directory. + ./do run [--test] Run a custom bash command in the wordpress container. + ./do acceptance [--premium] Run acceptance tests. + ./do build [--premium] Builds a .zip for the plugin. + ./do templates Generates templates classes and assets. + ./do [--test] [--premium] Run './do ' inside the plugin directory. + + Options: + --test Run the command using the 'test_wordpress' service. + --premium Run the command inside the premium plugin. +EOF +} + +function ssh_and_run { + params=("$@") + params=("${params[@]:1}") + docker-compose exec $1 bash -c "${params[@]}" +} + +if [ "$1" = "" -o "$1" = "--help" ]; then + syntax + +elif [ "$1" = "setup" ]; then + ./initial-setup.sh + +elif [ "$1" = "start" ]; then + docker-compose up -d + +elif [ "$1" = "stop" ]; then + docker-compose stop + +elif [ "$1" = "run" ]; then + params=("$@") + params=("${params[@]:1}") + if [ "$2" = "--test" ]; then + params=("${params[@]:1}") + ssh_and_run test_wordpress "${params[@]}" + else + ssh_and_run wordpress "${params[@]}" + fi + +elif [ "$1" = "ssh" ]; then + if [ "$2" = "--premium" ] || [ "$3" = "--premium" ]; then + dir=/var/www/html/wp-content/plugins/mailpoet-premium + else + dir=/var/www/html/wp-content/plugins/mailpoet + fi + + if [ "$2" = "--test" ] || [ "$3" = "--test" ]; then + docker-compose exec --workdir $dir test_wordpress bash + else + docker-compose exec --workdir $dir wordpress bash + fi + +elif [ "$1" = "acceptance" ]; then + if [ "$2" = "--premium" ]; then + cd mailpoet-premium + else + cd mailpoet + fi + COMPOSE_HTTP_TIMEOUT=200 docker-compose run codeception -e KEEP_DEPS=1 --steps --debug -vvv + cd .. + +elif [ "$1" = "build" ]; then + if [ "$2" = "--premium" ]; then + ssh_and_run wordpress "cd wp-content/plugins/mailpoet-premium && ./build.sh" + else + ssh_and_run wordpress "cd wp-content/plugins/mailpoet && ./build.sh" + fi + +elif [ "$1" = "templates" ]; then + ssh_and_run wordpress "cd ../templates && php generate.php" + +else + docker_service="wordpress" + plugin_directory="mailpoet" + params=("$@") + if [ "$1" = "--test" -o "$2" = "--test" ]; then + docker_service="test_wordpress" + params=("${params[@]:1}") + fi + if [ "$1" = "--premium" -o "$2" = "--premium" ]; then + plugin_directory="mailpoet-premium" + params=("${params[@]:1}") + fi + ssh_and_run $docker_service "cd wp-content/plugins/$plugin_directory && ./do ${params[@]}" +fi diff --git a/docker-compose.override.macos-sample.yml b/docker-compose.override.macos-sample.yml new file mode 100644 index 0000000000..00ebb3a697 --- /dev/null +++ b/docker-compose.override.macos-sample.yml @@ -0,0 +1,33 @@ +version: '3.7' + +services: + wordpress: + volumes: + - nfs-wordpress:/var/www/html + - nfs-mailpoet:/var/www/html/wp-content/plugins/mailpoet + - nfs-mailpoet-premium:/var/www/html/wp-content/plugins/mailpoet-premium + + test_wordpress: + volumes: + - nfs-mailpoet:/var/www/html/wp-content/plugins/mailpoet + - nfs-mailpoet-premium:/var/www/html/wp-content/plugins/mailpoet-premium + +volumes: + nfs-wordpress: + driver: local + driver_opts: + type: nfs + o: addr=host.docker.internal,nolock + device: ":/System/Volumes/Data${PWD}/wordpress" + nfs-mailpoet: + driver: local + driver_opts: + type: nfs + o: addr=host.docker.internal,nolock + device: ":/System/Volumes/Data${PWD}/mailpoet" + nfs-mailpoet-premium: + driver: local + driver_opts: + type: nfs + o: addr=host.docker.internal,nolock + device: ":/System/Volumes/Data${PWD}/mailpoet-premium" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..4414321e4e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,102 @@ +version: '3.7' + +services: + dashboard: + container_name: mp-dashboard + image: nginx:1.21-alpine + ports: + - 8888:80 + volumes: + - ./docker/dashboard:/usr/share/nginx/html:ro + + db: + container_name: mp-db + image: mysql:5.7 + volumes: + - my-datavolume:/var/lib/mysql + - ./docker/create_test_db.sh:/docker-entrypoint-initdb.d/10-create_test_db.sh + environment: + MYSQL_ROOT_PASSWORD: somewordpress + MYSQL_DATABASE: wordpress + MYSQL_TEST_DATABASE: test_wordpress + MYSQL_USER: wordpress + MYSQL_PASSWORD: wordpress + + wordpress: + container_name: mp-wp + build: + context: . + dockerfile: docker/php80/Dockerfile + args: + UID: ${UID:-1000} + GID: ${GID:-1000} + ports: + - "8002:80" + depends_on: + - db + - smtp + user: ${UID:-1000}:${GID:-1000} + environment: + WORDPRESS_DEBUG: 1 + WORDPRESS_DB_NAME: wordpress + WORDPRESS_DB_HOST: db:3306 + WORDPRESS_DB_USER: wordpress + WORDPRESS_DB_PASSWORD: wordpress + PHP_IDE_CONFIG: "serverName=Mailpoet" + COMPOSER_HOME: "/tmp/.composer" + NPM_CONFIG_CACHE: "/tmp/.npm" + MAILPOET_DEV_SITE: 1 + volumes: + - "./wordpress:/var/www/html" + - "./mailpoet:/var/www/html/wp-content/plugins/mailpoet" + - "./mailpoet-premium:/var/www/html/wp-content/plugins/mailpoet-premium" + - "./templates:/var/www/templates" + + test_wordpress: + container_name: mp-test-wp + build: + context: . + dockerfile: docker/php80/Dockerfile + args: + UID: ${UID:-1000} + GID: ${GID:-1000} + ports: + - "8003:80" + depends_on: + - db + - smtp + user: ${UID:-1000}:${GID:-1000} + environment: + WORDPRESS_DB_NAME: test_wordpress + WORDPRESS_DB_HOST: db:3306 + WORDPRESS_DB_USER: wordpress + WORDPRESS_DB_PASSWORD: wordpress + PHP_IDE_CONFIG: "serverName=Mailpoet" + volumes: + - "./mailpoet:/var/www/html/wp-content/plugins/mailpoet" + - "./mailpoet-premium:/var/www/html/wp-content/plugins/mailpoet-premium" + + smtp: + container_name: mp-mailhog + image: mailhog/mailhog:v1.0.0 + user: ${UID:-1000}:${GID:-1000} + environment: + MH_STORAGE: maildir + MH_MAILDIR_PATH: /output + volumes: + - "./fake_mail_output:/output" + ports: + - "8082:8025" + + adminer: + container_name: mp-adminer + image: adminer:latest + depends_on: + - db + ports: + - "8081:8080" + volumes: + - "./docker/php.ini:/usr/local/etc/php/conf.d/custom.ini" + +volumes: + my-datavolume: diff --git a/docker/create_test_db.sh b/docker/create_test_db.sh new file mode 100755 index 0000000000..41124365af --- /dev/null +++ b/docker/create_test_db.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +RUNMYSQL="mysql --user=root --password=${MYSQL_ROOT_PASSWORD}" + +$RUNMYSQL < + + + + MailPoet dev environment + + + + + + +

Dev environment

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
πŸ–₯ + WordPress + [wp-admin] + App for developmenthttp://localhost:8002
πŸš₯ + WordPress Tests + [wp-admin] + App for E2E testshttp://localhost:8003
πŸ’Ύ + Adminer + DB management + http://localhost:8081 +
πŸ“ͺMailHogEmail catcherhttp://localhost:8082
+ + diff --git a/docker/dashboard/logo.svg b/docker/dashboard/logo.svg new file mode 100644 index 0000000000..5e8db7c11e --- /dev/null +++ b/docker/dashboard/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/docker/php.ini b/docker/php.ini new file mode 100644 index 0000000000..1f9f176596 --- /dev/null +++ b/docker/php.ini @@ -0,0 +1,5 @@ +upload_max_filesize = 500M +memory_limit = 1024M +post_max_size = 500M +sendmail_path = /usr/bin/msmtp -C /etc/msmtprc -t +sendmail_from = 'wordpress@mp3.localhost' diff --git a/docker/php74/Dockerfile b/docker/php74/Dockerfile new file mode 100644 index 0000000000..2b03fccfe4 --- /dev/null +++ b/docker/php74/Dockerfile @@ -0,0 +1,44 @@ +FROM wordpress:php7.4 + +ARG UID=1000 +ARG GID=1000 + +# additinal extensions +RUN apt-get update \ + && apt-get install -y git zlib1g-dev wget gnupg msmtp \ + && docker-php-ext-install pdo_mysql \ + && pecl install xdebug-2.9.8 && \ + \ + # Install NodeJS + NPM + curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ + apt-get install -y nodejs build-essential && \ + \ + # Install WP-CLI + curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \ + chmod +x /usr/local/bin/wp && \ + \ + # Installing Transifex Client + apt-get update && \ + apt-get install -y python3-pip gettext zip subversion && \ + pip install transifex-client && \ + \ + # Clean up + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +COPY docker/php.ini /usr/local/etc/php/conf.d/php_user.ini + +# msmtp config +RUN printf "account default\nhost smtp\nport 1025" > /etc/msmtprc + +# xdebug config +ENV XDEBUGINI_PATH=/usr/local/etc/php/conf.d/xdebug.ini +RUN echo "zend_extension="`find /usr/local/lib/php/extensions/ -iname 'xdebug.so'` > $XDEBUGINI_PATH +COPY docker/php74/xdebug.ini /tmp/xdebug.ini +RUN cat /tmp/xdebug.ini >> $XDEBUGINI_PATH + +# allow .htaccess files (between and , which is WordPress installation) +RUN sed -i '//,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf + +# ensure existing content in /var/www/html respects UID and GID +RUN chown -R ${UID}:${GID} /var/www/html diff --git a/docker/php74/xdebug.ini b/docker/php74/xdebug.ini new file mode 100644 index 0000000000..8d5aaa58d1 --- /dev/null +++ b/docker/php74/xdebug.ini @@ -0,0 +1,9 @@ +xdebug.remote_enable=on +xdebug.remote_autostart=on +xdebug.remote_connect_back=off +xdebug.remote_handler=dbgp +xdebug.profiler_enable=0 +xdebug.profiler_output_dir="/var/www/html" +xdebug.remote_host=host.docker.internal +xdebug.remote_port=9000 +# xdebug.remote_log="/var/www/html/xdebug_remote.log" diff --git a/docker/php80/Dockerfile b/docker/php80/Dockerfile new file mode 100644 index 0000000000..1be7d06cd6 --- /dev/null +++ b/docker/php80/Dockerfile @@ -0,0 +1,48 @@ +FROM wordpress:php8.0 + +ARG UID=1000 +ARG GID=1000 + +# additinal extensions +RUN apt-get update \ + && apt-get install -y git zlib1g-dev libzip-dev zip wget gnupg msmtp libpng-dev \ + && \ + # Install NodeJS + NPM + curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ + apt-get install -y nodejs build-essential && \ + \ + # Install WP-CLI + curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \ + chmod +x /usr/local/bin/wp && \ + \ + # Installing Transifex Client + apt-get update && \ + apt-get install -y python3-pip gettext subversion && \ + pip3 install transifex-client && \ + \ + # Clean up + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +COPY docker/php.ini /usr/local/etc/php/conf.d/php_user.ini + +# msmtp config +RUN printf "account default\nhost smtp\nport 1025" > /etc/msmtprc + +# xdebug build an config +ENV XDEBUGINI_PATH=/usr/local/etc/php/conf.d/xdebug.ini +RUN git clone -b "3.0.2" --depth 1 https://github.com/xdebug/xdebug.git /usr/src/php/ext/xdebug \ + && docker-php-ext-configure xdebug --enable-xdebug-dev \ + && docker-php-ext-install xdebug \ + && mkdir /tmp/debug +COPY docker/xdebug.ini /tmp/xdebug.ini +RUN cat /tmp/xdebug.ini >> $XDEBUGINI_PATH + +# php extensions +RUN docker-php-ext-install pdo_mysql + +# allow .htaccess files (between and , which is WordPress installation) +RUN sed -i '//,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf + +# ensure existing content in /var/www/html respects UID and GID +RUN chown -R ${UID}:${GID} /var/www/html diff --git a/docker/php81/Dockerfile b/docker/php81/Dockerfile new file mode 100644 index 0000000000..2b8324f1d8 --- /dev/null +++ b/docker/php81/Dockerfile @@ -0,0 +1,48 @@ +FROM wordpress:php8.1-apache + +ARG UID=1000 +ARG GID=1000 + +# additinal extensions +RUN apt-get update \ + && apt-get install -y git zlib1g-dev libzip-dev zip wget gnupg msmtp libpng-dev \ + && \ + # Install NodeJS + NPM + curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ + apt-get install -y nodejs build-essential && \ + \ + # Install WP-CLI + curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \ + chmod +x /usr/local/bin/wp && \ + \ + # Installing Transifex Client + apt-get update && \ + apt-get install -y python3-pip gettext subversion && \ + pip3 install transifex-client && \ + \ + # Clean up + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +COPY docker/php.ini /usr/local/etc/php/conf.d/php_user.ini + +# msmtp config +RUN printf "account default\nhost smtp\nport 1025" > /etc/msmtprc + +# xdebug build an config +ENV XDEBUGINI_PATH=/usr/local/etc/php/conf.d/xdebug.ini +RUN git clone -b "3.1.1" --depth 1 https://github.com/xdebug/xdebug.git /usr/src/php/ext/xdebug \ + && docker-php-ext-configure xdebug --enable-xdebug-dev \ + && docker-php-ext-install xdebug \ + && mkdir /tmp/debug +COPY docker/xdebug.ini /tmp/xdebug.ini +RUN cat /tmp/xdebug.ini >> $XDEBUGINI_PATH + +# php extensions +RUN docker-php-ext-install pdo_mysql + +# allow .htaccess files (between and , which is WordPress installation) +RUN sed -i '//,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf + +# ensure existing content in /var/www/html respects UID and GID +RUN chown -R ${UID}:${GID} /var/www/html diff --git a/docker/xdebug.ini b/docker/xdebug.ini new file mode 100644 index 0000000000..4c09b73d93 --- /dev/null +++ b/docker/xdebug.ini @@ -0,0 +1,6 @@ +xdebug.mode=debug +xdebug.output_dir="/var/www/html" +xdebug.client_host=host.docker.internal +xdebug.client_port=9000 +xdebug.discover_client_host=On +# xdebug.log="/var/www/html/xdebug.log" diff --git a/initial-setup.sh b/initial-setup.sh new file mode 100755 index 0000000000..7b1c85cf01 --- /dev/null +++ b/initial-setup.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +git clone git@github.com:mailpoet/mailpoet.git +git clone git@github.com:mailpoet/mailpoet-premium.git + +# Save current UID and GID to .env so we can run images with current user +# to avoid any potential problems with file permissions (mainly on Linux). +cat < .env +UID=$(id -u) +UID=$(id -g) +EOT + +cat < mailpoet/.env + +WP_ROOT="/var/www/html" +WP_TEST_CACHE_PATH="/tmp" +WP_TEST_MAILER_ENABLE_SENDING="true" +WP_TEST_ENABLE_NETWORK_TESTS="true" + +# get following secrets from the Secret Store. Look for "MailPoet plugin .env" +WP_TRANSIFEX_API_TOKEN= +WP_TEST_IMPORT_MAILCHIMP_API= +WP_TEST_IMPORT_MAILCHIMP_LISTS= +WP_TEST_MAILER_AMAZON_ACCESS= +WP_TEST_MAILER_AMAZON_SECRET= +WP_TEST_MAILER_AMAZON_REGION= +WP_TEST_MAILER_MAILPOET_API= +WP_TEST_MAILER_SENDGRID_API= +WP_TEST_MAILER_SMTP_HOST= +WP_TEST_MAILER_SMTP_LOGIN= +WP_TEST_MAILER_SMTP_PASSWORD= +WP_SVN_USERNAME= +WP_SVN_PASSWORD= +WP_SLACK_WEBHOOK_URL= +WP_CIRCLECI_USERNAME= +WP_CIRCLECI_TOKEN= + +# get GitHub token from https://github.com/settings/tokens with repo access +WP_GITHUB_USERNAME= +WP_GITHUB_TOKEN= + +# get Jira token from https://id.atlassian.com/manage/api-tokens +WP_JIRA_USER= +WP_JIRA_TOKEN= + +EOT +cp mailpoet/.env mailpoet-premium +echo "MAILPOET_FREE_PATH=/var/www/html/wp-content/plugins/mailpoet" >> mailpoet-premium/.env + +# create Docker mount endpoints beforehand with current user (Docker would create them as root) +mkdir -p wordpress/wp-content/plugins/mailpoet +mkdir -p wordpress/wp-content/plugins/mailpoet-premium +mkdir -p fake_mail_output + +for plugin in "mailpoet" "mailpoet-premium"; do + docker-compose run --rm wordpress /bin/sh -c " + cd /var/www/html/wp-content/plugins/$plugin && + ./do install && + ./do compile:all + " +done + +docker-compose run --rm wordpress /bin/sh -c " + cd /var/www/templates && + mkdir assets classes exported +" +echo 'Donβ€˜t forget to configure environment variables in .env files in ./mailpoet and ./mailpoet-premium' +echo 'You can run the environment by executing "./do start" and visiting http://localhost:8002' diff --git a/mac-nfs-setup.sh b/mac-nfs-setup.sh new file mode 100755 index 0000000000..c28f622261 --- /dev/null +++ b/mac-nfs-setup.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env sh + +# ensure this is run on MacOS +if [ "$(uname -s)" != "Darwin" ]; then + (>&2 echo "This script can only be run on MacOS.") + exit 1 +fi + +# ensure root privileges +if [ $EUID -ne 0 ]; then + (>&2 echo "This script must be run with sudo.") + exit 1 +fi + +# add shared file settings for current user to /etc/exports (if not set yet) +LINE="/System/Volumes/Data/Users -alldirs -mapall="$SUDO_USER":$(id -gn "$SUDO_USER") localhost" +FILE=/etc/exports +grep -qF -- "$LINE" "$FILE" || echo "$LINE" >> "$FILE" + +# add NFS settings to /etc/nfs.conf (if not set yet) +LINE="nfs.server.mount.require_resv_port = 0" +FILE=/etc/nfs.conf +grep -qF -- "$LINE" "$FILE" || echo "$LINE" >> "$FILE" + +# restart NFS server +nfsd restart + +cat < PHP > Servers` set path mappings: + +```shell +mailpoet-dev-env/wordpress -> /var/www/html +mailpoet-dev-env/mailpoet -> /var/www/html/wp-content/plugins/mailpoet +mailpoet-dev-env/mailpoet-premium -> /var/www/html/wp-content/plugins/mailpoet-premium +``` + +For PHP 8 and XDebug 3 we support **browser debugging extension**. +You can choose extension by your browser in [JetBrains documentation](https://www.jetbrains.com/help/phpstorm/browser-debugging-extensions.html). + +To use XDebug inside the **cron**, you need to pass a URL argument `&XDEBUG_TRIGGER=yes` +[in the cron request](https://github.com/mailpoet/mailpoet/blob/bf7bd6d2d9090ed6ec7b8b575bb7d6b08e663a52/lib/Cron/CronHelper.php#L155-L166). +Alternatively, you can add `XDEBUG_TRIGGER: yes` to the `wordpress` service in `docker-compose.yml` and restart it (which will run XDebug also for all other requests). + +## πŸ’Ύ NFS volume sharing for Mac +NFS volumes can bring more stability and performance on Docker for Mac. To setup NFS volume sharing run: +```shell +sudo sh mac-nfs-setup.sh +``` + +Then create a Docker Compose override file with NFS settings and restart containers: +```shell +cp docker-compose.override.macos-sample.yml docker-compose.override.yml + +docker-compose down -v --remove-orphans +docker-compose up -d +``` + +## πŸ•Ή Commands +The `./do` script define aliases for most of the commands you will need while working on plugins: + +```shell +./do setup Setup the environment. +./do start Start the docker containers. +./do stop Stop the docker containers. +./do ssh [--test] Run an interactive bash shell inside the plugin directory. +./do run [--test] Run a custom bash command in the wordpress container. +./do acceptance [--premium] Run acceptance tests. +./do build [--premium] Builds a .zip for the plugin. +./do templates Generates templates classes and assets. +./do [--test] [--premium] Run './do ' inside the plugin directory. + +Options: + --test Run the command using the 'test_wordpress' service. + --premium Run the command inside the premium plugin. +``` + +You can access this help in your command line running `./do` without parameters. + +## βœ‰οΈ Adding new templates to the plugin +[Read the article.](https://mailpoet.atlassian.net/wiki/spaces/MAILPOET/pages/629374977/Adding+new+templates+to+the+plugin) + +## πŸš₯ Testing with PHP 7.4 or PHP 8.1 +To switch the environment to PHP 7.4/8.1: +1) Configure the `wordpress` service in `docker-compose.override.yml` to build from the php74 Dockerfile: + + ```yaml + wordpress: + build: + context: . + dockerfile: docker/php74/Dockerfile # OR docker/php81/Dockerfile + ``` +3) Run `docker-compose build wordpress`. +4) Start the stack with `./do start`. + +To switch back to PHP 8 remove what was added in 1) and, run `docker-compose build wordpress` for application container and `docker-compose build test_wordpress` for tests container, +and start the stack using `./do start`. + +## βœ… TODO +- install woo commerce, members and other useful plugins by default diff --git a/templates/.gitignore b/templates/.gitignore new file mode 100644 index 0000000000..aaf8363045 --- /dev/null +++ b/templates/.gitignore @@ -0,0 +1,5 @@ +vendor +assets +classes +exported +composer.phar diff --git a/templates/Class.php.tpl b/templates/Class.php.tpl new file mode 100644 index 0000000000..089fe8596d --- /dev/null +++ b/templates/Class.php.tpl @@ -0,0 +1,33 @@ +template_image_url = 'https://ps.w.org/mailpoet/assets/newsletter-templates/{{dir_name}}'; + $this->social_icon_url = $assets_url . '/img/newsletter_editor/social-icons'; + } + + function get() { + return array( + 'name' => __("{{template_name}}", 'mailpoet'), + 'categories' => json_encode(array('{{category}}', 'all')), + 'readonly' => 1, + 'thumbnail' => $this->getThumbnail(), + 'body' => json_encode($this->getBody()), + ); + } + + private function getThumbnail() { + return $this->template_image_url . '/thumbnail.jpg'; + } + + private function getBody() { + return {{body}}; + } + +} diff --git a/templates/composer.json b/templates/composer.json new file mode 100644 index 0000000000..c196cc3d33 --- /dev/null +++ b/templates/composer.json @@ -0,0 +1,6 @@ +{ + "name": "mailpoet/templates", + "require": { + "tarsana/functional": "^2.2" + } +} diff --git a/templates/composer.lock b/templates/composer.lock new file mode 100644 index 0000000000..ba394cc9a0 --- /dev/null +++ b/templates/composer.lock @@ -0,0 +1,66 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a607345ee7a66b12b9ee1d37141baa67", + "packages": [ + { + "name": "tarsana/functional", + "version": "2.2.2", + "source": { + "type": "git", + "url": "https://github.com/tarsana/functional.git", + "reference": "77ab9cbe8e44865edb686c07cc0848d8a950ed19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tarsana/functional/zipball/77ab9cbe8e44865edb686c07cc0848d8a950ed19", + "reference": "77ab9cbe8e44865edb686c07cc0848d8a950ed19", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tarsana\\Functional\\": "src/Classes/", + "Tarsana\\UnitTests\\Functional\\": "tests/" + }, + "files": [ + "src/Internal/_functions.php", + "src/Internal/_stream.php", + "src/functions.php", + "src/operators.php", + "src/common.php", + "src/object.php", + "src/string.php", + "src/list.php", + "src/math.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Amine Ben hammou", + "email": "webneat@gmail.com" + } + ], + "description": "functional programming library for PHP", + "time": "2018-07-21T01:02:03+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/templates/config.php b/templates/config.php new file mode 100644 index 0000000000..e8ace13ba8 --- /dev/null +++ b/templates/config.php @@ -0,0 +1,7 @@ + __DIR__ . '/exported', + 'assetsDir' => __DIR__ . '/assets', + 'classesDir' => __DIR__ . '/classes', + 'classTemplatePath' => __DIR__ . '/Class.php.tpl', +]; diff --git a/templates/generate.php b/templates/generate.php new file mode 100644 index 0000000000..6b67ebb092 --- /dev/null +++ b/templates/generate.php @@ -0,0 +1,197 @@ +templatesDir) + ); +} + +function loadTemplates(string $templatesDir) : array { + return F\s(glob("{$templatesDir}/*.json")) + ->map(F\pipe('file_get_contents', function($content) { + return json_decode($content, true); + })) + ->result(); +} + +function generate(object $config, array $template) : void { + $templateDir = makeTemplateDirectory($config->assetsDir, $template); + $classTemplate = file_get_contents($config->classTemplatePath); + F\s($template) + ->saveThumbnail($templateDir) + ->normalizeBody($templateDir) + ->renderBody() + ->renderTemplate($classTemplate) + ->saveTemplate($config->classesDir) + ->result(); + echo $template['name'], ' generated!', PHP_EOL; +} + +function makeTemplateDirectory(string $assetsDir, array $template) : string { + $path = $assetsDir. '/' . getDirectoryName($template); + if (!is_dir($path) && !mkdir($path)) + throw new \Exception("Unable to create directory '{$path}'!"); + return $path; +} + +function saveThumbnail(string $templateDir, array $template) : array { + $thumbnail = substr($template['thumbnail'], 23); // 'data:image/jpeg;base64,' is 23 chars + file_put_contents($templateDir.'/thumbnail.jpg', base64_decode($thumbnail)); + return $template; +} +F\Stream::operation('saveThumbnail', 'String -> Array -> Array'); + +function normalizeBody(string $templateDir, array $template) : array { + $template['body'] = F\s($template['body']) + ->removeContainerDefaults() + ->downloadAndReplaceImages($templateDir) + ->replaceSocialIconURLs() + ->result(); + return $template; +} +F\Stream::operation('normalizeBody', 'String -> Array -> Array'); + +function renderBody(array $template) : array { + $template['body'] = F\s($template['body']) + ->then('export') + ->render([ + 'image_url' => '\' . $this->template_image_url . \'', + 'icon_url' => '\' . $this->social_icon_url . \'' + ]) + ->replace("'' . ", '') + ->split("\n") + ->map(F\prepend(" ")) + ->join("\n") + ->then('trim') + ->result(); + + return $template; +} +F\Stream::operation('renderBody', 'Array -> Array'); + +function renderTemplate(string $classTemplate, array $template) : array { + $template['body'] = render([ + 'class_name' => getClassName($template), + 'dir_name' => getDirectoryName($template), + 'template_name' => $template['name'], + 'category' => getCategory($template), + 'body' => $template['body'] + ], $classTemplate); + + return $template; +} +F\Stream::operation('renderTemplate', 'String -> Array -> Array'); + +function saveTemplate(string $classesDir, array $template) : void { + file_put_contents($classesDir . '/' . getClassName($template) . '.php', $template['body']); +} +F\Stream::operation('saveTemplate', 'String -> Array -> Null'); + +function removeContainerDefaults(array $body) : array { + unset($body['blockDefaults']['container']); + return $body; +} +F\Stream::operation('removeContainerDefaults', 'Array -> Array'); + +function downloadAndReplaceImages(string $templateDir, array $block) : array { + if (!empty($block['content'])) { + $block['content'] = downloadAndReplaceImages($templateDir, $block['content']); + } + + if ($block['type'] == 'image' && !empty($block['src'])) { + $block['src'] = downloadImage($templateDir, $block['src']); + } + + if (!empty($block['image']) && !empty($block['image']['src'])) { + $block['image']['src'] = downloadImage($templateDir, $block['image']['src']); + } + + if (!empty($block['blocks'])) { + foreach ($block['blocks'] as $i => $innerBlock) { + $block['blocks'][$i] = downloadAndReplaceImages($templateDir, $innerBlock); + } + } + + return $block; +} +F\Stream::operation('downloadAndReplaceImages', 'String -> Array -> Array'); + +function replaceSocialIconURLs(array $block) : array { + if (!empty($block['content'])) { + $block['content'] = replaceSocialIconURLs($block['content']); + } + + if (!empty($block['blockDefaults']) && !empty($block['blockDefaults']['social'])) { + $block['blockDefaults']['social'] = replaceSocialIconURLs($block['blockDefaults']['social']); + } + + if ($block['type'] == 'socialIcon' && !empty($block['image'])) { + $block['image'] = F\s($block['image']) + ->split('?') + ->head() + ->split('/') + ->take(-2) + ->prepend('{{icon_url}}') + ->join('/') + ->result(); + } + + if (!empty($block['icons'])) { + foreach ($block['icons'] as $i => $icon) { + $block['icons'][$i] = replaceSocialIconURLs($icon); + } + } + + if (!empty($block['blocks'])) { + foreach ($block['blocks'] as $i => $innerBlock) { + $block['blocks'][$i] = replaceSocialIconURLs($innerBlock); + } + } + + return $block; +} +F\Stream::operation('replaceSocialIconURLs', 'Array -> Array'); + +function render(array $data, string $text) : string { + foreach ($data as $name => $value) { + $text = str_replace('{{'.$name.'}}', $value, $text); + } + return $text; +} +F\Stream::operation('render', 'Array -> String -> String'); + + +function getDirectoryName(array $template) : string { + return F\snakeCase('-', $template['name']); +} + +function getClassName(array $template) : string { + return ucfirst(F\camelCase($template['name'])); +} + +function getCategory(array $template) : string { + $categories = json_decode($template['categories']); + return $categories[0] == 'saved' ? $categories[1] : $categories[0]; +} + +function downloadImage($templateDir, $src) { + $name = F\s($src) + ->split('/') + ->last() + ->split('?') + ->head() + ->result(); + file_put_contents($templateDir . '/' . $name, file_get_contents($src)); + return '{{image_url}}/' . $name; +} + +function export($variable) : string { + return var_export($variable, true); +} + +main(require __DIR__.'/config.php');